# Marker3D标记
[Marker3D](https://vjmap.com/map3d/api/classes/Marker3D.html)是基于threejs中 `CSS3DObject` 实现的div标记。

## 常用方法

|方法|描述|
|:---|:---|
|[isShow](https://vjmap.com/map3d/api/classes/Marker3D.html#isShow)|是否显示|
|[show](https://vjmap.com/map3d/api/classes/Marker3D.html#show)|显示|
|[hide](https://vjmap.com/map3d/api/classes/Marker3D.html#hide)|隐藏|
|[getElement](https://vjmap.com/map3d/api/classes/Marker3D.html#getElement)|获取元素对象|
|[updateSize](https://vjmap.com/map3d/api/classes/Marker3D.html#updateSize)|更新大小|
|[getPosition](https://vjmap.com/map3d/api/classes/Marker3D.html#getPosition)|获取位置|
|[setPosition](https://vjmap.com/map3d/api/classes/Marker3D.html#setPosition)|设置位置|
|[updatePopupPosition](https://vjmap.com/map3d/api/classes/Marker3D.html#updatePopupPosition)|更新弹出框信息窗口位置|
|[addTo](https://vjmap.com/map3d/api/classes/Marker3D.html#addTo)|增加至应用或实体中|
|[isCss3dSprite](https://vjmap.com/map3d/api/classes/Marker3D.html#isCss3dSprite)|是否为Css3dSprite|
|[togglePopup](https://vjmap.com/map3d/api/classes/Marker3D.html#togglePopup)|切换弹出信息框显示或隐藏|
|[setPopup](https://vjmap.com/map3d/api/classes/Marker3D.html#setPopup)|设置弹出信息框|
|[getOpacity](https://vjmap.com/map3d/api/classes/Marker3D.html#getOpacity)|获取透明度|
|[setOpacity](https://vjmap.com/map3d/api/classes/Marker3D.html#setOpacity)|设置透明度|
|[remove](https://vjmap.com/map3d/api/classes/Marker3D.html#remove)|移除|
|[setAnimation](https://vjmap.com/map3d/api/classes/Marker3D.html#setAnimation)|设置动画|
|[setCursor](https://vjmap.com/map3d/api/classes/Marker3D.html#setCursor)|设置光标|

## 信号名

|方法|描述|
|:---|:---|
|click|点击|
|occlusion|遮挡状态变化，回调参数 `{object, occlusion}`。⚠️ 只有构造选项 `occlusionOpacity < 1`（默认 1）时才做遮挡检测，否则永不触发；被遮挡时只把透明度设为 `occlusionOpacity`，元素不隐藏|
|elementSizeReady|元素大小已就绪。⚠️ 同时给了 `pixelWidth` 和 `pixelHeight` 时在**构造函数内同步**派发，`new` 之后再 `.add` 的监听收不到；否则在挂载后由 ResizeObserver 量到尺寸时异步派发|
|onAdd|增加|
|onRemove|移除|

对于div的事件，可以通过[getElement](https://vjmap.com/map3d/api/classes/Marker3D.html#getElement)获取div元素，对此div增加事件。如
```js
marker3d.getElement().addEventListener("dblclick", event => {
    console.log("双击")
});
```

> ⚠️ **`isHide: true` 不是 addTo 同步生效**：`isHide` 在内部 `ResizeObserver` 回调的 `setTimeout(...,10ms)` 里才被应用——`addTo(app)` 后立即 `isShow()` 仍返回 `true`，要等元素完成一次尺寸测量（约 10ms+ 之后）才会变 `false`。不要在 addTo 后立刻根据 `isShow()` 判断初始隐藏是否生效。
>
> ⚠️ 另外（headless/无布局环境）：若 `ResizeObserver` 回调始终不触发（如 headless Chrome 无布局刷新），`isShow()` **可恒为 true**——等待翻 `false` 的轮询必须有超时上限，不能假定它一定会翻。

## 用法示例

### 创建一个marker3D
```js
let marker3d = new vjmap3d.Marker3D({
    color: vjmap3d.randHtmlColor(),
    width: 1
});
marker3d.setPosition([2, 0, 1]);
marker3d.addTo(app);
```

### 事件
```js
marker3d.signal.click.add(() => {
    app.logInfo("click");
});
marker3d.signal.occlusion.add(() => { // 需构造时设 occlusionOpacity < 1 才会触发
    app.logInfo("occlusion");
});
marker3d.signal.elementSizeReady.add(() => {
    app.logInfo("elementSizeReady");
});
marker3d.signal.onAdd.add(() => {
    app.logInfo("onAdd");
});
marker3d.signal.onRemove.add(() => {
    app.logInfo("onRemove");
});
marker3d.getElement().addEventListener("contextmenu", event => {
    // @ts-ignore
    if (event.button != 2) return;//不是右键
    // 阻止地图默认右键菜单触发
    event.preventDefault();
    event.stopPropagation();
    new vjmap3d.ContextMenu({
        event: event,
        theme: "dark", //light
        width: "250px",
        items: [
            {type: 'custom', markup: `&lt;span style="color: #ffff00; padding-left: 30px">我是标注Marker的右键菜单&lt;/span>`},
            {
                label: '删除Marker',
                onClick: () => {
                    marker3d.remove()
                }
            }
        ]
    });
})
```

<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 marker3d = new vjmap3d.Marker3D({
    color: vjmap3d.randHtmlColor(),
    width: 1
});
marker3d.setPosition([2, 0, 1]);
marker3d.addTo(app);
marker3d.signal.click.add(() => {
    app.logInfo("click");
});
marker3d.signal.occlusion.add(() => { // 需构造时设 occlusionOpacity < 1 才会触发
    app.logInfo("occlusion");
});
marker3d.signal.elementSizeReady.add(() => {
    app.logInfo("elementSizeReady");
});
marker3d.signal.onAdd.add(() => {
    app.logInfo("onAdd");
});
marker3d.signal.onRemove.add(() => {
    app.logInfo("onRemove");
});
marker3d.getElement().addEventListener("contextmenu", event => {
    // @ts-ignore
    if (event.button != 2) return;//不是右键
    // 阻止地图默认右键菜单触发
    event.preventDefault();
    event.stopPropagation();
    new vjmap3d.ContextMenu({
        event: event,
        theme: "dark", //light
        width: "250px",
        items: [
            {type: 'custom', markup: `&lt;span style="color: #ffff00; padding-left: 30px">我是标注Marker的右键菜单&lt;/span>`},
            {
                label: '删除Marker',
                onClick: () => {
                    marker3d.remove()
                }
            }
        ]
    });
})
</pre>
</mapFrame>

### 关联信息提示框
```js
for (let i = 0; i < 100; i++) {
    let marker3d = new vjmap3d.Marker3D({
        color: vjmap3d.randHtmlColor(),
        asSprite: true,
        width: 1
    });
    marker3d.setPosition(vjmap3d.toVector3(vjmap3d.randPoint3D()));
    marker3d.addTo(app);
    marker3d.setPopup(
        new vjmap3d.Popup2D({
            backgroundColor: "#A0FFA0",
            buttonColor: "#f00",
            closeOnClick: true,
            closeButton: true
        }).setHTML(`index${i + 1}`)
    );
    // marker3d.togglePopup();
}
```

<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" } // 是否显示视角指示器
    }
})
for (let i = 0; i < 100; i++) {
    let marker3d = new vjmap3d.Marker3D({
        color: vjmap3d.randHtmlColor(),
        asSprite: true,
        width: 1
    });
    marker3d.setPosition(vjmap3d.toVector3(vjmap3d.randPoint3D()));
    marker3d.addTo(app);
    marker3d.setPopup(
        new vjmap3d.Popup2D({
            backgroundColor: "#A0FFA0",
            buttonColor: "#f00",
            closeOnClick: true,
            closeButton: true
        }).setHTML(`index${i + 1}`)
    );
    // marker3d.togglePopup();
}
</pre>
</mapFrame>

### 遮挡时设置透明度
```js
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
    color: '#0ff'
});
let mesh = new THREE.Mesh(box, material);
mesh.position.set(0, 0, 0)
app.scene.add(mesh);
//
let marker3d = new vjmap3d.Marker3D({
    color: "#f00",
    width: 1,
    occlusionOpacity: 0.2 // 遮挡时透明度
});
marker3d.setPosition([0, -1, -1.5]);
marker3d.addTo(app);  
marker3d.signal.occlusion.add(e => {
    if (e.occlusion) {
        app.logInfo("被遮挡", "warn");
    } else {
        app.logInfo("没被遮挡", "success");
    }
});
app.signal.onAppAfterRender.add(t => {
    app.cameraControl.azimuthAngle += 20 * t.deltaTime * THREE.MathUtils.DEG2RAD;
})
```
<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 box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
    color: '#0ff'
});
let mesh = new THREE.Mesh(box, material);
mesh.position.set(0, 0, 0)
app.scene.add(mesh);
//
let marker3d = new vjmap3d.Marker3D({
    color: "#f00",
    width: 1,
    occlusionOpacity:0.2 // 遮挡时透明度
});
marker3d.setPosition([0, -1, -1.5]);
marker3d.addTo(app);  
marker3d.signal.occlusion.add(e => {
    if (e.occlusion) {
        app.logInfo("被遮挡", "warn");
    } else {
        app.logInfo("没被遮挡", "success");
    }
});
app.signal.onAppAfterRender.add(t => {
    app.cameraControl.azimuthAngle += 20 * t.deltaTime * THREE.MathUtils.DEG2RAD;
})
</pre>
</mapFrame>

### 动画Marker

```js
let marker3d = new vjmap3d.Marker3D({
    color: vjmap3d.randHtmlColor(),
    width: 1,
});
marker3d.setPosition(vjmap3d.toVector3(vjmap3d.toVector3(vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}))))
marker3d.addTo(app);
marker3d.setAnimation("MAP_ANIMATION_BOUNCE");
```

### RotatingTextBorderMarker3d
```js
// 创建一个缺省的
let markerEle = new vjmap3d.RotatingTextBorderMarker(
    {
        position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
        text: "3D地图"
    },
    {}
);
let marker3d = markerEle.createMarker3d({
    pixelWidth: 120,
    pixelHeight: 50,
    width: 5,
    asSprite: true
});
marker3d.addTo(app);
```

### BreathingApertureMarker3D
```js
// 创建一个缺省的
let markerEle = new vjmap3d.BreathingApertureMarker(
    {
        position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
        text: "3D地图"
    },
    {
        textColor: vjmap3d.randHtmlColor()
    }
);
let marker3d = markerEle.createMarker3d({
    pixelWidth: 120,
    pixelHeight: 50,
    width: 5,
    asSprite: true
});
marker3d.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" } // 是否显示视角指示器
    }
})
const ui = await app.getConfigPane({ title: "Marker", style: { width: "250px"}});
let gui = new vjmap3d.UiPanelJsonConfig();
gui.addButton("动画Marker", async () => {
    let marker3d = new vjmap3d.Marker3D({
        color: vjmap3d.randHtmlColor(),
        width: 1,
    });
    marker3d.setPosition(vjmap3d.toVector3(vjmap3d.toVector3(vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}))))
    marker3d.addTo(app);
    marker3d.setAnimation("MAP_ANIMATION_BOUNCE");
});
gui.addButton("RotatingTextBorderMarker3d", async () => {
    // 创建一个缺省的
    let markerEle = new vjmap3d.RotatingTextBorderMarker(
        {
            position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
            text: "3D地图"
        },
        {}
    );
    let marker3d = markerEle.createMarker3d({
        pixelWidth: 120,
        pixelHeight: 50,
        width: 5,
        asSprite: true
    });
    marker3d.addTo(app);
});
gui.addButton("BreathingApertureMarker", async () => {
    // 创建一个缺省的
    let markerEle = new vjmap3d.BreathingApertureMarker(
        {
            position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
            text: "3D地图"
        },
        {
            textColor: vjmap3d.randHtmlColor()
        }
    );
    let marker3d = markerEle.createMarker3d({
        pixelWidth: 120,
        pixelHeight: 50,
        width: 5,
        asSprite: true
    });
    marker3d.addTo(app);
});
gui.toJson().forEach(c => ui.appendChild(c));
</pre>
</mapFrame>
