# 精灵标记实体

`SpriteMarkerEntity` 是由 实体`Entity` 和 `SpriteMarkerModule` 模块组合而成的新的实体。

 [SpriteMarkerEntity](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html) =  [Entity](https://vjmap.com/map3d/api/classes/Entity.html) + [SpriteMarkerModule](https://vjmap.com/map3d/api/classes/SpriteMarkerModule.html) 

 `SpriteMarkerModule` 中引用的是  [SpriteMarker](https://vjmap.com/map3d/api/classes/SpriteMarker.html)  对象
 ## 常用方法


 `SpriteMarkerEntity` 是由 实体`Entity` 派生而来。具体 [Entity](https://vjmap.com/map3d/api/classes/Entity.html) 的所有属性和方法。同时也具有[SpriteMarkerModule](https://vjmap.com/map3d/api/classes/SpriteMarkerModule.html)  的相关方法。通过 [SpriteMarker](https://vjmap.com/map3d/api/classes/SpriteMarkerModule.html#SpriteMarker) 对象 可访问  [SpriteMarker](https://vjmap.com/map3d/api/classes/SpriteMarker.html)  类的所有方法


 
|方法|描述|
|:---|:---|
|[SpriteMarker](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#SpriteMarker)|获取精灵标记对象|
|[setMaterial](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#setMaterial)|设置材质|
|[updateRenderTexture](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#updateRenderTexture)|更新渲染材质|
|[refreshRenderTexture](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#refreshRenderTexture)|刷新渲染材质|
|[dispatchRenderTextureEvent](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#dispatchRenderTextureEvent)|给渲染材质触发相应事件|
|[setAllowOverlap](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#setAllowOverlap)|设置是否允许重叠|
|[updateScale](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#updateScale)|更新缩放尺寸|
|[getBoundingRect](https://vjmap.com/map3d/api/classes/SpriteMarkerEntity.html#getBoundingRect)|获取矩形范围|


    
## 使用示例


### 创建SpriteMarkerEntity
```js
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, position: "leftBottom" } // 是否显示视角指示器
    }
})
let texture = await vjmap3d.ResManager.loadTextureSync(env.assetsPath + "image/sensor1.png")
let spriteMarker = new vjmap3d.SpriteMarkerEntity(
{
    position: [-2, 0, 1], // 位置 
    sizeAttenuation: false, // 固定像素大小，不随相机变化
    anchorX: "center",  // x对齐方式
    anchorY: "bottom", // y对齐方式
    texture: texture // 材质
});
spriteMarker.addTo(app)
```

如果不做为实体创建，只创建`SpriteMarker`，可用如下代码:
```js
let spriteMarker = new vjmap3d.SpriteMarker(
{
    position: [-2, 0, 1], // 位置 
    sizeAttenuation: false, // 固定像素大小，不随相机变化
    anchorX: "center",  // x对齐方式
    anchorY: "bottom", // y对齐方式
    texture: texture // 材质
}, app);
app.scene.add(spriteMarker)
```

<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, position: "leftBottom" } // 是否显示视角指示器
    }
})
let texture = await vjmap3d.ResManager.loadTextureSync(env.assetsPath + "image/sensor1.png")
let spriteMarker = new vjmap3d.SpriteMarkerEntity(
{
    position: [-2, 0, 1], // 位置 
    sizeAttenuation: false, // 固定像素大小，不随相机变化
    anchorX: "center",  // x对齐方式
    anchorY: "bottom", // y对齐方式
    texture: texture // 材质
});
spriteMarker.addTo(app)
</pre>
</mapFrame>






### 通过渲染材质绘制

原理： 通过2D渲染引擎 [render2d](../advance/render2d.md) 渲染出材质，做为材质参数输入

```js
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let spriteMarker = new vjmap3d.SpriteMarkerEntity(
{
    position: [-2, 0, 1], // 位置 
    sizeAttenuation: false, // 固定像素大小，不随相机变化
    anchorX: "center",  // x对齐方式
    anchorY: "bottom", // y对齐方式
    renderTexture: { // 渲染材质
        autoCanvasSize: false,
        canvasWidth: 80, // 像素宽，这里是根据底图的像素宽高定的
        canvasHeight: 80, // 像素高，这里是根据底图的像素宽高定的
        sharedCanvas: false,
        elements: [
            new vjmap3d.render2d.Image({
                style: {
                    x: 0,
                    y: 0,
                    image: image,
                    width: 80, // 像素宽，这里是图片像素宽高定的
                    height: 80 // 像素高，这里是图片像素宽高定的
                }
            }),
            new vjmap3d.render2d.Text({
                style: {
                    x: 40,
                    y: 20,
                    text: '唯杰',
                    fill: "#0ff",
                    font: '30px Arial',
                    verticalAlign: "top",
                    align: "center"
                }
            })
        ]
    }
});
spriteMarker.addTo(app)
```

<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, position: "leftBottom" } // 是否显示视角指示器
    }
})
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let spriteMarker = new vjmap3d.SpriteMarkerEntity(
{
    position: [-2, 0, 1], // 位置 
    sizeAttenuation: false, // 固定像素大小，不随相机变化
    anchorX: "center",  // x对齐方式
    anchorY: "bottom", // y对齐方式
    renderTexture: {
        autoCanvasSize: false,
        canvasWidth: 80, // 像素宽，这里是根据底图的像素宽高定的
        canvasHeight: 80, // 像素高，这里是根据底图的像素宽高定的
        sharedCanvas: false,
        elements: [
            new vjmap3d.render2d.Image({
                style: {
                    x: 0,
                    y: 0,
                    image: image,
                    width: 80, // 像素宽，这里是图片像素宽高定的
                    height: 80 // 像素高，这里是图片像素宽高定的
                }
            }),
            new vjmap3d.render2d.Text({
                style: {
                    x: 40,
                    y: 20,
                    text: '唯杰',
                    fill: "#0ff",
                    font: '30px Arial',
                    verticalAlign: "top",
                    align: "center",
                    //backgroundColor: vjmap3d.render2d.color.random(),
                    //borderColor: vjmap3d.render2d.color.random(),
                    //borderWidth: 2,
                    //borderRadius: 5,
                    // textShadowBlur: 2,
                    // textShadowColor: '#893e95',
                    // textShadowOffsetX: 2,
                    // textShadowOffsetY: 4,
                }
            })
        ]
    }
});
spriteMarker.addTo(app)
</pre>
</mapFrame>




### 随相机尺寸大小变化

需要进行以下设置
```
/** 是否使用大小衰减(根据相机远近自动调整点的大小) false的话，表示用像素大小，true表示世界坐标 */
sizeAttenuation: true,
/** 宽(sizeAttenuation为false是像素大小，否则表示世界坐标宽度) */
width: 1,
```


<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, position: "leftBottom" } // 是否显示视角指示器
    }
})
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let spriteMarker = new vjmap3d.SpriteMarkerEntity(
{
    position: [-2, 0, 1], // 位置 
    sizeAttenuation: true, // 世界坐标大小，随相机远近缩放（近大远小）
    width: 1, // 世界坐标宽度
    anchorX: "center",  // x对齐方式
    anchorY: "bottom", // y对齐方式
    renderTexture: {
        autoCanvasSize: false,
        canvasWidth: 80, // 像素宽，这里是根据底图的像素宽高定的
        canvasHeight: 80, // 像素高，这里是根据底图的像素宽高定的
        sharedCanvas: false,
        elements: [
            new vjmap3d.render2d.Image({
                style: {
                    x: 0,
                    y: 0,
                    image: image,
                    width: 80, // 像素宽，这里是图片像素宽高定的
                    height: 80 // 像素高，这里是图片像素宽高定的
                }
            }),
            new vjmap3d.render2d.Text({
                style: {
                    x: 40,
                    y: 20,
                    text: '唯杰',
                    fill: "#0ff",
                    font: '30px Arial',
                    verticalAlign: "top",
                    align: "center"
                }
            })
        ]
    }
});
spriteMarker.addTo(app)
</pre>
</mapFrame>




### 给渲染材质中的元素增加事件效果

渲染材质中的每个元素都可以响应事件进行事件处理，渲染材质的事件由`SpriteMarkerEntity`派发事件。

可点击下面示例中的文字进行查看效果

<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, position: "leftBottom" } // 是否显示视角指示器
    }
})
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png");
// 创建一个文字按钮1
let button1 = new vjmap3d.render2d.Text({
    style: {
        x: 40,
        y: 12,
        text: '查看详情',
        fill: "#0ff",
        font: '14px Arial',
        verticalAlign: "top",
        align: "center"
    }
})
// 创建一个文字按钮2
let button2 = new vjmap3d.render2d.Text({
    style: {
        x: 40,
        y: 38,
        text: '点击跳转',
        fill: "#BDFFAB",
        font: '14px Arial',
        verticalAlign: "top",
        align: "center"
    }
})
let spriteMarker = new vjmap3d.SpriteMarkerEntity(
{
    position: [-2, 0, 1], // 位置 
    sizeAttenuation: false, // 固定像素大小，不随相机变化
    anchorX: "center",  // x对齐方式
    anchorY: "bottom", // y对齐方式
    renderTexture: {
        autoCanvasSize: false,
        canvasWidth: 80, // 像素宽，这里是根据底图的像素宽高定的
        canvasHeight: 80, // 像素高，这里是根据底图的像素宽高定的
        sharedCanvas: false,
        elements: [
            new vjmap3d.render2d.Image({
                style: {
                    x: 0,
                    y: 0,
                    image: image,
                    width: 80, // 像素宽，这里是图片像素宽高定的
                    height: 80 // 像素高，这里是图片像素宽高定的
                }
            }),
            button1,
            button2
        ]
    }
});
// 响应文字按钮1的相关事件
button1.on("mouseover", function() {
    button1.attr("style", {
        fill: "#ff0"
    });
    spriteMarker.refreshRenderTexture();
    app.setCursor("pointer")
});
button1.on("mouseout", function() {
    button1.attr("style", {
        fill: "#0ff"
    });
    spriteMarker.refreshRenderTexture();
    app.setCursor("")
});
button1.on("mouseup", function() {
    app.logInfo("您点击了 查看详情 ", 2000)
});
//
// 响应文字按钮2的相关事件
button2.on("mouseover", function() {
    button2.attr("style", {
        fill: "#ff0"
    });
    spriteMarker.refreshRenderTexture();
    app.setCursor("pointer")
});
button2.on("mouseout", function() {
    button2.attr("style", {
        fill: "#BDFFAB"
    });
    spriteMarker.refreshRenderTexture();
    app.setCursor("")
});
button2.on("mouseup", function() {
    app.logInfo("您点击了 点击跳转 ", 2000)
});
//
// 增加spriteMarker至应用
spriteMarker.addTo(app);
spriteMarker.pointerEvents = true; // 允许事件交互
// 响应spriteMarker的相关事件，并且向RenderTexture的元素进行派发事件
spriteMarker.on("mousemove", e => {
    spriteMarker.dispatchRenderTextureEvent("mousemove", e?.intersection?.uv);
});
spriteMarker.on("mouseup", e => {
    spriteMarker.dispatchRenderTextureEvent("mouseup", e?.intersection?.uv);
});
spriteMarker.on("mouseout", e => {
    spriteMarker.dispatchRenderTextureEvent("mouseout");
});
</pre>
</mapFrame>



### 批量绘制
批量绘制时，需要注意的有：
- 材质或相关资源尽量共享
- 使用`allowOverlap`进行碰撞检测

```js
 let spriteMarker = new vjmap3d.SpriteMarkerEntity({
    allowOverlap: false,
    sizeAttenuation: false, // 必须显式写：未传时按 true 处理，又没给 width/height 时 addTo 会抛 "width or height is null"
    renderTexture: {
        sharedCanvas: true, // 提高效率共享一个canvas（缺省即共享；仅在 autoCanvasSize:true 且未设 canvasWidth 时生效）
        autoCanvasSize: true,
        elements: [ /* render2d 元素 */ ]
    }
})
```

> - `sizeAttenuation` 未传时按 `true`（世界坐标大小）处理，此时**必须给 `width` 或 `height`**（世界单位），否则 `addTo` 时抛 `width or height is null`；要固定像素大小就显式写 `sizeAttenuation: false`（宽高缺省取纹理像素尺寸）。
> - `sharedCanvas` 只在 `autoCanvasSize: true` **且没有设置 `canvasWidth`**（也没有 `render2dObject`/`pointerEvents`）时才生效；一旦写了 `canvasWidth/canvasHeight`，每个标记都会新建自己的 canvas，`sharedCanvas: true` 被忽略。


### 3D示例

<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, position: "leftBottom" } // 是否显示视角指示器
    }
})
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let imgEle = new vjmap3d.render2d.Image({
    style: {
        x: 0,
        y: 0,
        image: image,
        width: 80, // 像素宽，这里是图片像素宽高定的
        height: 80 // 像素高，这里是图片像素宽高定的
    }
})
for(let i = 0; i < 20; i++) {
    for(let j = 0; j < 20; j++) {
        let positon = [i * 5 - 50, 0, j * 5 - 50];
        let idx = `${j * 20 + i}`
        let spriteMarker = new vjmap3d.SpriteMarkerEntity(
        {
            position: positon, // 位置 
            sizeAttenuation: false, // 固定像素大小，不随相机变化
            allowOverlap: false,
            anchorX: "center",  // x对齐方式
            anchorY: "bottom", // y对齐方式
            renderTexture: {
                autoCanvasSize: true,
                sharedCanvas: true, // ⚠️ 下面设了 canvasWidth，共享 canvas 实际不生效（每个标记各建一个 canvas）
                canvasWidth: 80, // 像素宽，这里是根据底图的像素宽高定的
                canvasHeight: 80, // 像素高，这里是根据底图的像素宽高定的
                elements: [
                    imgEle,
                    new vjmap3d.render2d.Text({
                        style: {
                            x: 40,
                            y: 20,
                            text: idx,
                            fill: vjmap3d.render2d.color.random(),
                            font: '30px Arial',
                            verticalAlign: "top",
                            align: "center"
                        }
                    })
                ]
            }
        });
        // 增加spriteMarker至应用
        spriteMarker.addTo(app);
        spriteMarker.add(vjmap3d.EventModule, {
            clickHighlight: true,
            highlightOpacity: 0.2,
            hoverSelect: true,
            hoverHtmlText: `ID: ${idx}`,
            popupOptions: {
                anchor: "bottom",
                offset: [0, -80]
            },
            clickCallback: (ent, isClick) => {
                if (isClick) app.logInfo("您点击的ID是: " + idx, 2000)
            }
        });
        
    }
}
</pre>
</mapFrame>


### 2D地图示例
<mapFrame>
<pre>
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    // @ts-ignore
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapLightStyle() // div为深色背景颜色时，这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    showError(res.error)
    return;
}
// 获取地图范围
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapBounds);
//
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式，这里是栅格样式
    center: prj.toLngLat(mapBounds.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    pitch: 45,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
//
// 关联服务对象和投影对象
map.attach(svc, prj);
await map.onLoad();
//
// 创建3d图层
let mapLayer = new vjmap3d.MapThreeLayer(map, {
    stat: {
        show: true,
        left: "0"
    }
});
map.addLayer(new vjmap.ThreeLayer({ context: mapLayer }))
let app = mapLayer.app;
let bounds = map.getGeoBounds(0.4);
let image = await vjmap3d.loadImage(env.assetsPath + "image/imgbg.png")
let imgEle = new vjmap3d.render2d.Image({
    style: {
        x: 0,
        y: 0,
        image: image,
        width: 80, // 像素宽，这里是图片像素宽高定的
        height: 80 // 像素高，这里是图片像素宽高定的
    }
})
for(let i = 0; i < 20; i++) {
    for(let j = 0; j < 20; j++) {
        let positon = vjmap3d.toWorld([bounds.min.x + i * bounds.width() / 20, bounds.min.y + j * bounds.height() / 20]);
        let idx = `${j * 20 + i}`
        let spriteMarker = new vjmap3d.SpriteMarkerEntity(
        {
            position: positon, // 位置 
            sizeAttenuation: false, // 固定像素大小，不随相机变化
            allowOverlap: false,
            anchorX: "center",  // x对齐方式
            anchorY: "bottom", // y对齐方式
            renderTexture: {
                autoCanvasSize: true,
                sharedCanvas: true, // ⚠️ 下面设了 canvasWidth，共享 canvas 实际不生效（每个标记各建一个 canvas）
                canvasWidth: 80, // 像素宽，这里是根据底图的像素宽高定的
                canvasHeight: 80, // 像素高，这里是根据底图的像素宽高定的
                elements: [
                    imgEle,
                    new vjmap3d.render2d.Text({
                        style: {
                            x: 40,
                            y: 20,
                            text: idx,
                            fill: vjmap3d.render2d.color.random(),
                            font: '30px Arial',
                            verticalAlign: "top",
                            align: "center"
                        }
                    })
                ]
            }
        });
        spriteMarker.idx = idx;
        // 增加spriteMarker至应用
        spriteMarker.addTo(app);
        spriteMarker.add(vjmap3d.EventModule, {
            hoverHighlight: false,
            hoverSelect: false,
            hoverHtmlText: `ID: ${idx}`,
            popupOptions: {
                anchor: "bottom",
                offset: [0, -80]
            },
            popupAsEntityChild: false, // popup如果直接加到实体上，因为实体group位置在地图中心点，所以不能把popup做为他的子实体加入。或者用下面的语句加入实体中的SpriteMarker中去
            //popupCallback: (entity, popup, isHover) => entity.node.children[0],
            hoverCallback: (ent, isHover) => {
                let opacity = isHover ? 0.6 : 1.0;
                if (spriteMarker.SpriteMarker.material.opacity != opacity) {
                    spriteMarker.SpriteMarker.material.opacity = opacity
                }
            },
            clickCallback: (ent, isClick) => {
                if (!isClick) return
                app.logInfo("您点击的ID是: " + idx, 2000)
            }
        });
        
    }
}
</pre>
</mapFrame>
