# 精灵文本实体


`SpriteTextsEntity` 是由 实体`Entity` 和 `SpriteTextsModule` 模块组合而成的新的实体。

 [SpriteTextsEntity](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html) =  [Entity](https://vjmap.com/map3d/api/classes/Entity.html) + [SpriteTextsModule](https://vjmap.com/map3d/api/classes/SpriteTextsModule.html) 

 `SpriteTextsModule` 中引用的是  [SpriteTexts](https://vjmap.com/map3d/api/classes/SpriteTexts.html)  对象
 ## 常用方法


 `SpriteTextsEntity` 是由 实体`Entity` 派生而来。具体 [Entity](https://vjmap.com/map3d/api/classes/Entity.html) 的所有属性和方法。同时也具有[SpriteTextsModule](https://vjmap.com/map3d/api/classes/SpriteTextsModule.html)  的相关方法。通过 [SpriteTexts](https://vjmap.com/map3d/api/classes/SpriteTextsModule.html#SpriteTexts) 对象 可访问  [SpriteTexts](https://vjmap.com/map3d/api/classes/SpriteTexts.html)  类的所有方法


 
|方法|描述|
|:---|:---|
|[SpriteTexts](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#SpriteTexts)|获取精灵图像对象|
|[getData](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#getData)|获取数据|
|[setData](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#setData)|设置数据。⚠️ 内容未变的文字精灵会被**复用**、不重设 `userData.objectDataIndex`，删除/中间插入后事件拿到的下标会错位，见下文"修改、新增、删除"|
|[setItemHighlight](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#setItemHighlight)|设置某项高亮：`setItemHighlight(index, isHighlight)`，`index` 是数据**下标**（事件里的 `userData.objectDataIndex`），不是 id。⚠️ 仅当该数据项设置了 `highlightStyle`（高亮时的文字样式）才有效，否则静默无效|
|[update](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#update)|更新数据|
|[getOptions](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#getOptions)|获取选项|
## 使用示例


### 创建SpriteTextsEntity

```js
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    stat: { show: true, left: "0" },
    scene: {
        gridHelper: { visible: true,  } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let data = [];
for(let i = 0; i < 20; i++) {
    for(let j = 0; j < 20; j++) {
        let id = j * 20 + i;
        let strId = id + '';
        if (strId.length <= 2) strId = " " + strId;
        data.push({
            position:  [i * 5 - 50, 0, j * 5 - 50],
            text: `标签\n${strId}`,
            style: {
                x: 5,
                y: 5,
                fill: vjmap3d.render2d.color.random(),
                font: '16px Microsoft Yahei',//22px sans-serif
                padding: [10, 20, 20, 20],
                backgroundColor: {
                    image: image
                }
            },
            id: id
        });
    }
}
let spriteTextsEnt = new vjmap3d.SpriteTextsEntity({
    data: data,
    sizeAttenuation: false,
    allowOverlap: false,
});
spriteTextsEnt.addTo(app);
```

### 增加事件
```js
spriteTextsEnt.pointerEvents = true
spriteTextsEnt.on("mouseover", e => {
    let index = e?.intersection?.object?.userData.objectDataIndex
    if (index !== undefined) {
        spriteTextsEnt.setItemHighlight(index, true);
        app.popup2d.show();
        app.popup2d.setOffset([0, -40]);
        app.popup2d.setHTML(`<span style="color:red">ID:${data[index].id}</span>`)
        app.popup2d.setPosition(data[index].position)
    }
})
spriteTextsEnt.on("mouseout", e => {
    let index = e?.intersection?.object?.userData.objectDataIndex
    if (index !== undefined) {
        spriteTextsEnt.setItemHighlight(index, false)
        app.popup2d.hide()
    }
})
spriteTextsEnt.on("click", e => {
    let index = e?.intersection?.object?.userData.objectDataIndex
    if (index !== undefined) {
        app.logInfo(`您点击了ID:${data[index].id}`)
    }
})
```

### 修改、新增、删除
调用 [setData](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#setData) 来对数据进行增删改

> ⚠️ `setData` 会按"id+位置+文字+样式"复用内容未变的精灵，被复用的精灵**不会更新** `userData.objectDataIndex`（也不回填数据项的 `object`）。末尾追加、原位修改没问题；但**删除或在中间插入**后，后面那些精灵的下标全部错位，事件里 `data[index]` 会指向别的项（带 `highlightStyle` 时 `setItemHighlight` 还可能抛错）。删除/插入时先清空再重设：`spriteTextsEnt.setData([]); spriteTextsEnt.setData(data);`
```js
// 如新增一条
 data.push({
    position: vjmap3d.randPoint3D(),
    text: `标签\n${data.length + 1}`,
    style: {
        x: 5,
        y: 5,
        fill: vjmap3d.render2d.color.random(),
        font: '16px Microsoft Yahei',//22px sans-serif
        padding: [10, 20, 20, 20],
        backgroundColor: {
            image: image
        }
    },
    id: data.length + 1
})
spriteTextsEnt.setData(data)
```

### 设置是否允许重叠
调用 [getOptions](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#getOptions) 来获取属性，`update`更新数据
```js
spriteTextsEnt.getOptions().allowOverlap = true;
spriteTextsEnt.update(); // 更新
```

### 设置透明度随相机距离大小消退距离
调用 [getOptions](https://vjmap.com/map3d/api/classes/SpriteTextsEntity.html#getOptions) 来获取属性，`update`更新数据
```js
spriteTextsEnt.getOptions().fadeDistance = 100; // 距相机距离
spriteTextsEnt.update(); // 更新
```

> ⚠️ webgl 引擎下 `fadeDistance` 是在文字材质**首次编译**时写进着色器的，而文字材质按"文字+样式"缓存复用，`update()` 不会让已渲染过的材质重新编译——所以运行时改 `fadeDistance` 后 `update()` 对已显示的文字**基本不生效**。最好在构造时就设定；运行时要改，先 `setData([])`（会释放缓存材质）再 `setData(data)` 重建。

### 示例
<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    stat: { show: true, left: "0" },
    scene: {
        gridHelper: { visible: true,  } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let data = [];
for(let i = 0; i < 20; i++) {
    for(let j = 0; j < 20; j++) {
        let id = j * 20 + i;
        let strId = id + '';
        if (strId.length <= 2) strId = " " + strId;
        data.push({
            position:  [i * 5 - 50, 0, j * 5 - 50],
            text: `标签\n${strId}`,
            style: {
                x: 5,
                y: 5,
                fill: vjmap3d.render2d.color.random(),
                font: '16px Microsoft Yahei',//22px sans-serif
                padding: [10, 20, 20, 20],
                backgroundColor: {
                    image: image
                }
            },
            id: id
        });
    }
}
let spriteTextsEnt = new vjmap3d.SpriteTextsEntity({
    data: data,
    sizeAttenuation: false,
    allowOverlap: false,
});
spriteTextsEnt.addTo(app);
//
spriteTextsEnt.pointerEvents = true
spriteTextsEnt.on("mouseover", e => {
    let index = e?.intersection?.object?.userData.objectDataIndex
    if (index !== undefined) {
        spriteTextsEnt.setItemHighlight(index, true);
        app.popup2d.show();
        app.popup2d.setOffset([0, -40]);
        app.popup2d.setHTML(`<span style="color:red">ID:${data[index].id}</span>`)
        app.popup2d.setPosition(data[index].position)
    }
})
spriteTextsEnt.on("mouseout", e => {
    let index = e?.intersection?.object?.userData.objectDataIndex
    if (index !== undefined) {
        spriteTextsEnt.setItemHighlight(index, false)
        app.popup2d.hide()
    }
})
spriteTextsEnt.on("click", e => {
    let index = e?.intersection?.object?.userData.objectDataIndex
    if (index !== undefined) {
        app.logInfo(`您点击了ID:${data[index].id}`)
    }
})
const ui = await app.getConfigPane({ title: "设置", style: { width: "250px", overflow: "hidden"}});
let cfg = [
    {
        type: 'checkbox',
        label: '允许重叠',
        property: [spriteTextsEnt.getOptions(), "allowOverlap"],
        onChange: () => {
           spriteTextsEnt.update()
        }
    },
    {
        type: 'slider',
        label: '消退距离',
        bounds: [0, 5000],
        stepSize: 1,
        getValue: () => spriteTextsEnt.getOptions().fadeDistance ?? 0,
        setValue: (v) => {
           spriteTextsEnt.getOptions().fadeDistance = v;
           // webgl 下缓存材质不会重编译，update() 不够，需清空重建
           spriteTextsEnt.setData([])
           spriteTextsEnt.setData(data)
        }
    },
    {
        type: 'button',
        label: '再增加一个',
        value: ()=> {
            data.push({
                position: vjmap3d.randPoint3D(),
                text: `标签\n${data.length + 1}`,
                style: {
                    x: 5,
                    y: 5,
                    fill: vjmap3d.render2d.color.random(),
                    font: '16px Microsoft Yahei',//22px sans-serif
                    padding: [10, 20, 20, 20],
                    backgroundColor: {
                        image: image
                    }
                },
                id: data.length + 1
            })
            spriteTextsEnt.setData(data)
        }
    },
    {
        type: 'button',
        label: '修改第一个文字',
        value: ()=> {
            if (data.length > 0) {
                data[0].text = "修改";
            }
            spriteTextsEnt.setData(data)
        }
    },
    {
        type: 'button',
        label: '删除第一个',
        value: ()=> {
            if (data.length == 0) {
                return
            }
            data.splice(0, 1)
            // 删除会让复用精灵的下标错位，先清空再重设
            spriteTextsEnt.setData([])
            spriteTextsEnt.setData(data)
        }
    }
]
cfg.forEach(c => ui.appendChild(c));
</pre>
</mapFrame>




## SpriteImagesEntity 和 SpriteTextsEntity 组合使用

`SpriteImagesEntity` 和 `SpriteTextsEntity` 组合使用时，碰撞检测时，如需对应的两组同时隐藏或显示，需设置`collideId`属性为同一个，根据 `collideZIndex` 来进行排序
```js
let textDatas = [];
let imageDatas = [];
for(let i = 0; i < 20; i++) {
    for(let j = 0; j < 20; j++) {
        let id = j * 20 + i;
        let position =  [i * 5 - 50, 0, j * 5 - 50]
        // 图片
        imageDatas.push({
            position: position,
            icon: env.assetsPath + `image/sensor${(i % 5) + 1}.png`,
            highlightOpacity: 0.6,
            id: id,
            collideZIndex: id,
            collideId: 'spriteImageText' + id, // 同组的id要一样
        });

        // 文本
        textDatas.push({
            position: position,
            text: `ID: ${id}`,
            style: {
                fill: '#FF1285',
                font: '20px sans-serif',
                padding: [5, 5, 5, 5],
                backgroundColor: '#FFFE91',
                borderColor: '#FFFE91',
                borderWidth: 2,
                borderRadius: 5
            },
            id: id,
            anchorX: "center",
            anchorY: "top",
            offsetY: 5,
            collideZIndex: id,
            collideId: 'spriteImageText' + id, // 同组的id要一样
        });
    }
}
//
let entImage = new vjmap3d.SpriteImagesEntity({
    data: imageDatas,
    sizeAttenuation: false,
    allowOverlap: false,
});
entImage.addTo(app);
//
let entText = new vjmap3d.SpriteTextsEntity({
    data: textDatas,
    sizeAttenuation: false,
    allowOverlap: false,
});
entText.addTo(app);
```


<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    stat: { show: true, left: "0" },
    scene: {
        gridHelper: { visible: true,  } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let textDatas = [];
let imageDatas = [];
for(let i = 0; i < 20; i++) {
    for(let j = 0; j < 20; j++) {
        let id = j * 20 + i;
        let position =  [i * 5 - 50, 0, j * 5 - 50]
        // 图片
        imageDatas.push({
            position: position,
            icon: env.assetsPath + `image/sensor${(i % 5) + 1}.png`,
            highlightOpacity: 0.6,
            id: id,
            collideId: 'spriteImageText' + id,
            collideZIndex: id,
        });
        // 文本
        textDatas.push({
            position: position,
            text: `ID: ${id}`,
            style: {
                fill: '#FF1285',
                font: '20px sans-serif',
                padding: [5, 5, 5, 5],
                backgroundColor: '#FFFE91',
                borderColor: '#FFFE91',
                borderWidth: 2,
                borderRadius: 5
            },
            id: id,
            anchorX: "center",
            anchorY: "top",
            offsetY: 5,
            collideZIndex: id,
            collideId: 'spriteImageText' + id,
        });
    }
}
let entImage = new vjmap3d.SpriteImagesEntity({
    data: imageDatas,
    sizeAttenuation: false,
    allowOverlap: false,
});
entImage.addTo(app);
//
let entText = new vjmap3d.SpriteTextsEntity({
    data: textDatas,
    sizeAttenuation: false,
    allowOverlap: false,
});
entText.addTo(app);
</pre>
</mapFrame>
