唯杰地图 logo唯杰地图
VJMAP
EN

内置对象

vjmap3d/references/builtin/objects.md 30.6 KB1201 行
原文

WebGPU 引擎

本页内置对象(Label / MeshLine / ScreenQuad / Heatmap / TextMesh / TrailLine 等)的内部材质在 render: { engine: "webgpu" } 时会自动切换为 TSL 实现,用法不变。例外:ScreenQuad 的自定义 GLSL fragmentShader/vertexShader/uniforms 选项在 webgpu 下被丢弃(只告警,仅显示 map 纹理),需要自定义着色请改传 material(如 ShadertoyMaterial)。webgpu 引擎下请在 await app.whenReady() 之后再创建,详见 WebGPU 渲染引擎。

Label 文本

通过 createLabel 创建文本。

用法

let label = vjmap3d.createLabel({
    lineHeight: 1,
    color: "#0ff",
    text: "Hello,VJMAP3D!",
    anchorPoint: [0.5, 0],
    transparent: true,
    position: [0, 0, 0]
});
//label.setText("Hello");
//label.position.set(0, 0, 0);

示例

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 label = vjmap3d.createLabel({
    lineHeight: 1,
    color: "#0ff",
    text: "Hello,VJMAP3D!",
    anchorPoint: [0.5, 0],
    transparent: true,
    position: [0, 0, 0]
});
//label.setText("Hello");
//label.position.set(0, 0, 0);
app.scene.add(label);

MeshLine Mesh线

通过 MeshLine 创建MeshLine。

用法

let mesh_line = new vjmap3d.MeshLineGeometry();
mesh_line.setPoints(positions[n], null, false);

let texture = vjmap3d.ResManager.loadTexture(env.assetsPath + "textures/roadline.png");
texture.wrapS = THREE.RepeatWrapping;
let meshline_material = new vjmap3d.MeshLineMaterial({
    map: texture,
    useMap: true,
    color: new THREE.Color(colors[n]),
    transparent: true,
    resolution: app.commonUniforms.resolution,
    time: app.commonUniforms.time,
    sizeAttenuation: false,
    lineWidth: 20,
    depthTest: true,
    side: THREE.DoubleSide,
    repeat: new THREE.Vector2(1, 1),
    speed: new THREE.Vector2(1, 0),
    fog: false
});
let trail_mesh = new vjmap3d.MeshLine(mesh_line, meshline_material); 
app.scene.add(trail_mesh);

示例

let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    stat: { show: true, left: "0" },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    },
    control: { leftButtonPan: true } // 设置为左键用于平移 (同时右键将用于旋转) 和地图2d使用习惯一样
})
const tdtVecUrl =
    "https://t3.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=YOUR_TK";
const tdtAnnoUrl = `https://t3.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=YOUR_TK`;
let provider = new vjmap3d.MapProvider(
    [
        {
            url: tdtVecUrl
        },
        {
            url: tdtAnnoUrl
        }
    ],
    {
        rootTile: [10, 836, 446],
        colorFilter: {
            inVerseColor: true,
            bright: 1.2,
            contrast: 1,
            saturation: 1,
            monochromeColor: "#4586b6"
        }
    }
);
let mapviewEnt = new vjmap3d.MapViewEntity({
    provider,
    baseScale: 100,
});
mapviewEnt.addTo(app);
let res = await vjmap3d.httpHelper.get(env.assetsPath + "json/szmapdata.json");
let linePrimary = res.data.features;
let positions = [];
let colors = [];
for (var i = 0; i < linePrimary.length; i++) {
    const item = linePrimary[i];
    const points = [];
    item.geometry.coordinates.forEach(ln => {
        points.push(mapviewEnt.lngLatToWorld(ln));
    });
    let lineColor = "#ffff00";
    if (item.properties.highway === "primary") {
        lineColor = "#7eff10";
    } else if (item.properties.highway === "tertiary") {
        lineColor = "#0eeeee";
    } else if (item.properties.highway === "secondary") {
        lineColor = "#ffffff";
    } else {
        lineColor = "#ff0ffe";
    }
    positions.push(points);
    colors.push(lineColor);
}
for (let n = 0; n < positions.length; n++) {
    let mesh_line = new vjmap3d.MeshLineGeometry();
    mesh_line.setPoints(positions[n], null, false);
    //
    let texture = vjmap3d.ResManager.loadTexture(env.assetsPath + "textures/roadline.png");
    texture.wrapS = THREE.RepeatWrapping;
    let meshline_material = new vjmap3d.MeshLineMaterial({
        map: texture,
        useMap: true,
        color: new THREE.Color(colors[n]),
        transparent: true,
        resolution: app.commonUniforms.resolution,
        time: app.commonUniforms.time,
        sizeAttenuation: false,
        lineWidth: 20,
        depthTest: true,
        side: THREE.DoubleSide,
        repeat: new THREE.Vector2(1, 1),
        speed: new THREE.Vector2(1, 0),
        fog: false
    });
    let trail_mesh = new vjmap3d.MeshLine(mesh_line, meshline_material); 
    app.scene.add(trail_mesh);
}
app.cameraControl.loadState({
    cameraTarget: new THREE.Vector3(-40, 0, -30),
    cameraPosition: new THREE.Vector3(-40, 8, -30)
})

PathTube 管道

通过 PathTubeEntities 创建管道实体。

用法

let texture = new THREE.TextureLoader().load(env.assetsPath + "textures/diffuse.jpg", () => {
    texture.wrapS = THREE.RepeatWrapping;
});
let outMaterial = new THREE.MeshPhongMaterial({
    color: 0x7dffeb,
    map: texture,
    side: THREE.FrontSide
});
let texture2 = new THREE.TextureLoader().load(env.assetsPath + "textures/uv_grid_opengl.jpg", () => {
    texture2.wrapS = THREE.RepeatWrapping;
});
let innerMaterial = new THREE.MeshPhongMaterial({
    map: texture2,
    side: THREE.FrontSide
});
let opts = {
    data: [
        {
            paths: [
                [3, 0, 2],
                [-10, 0.5, -5],
                [-12, 0, 3],
                [-5, 2, 4]
            ],
            name: "path1",
        },
        {
            paths: [
                [3, 0, 2],
                [12, 0, -3],
                [15, 3, 4]
            ],
            name: "path2",
        },
        {
            paths: [
                [2, 1, 10],
                [-1, 0, 11],
                [3, 0, 2]
            ],
            name: "path3",
        }
    ],
    nodeRadius: 0.5,
    innerSideMaterial: innerMaterial,
    outerMaterial: outMaterial,
    sect: {
        shape: "arch"
    }
}
let pathTube = new vjmap3d.PathTubeEntities(opts);
pathTube.addTo(app);

示例

let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    stat: { show: true, left: "0" },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let texture = new THREE.TextureLoader().load(env.assetsPath + "textures/diffuse.jpg", () => {
    texture.wrapS = THREE.RepeatWrapping;
});
let outMaterial = new THREE.MeshPhongMaterial({
    color: 0x7dffeb,
    map: texture,
    side: THREE.FrontSide
});
let texture2 = new THREE.TextureLoader().load(env.assetsPath + "textures/uv_grid_opengl.jpg", () => {
    texture2.wrapS = THREE.RepeatWrapping;
});
let innerMaterial = new THREE.MeshPhongMaterial({
    map: texture2,
    side: THREE.FrontSide
});
let opts = {
    data: [
        {
            paths: [
                [3, 0, 2],
                [-10, 0.5, -5],
                [-12, 0, 3],
                [-5, 2, 4]
            ],
            name: "path1",
        },
        {
            paths: [
                [3, 0, 2],
                [12, 0, -3],
                [15, 3, 4]
            ],
            name: "path2",
        },
        {
            paths: [
                [2, 1, 10],
                [-1, 0, 11],
                [3, 0, 2]
            ],
            name: "path3",
        }
    ],
    nodeRadius: 0.5,
    innerSideMaterial: innerMaterial,
    outerMaterial: outMaterial,
    sect: {
        shape: "arch"
    }
}
let pathTube = new vjmap3d.PathTubeEntities(opts);
pathTube.addTo(app);
// 
for(let n = 0; n < pathTube.entity.childrens.length; n++) {
    let ent = pathTube.entity.childrens[n];
    ent.add(vjmap3d.EventModule, {
        clickHighlight: true,
        highlightOpacity: 0.2,
        hoverSelect: true,
        // @ts-ignore
        hoverHtmlText: ent.node.isNode ? 
       `节点:关联${ent.node.userData.dataIndexs} 条路径`:
        opts.data[ent.node.userData.dataIndex].name,
        popupOptions: {
            anchor: "bottom"
        }
    });
}

ExtrudeMesh 挤出模型

通过 ExtrudeMesh 创建挤出模型。

用法

let texture = vjmap3d.ResManager.loadTexture(env.assetsPath + "textures/uv_grid_opengl.jpg");
texture.wrapS = THREE.RepeatWrapping;
const material = new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,
    map: texture,
    transparent: true,
    opacity: 1.0
});
// 挤出路径的点位置    转弯半径   转弯分段数     当前点的缩放            取个名字
let paths = [
    {
        vector3: [-3, 0, 0],
        radius: 0.5,
        segments: 2,
        scale: { x: 1, y: 1 }
    },
    {
        vector3: [3, 0, 0],
        radius: 0.5,
        segments: 2,
        scale: { x: 1, y: 1 }
    },{
        vector3: [0, 0, 5],
        radius: 0.5,
        segments: 2,
        scale: { x: 1, y: 1 }
    }
];
const extrudeMesh = new vjmap3d.ExtrudeMesh(
    {
        paths: paths, // 自动生成路径的
        shapePath:[[-0.5, 0], [0.5, 0], [0.25, 0.5], [-0.25, 0.5], [-0.5, 0]],// 被拉伸成模型的平面路径
        closed: false, // 是否闭合路径
    },
    material
);
app.scene.add(extrudeMesh);

示例

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 texture = vjmap3d.ResManager.loadTexture(env.assetsPath + "textures/uv_grid_opengl.jpg");
texture.wrapS = THREE.RepeatWrapping;
const material = new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,
    map: texture,
    transparent: true,
    opacity: 1.0
});
// 挤出路径的点位置    转弯半径   转弯分段数     当前点的缩放            取个名字
let paths = [
    {
        vector3: [-3, 0, 0],
        radius: 0.5,
        segments: 2,
        scale: { x: 1, y: 1 }
    },
    {
        vector3: [3, 0, 0],
        radius: 0.5,
        segments: 2,
        scale: { x: 1, y: 1 }
    },{
        vector3: [0, 0, 5],
        radius: 0.5,
        segments: 2,
        scale: { x: 1, y: 1 }
    }
];
const extrudeMesh = new vjmap3d.ExtrudeMesh(
    {
        paths: paths, // 自动生成路径的
        shapePath:[[-0.5, 0], [0.5, 0], [0.25, 0.5], [-0.25, 0.5], [-0.5, 0]],// 被拉伸成模型的平面路径
        closed: false, // 是否闭合路径
    },
    material
);
app.scene.add(extrudeMesh);
// 创建一个平面的道路
let texture2 = vjmap3d.ResManager.loadTexture(env.assetsPath + "textures/road1.png");
texture2.wrapS = THREE.RepeatWrapping;
const material2 = new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,
    map: texture2,
    transparent: true,
    opacity: 1.0
});
let paths2 = [
    {
        vector3: [-10, 0, 0],
        radius: 0.5,
        segments: 6,
        scale: { x: 1, y: 1 }
    },
    {
        vector3: [-5, 0, 0],
        radius: 0.5,
        segments: 6,
        scale: { x: 1, y: 1 }
    },{
        vector3: [-8, 0, 5],
        radius: 0.5,
        segments: 6,
        scale: { x: 1, y: 1 }
    }
];
const extrudeMesh2 = new vjmap3d.ExtrudeMesh(
    {
        paths: paths2, // 自动生成路径的
        shapePath:[[-0.5, 0], [0.5, 0]],// 被拉伸成模型的平面路径
        closed: false, // 是否闭合路径
    },
    material2
);
app.scene.add(extrudeMesh2);

ScreenQuad 铺满屏幕的四边形

通过 ScreenQuad 创建。

用法

let shader = await new THREE.FileLoader().loadAsync(env.assetsPath + "shader/fire.frag")
let mat = new vjmap3d.ShadertoyMaterial(app, {
    shader: shader,
    brightOpacity: 1,
    mouseInputPos: [400, 200],
    transparent: true,
    side: THREE.DoubleSide
});
let screenQuad = new vjmap3d.ScreenQuad(app, {
    material: mat
});
app.scene.add(screenQuad.mesh);

示例

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 shader = await new THREE.FileLoader().loadAsync(env.assetsPath + "shader/fire.frag")
let mat = new vjmap3d.ShadertoyMaterial(app, {
    shader: shader,
    brightOpacity: 1,
    mouseInputPos: [400, 200],
    transparent: true,
    side: THREE.DoubleSide
});
let screenQuad = new vjmap3d.ScreenQuad(app, {
    material: mat
});
app.scene.add(screenQuad.mesh);

Heatmap 热力图

通过 Heatmap 创建热力图。

用法

let data = [];
for(let k = 0; k < 100; k++) {
    data.push({
        point: vjmap3d.randPoint2D({minX: -10, maxX: 10, minY: -10, maxY: 10}),
        value: Math.random() * 50
    })
}
//
let heatmap = new vjmap3d.Heatmap({
    mode3D: false, //  3d模式
    heightRatio: 1.0,
    bounds: [[-10, -10], [10, 10]],
    data: data,
    min: 0,
    max: 50,
    // 可以传一个多边形进行裁剪
    clipPolygon: [
        [-10,0],
        [0, -10],
        [10, 0],
        [0, 10]
    ]
})
let mesh = await heatmap.create();
let heatmapEnt = new vjmap3d.MeshEntity(mesh);
heatmapEnt.addTo(app);

示例

二维热力图

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 data = [];
for(let k = 0; k < 100; k++) {
    data.push({
        point: vjmap3d.randPoint2D({minX: -10, maxX: 10, minY: -10, maxY: 10}),
        value: Math.random() * 50
    })
}
//
let heatmap = new vjmap3d.Heatmap({
    mode3D: false, //  3d模式
    heightRatio: 1.0,
    bounds: [[-10, -10], [10, 10]],
    data: data,
    min: 0,
    max: 50,
    // 可以传一个多边形进行裁剪
    clipPolygon: [
        [-10,0],
        [0, -10],
        [10, 0],
        [0, 10]
    ]
})
let mesh = await heatmap.create();
let heatmapEnt = new vjmap3d.MeshEntity(mesh);
heatmapEnt.addTo(app);
heatmapEnt.pointerEvents = true
//
const popup = new vjmap3d.Popup2D({
    backgroundColor: "#A0FFA0",
    buttonColor: "#f00",
    closeOnClick: false,
    closeButton: false,
    showArrow: true,
    isHide: true
}).setText("").addTo(app);
//
heatmapEnt.on("mousemove", e => {
    console.log(e)
    if (e?.intersection?.uv) {
        let value = heatmap.getValueByUV(e?.intersection?.uv.toArray());
        popup.setText(`值: ${value}`)
        if (value) {
            popup.position.copy(e?.intersection?.point)
            popup.show();
        } else {
            popup.hide();
        }
    }
})
heatmapEnt.on("mouseout", e => {
    popup.hide();
})
// 模拟值变化
setInterval(() => {
    let data = [];
    for(let k = 0; k < 100; k++) {
        data.push({
            point: vjmap3d.randPoint2D({minX: -10, maxX: 10, minY: -10, maxY: 10}),
            value: Math.random() * 50
        })
    }
    heatmap.setData({
        //  bounds: [[0, 0], [100, 100]],
        data,
        min: 0,
        max: 50
    })
}, 3000)

三维热力图

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 data = [];
for(let k = 0; k < 100; k++) {
    data.push({
        point: vjmap3d.randPoint2D({minX: -10, maxX: 10, minY: -10, maxY: 10}),
        value: Math.random() * 50
    })
}
//
let heatmap = new vjmap3d.Heatmap({
    mode3D: true, //  3d模式
    heightRatio: 1.0,
    bounds: [[-10, -10], [10, 10]],
    data: data,
    min: 0,
    max: 50
})
let mesh = await heatmap.create();
let heatmapEnt = new vjmap3d.MeshEntity(mesh);
heatmapEnt.addTo(app);
heatmapEnt.pointerEvents = true
//
const popup = new vjmap3d.Popup2D({
    backgroundColor: "#A0FFA0",
    buttonColor: "#f00",
    closeOnClick: false,
    closeButton: false,
    showArrow: true,
    isHide: true
}).setText("").addTo(app);
//
heatmapEnt.on("mousemove", e => {
    console.log(e)
    if (e?.intersection?.uv) {
        let value = heatmap.getValueByUV(e?.intersection?.uv.toArray());
        popup.setText(`值: ${value}`)
        if (value) {
            popup.position.copy(e?.intersection?.point)
            popup.show();
        } else {
            popup.hide();
        }
    }
})
heatmapEnt.on("mouseout", e => {
    popup.hide();
})
// 模拟值变化
setInterval(() => {
    let data = [];
    for(let k = 0; k < 100; k++) {
        data.push({
            point: vjmap3d.randPoint2D({minX: -10, maxX: 10, minY: -10, maxY: 10}),
            value: Math.random() * 50
        })
    }
    heatmap.setData({
        //  bounds: [[0, 0], [100, 100]],
        data,
        min: 0,
        max: 50
    })
}, 3000)

生成热力图数据分析材质

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" } // 是否显示视角指示器
    }
})
//
 const box = new THREE.Mesh(
    new THREE.BoxGeometry(3, 3, 3),
    new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
let meshEnt = new vjmap3d.MeshEntity(box);
meshEnt.addTo(app);
// 
const createHeatmap = async () => {
    // 获取实体的外包盒
    let box3d = meshEnt.getBoundingBox(false);
    let pointNum = 10; // 模拟十个点的业务数据
    let data = []
    let size = new THREE.Vector3();
    box3d.getSize(size)
    for(let k = 0 ; k < pointNum; k++) {
        data.push({
            x: box3d.min.x + Math.random() * size.x,
            y: box3d.min.y + Math.random() * size.y,
            z: box3d.min.z + Math.random() * size.z,
            value: Math.random() * 100
        })
    }
    // 设置热力图数据分析材质
    await meshEnt.setHeatmapDataMaterial({
        data,
    })
}
createHeatmap();
let control = new vjmap3d.ButtonGroupControl({
    buttons: [
        {
            id: "id1",
            html: "数据热力图",
            style: {
                width: "100px"
            },
            onclick: async () => {
                await createHeatmap();
            }
        },{
            id: "id2",
            html: "取消热力图",
            style: {
                width: "100px"
            },
            onclick: async () => {
                // 还原成原来的材质
                meshEnt.resetMaterial();
            }
        }
    ]
});
app.addControl(control, "top-right");

MeasureRuler 测量标尺

通过 creataMeasureRuler 创建测量标尺。

用法

{
    let lineStart = new THREE.Vector3(-3, 0, -6);
    let lineEnd = new THREE.Vector3(2, 0, 3);
    vjmap3d.creataMeasureRuler(app, {
        startPoint: lineStart,
        endPoint: lineEnd
    })
}
{
    let lineStart = new THREE.Vector3(5, 0, 5);
    let lineEnd = new THREE.Vector3(5, 5, 5);
    vjmap3d.creataMeasureRuler(app, {
        startPoint: lineStart,
        endPoint: lineEnd,
        planePoint: [4, 0, 5],
        showTextCb(ptStart, ptEnd, dist, fractionDigits) {
            return `${dist.toFixed(fractionDigits ?? 2)} 米`
        },
    })
}
{
    let lineStart = new THREE.Vector3(-17, 0, -6);
    let lineEnd = new THREE.Vector3(-3, 6, -19);
    let ent = vjmap3d.creataMeasureRuler(app, {
        startPoint: lineStart,
        endPoint: lineEnd,
        textAngleCb(ptStart, ptEnd, quat) {
            return [{
                axis: "x",
                angle: Math.PI
            }]
        }
    })
}
const ui = await app.getConfigPane({ title: "绘制", style: { width: "250px"}});
ui.appendChild({
    type: "button",
    label: '绘制标尺',
    value: async ()=>{
        let line = await app.actionDrawLineSting({
            pointMaxCount: 2
        });
        if (line.isCancel) return;
        let lineClr = vjmap3d.randColor();
        let arrowClr = vjmap3d.randColor();
        let txtClr = vjmap3d.randColor();
        let sideLineClr = vjmap3d.randColor();
        let ruler = vjmap3d.creataMeasureRuler(app, {
            startPoint: line.data.coordinates[0],
            endPoint: line.data.coordinates[1],
            rulerLineStyle: {
                color: lineClr,
            },
            arrowStyle: {
                color: arrowClr,
            },
            sideLineStyle: {
                color: sideLineClr
            },
            textStyle: {
                color: txtClr,
                billboard: false
            }
        })
    }
})

示例

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 lineStart = new THREE.Vector3(-3, 0, -6);
    let lineEnd = new THREE.Vector3(2, 0, 3);
    vjmap3d.creataMeasureRuler(app, {
        startPoint: lineStart,
        endPoint: lineEnd
    })
}
{
    let lineStart = new THREE.Vector3(5, 0, 5);
    let lineEnd = new THREE.Vector3(5, 5, 5);
    vjmap3d.creataMeasureRuler(app, {
        startPoint: lineStart,
        endPoint: lineEnd,
        planePoint: [4, 0, 5],
        showTextCb(ptStart, ptEnd, dist, fractionDigits) {
            return `${dist.toFixed(fractionDigits ?? 2)} 米`
        },
    })
}
{
    let lineStart = new THREE.Vector3(-17, 0, -6);
    let lineEnd = new THREE.Vector3(-3, 6, -19);
    let ent = vjmap3d.creataMeasureRuler(app, {
        startPoint: lineStart,
        endPoint: lineEnd,
        textAngleCb(ptStart, ptEnd, quat) {
            return [{
                axis: "x",
                angle: Math.PI
            }]
        }
    })
}
const ui = await app.getConfigPane({ title: "绘制", style: { width: "250px"}});
ui.appendChild({
    type: "button",
    label: '绘制标尺',
    value: async ()=>{
        let line = await app.actionDrawLineSting({
            pointMaxCount: 2
        });
        if (line.isCancel) return;
        let lineClr = vjmap3d.randColor();
        let arrowClr = vjmap3d.randColor();
        let txtClr = vjmap3d.randColor();
        let sideLineClr = vjmap3d.randColor();
        let ruler = vjmap3d.creataMeasureRuler(app, {
            startPoint: line.data.coordinates[0],
            endPoint: line.data.coordinates[1],
            rulerLineStyle: {
                color: lineClr,
            },
            arrowStyle: {
                color: arrowClr,
            },
            sideLineStyle: {
                color: sideLineClr
            },
            textStyle: {
                color: txtClr,
                billboard: false
            }
        })
    }
})

TextMesh 文本Mesh

通过 createTextMesh 创建文本Mesh。

提示

因为创建文本mesh,需要通过await Plugins.loadPlugin("text3d")加载text3d来加载插件,所以createTextMesh是一个异步函数。

用法

const textParams = {
    color: '#00ffff',
    text: `春天是一年四季中最美丽的季节之一,也是最富有生机的季节。
    在这个季节里,大自然给我们带来了很多惊喜和愉悦。
    在这个季节里,我喜欢去公园里散步,欣赏那里的美景。
    The end 。`,
    fontSize: 2,
    maxWidth: 40,
    lineHeight: 1,
    font: env.assetsPath + 'fonts/gb2312.ttf',
    // onPreloadEnd 只有同时传了 font 和 characters(要预加载的字符)时才会被调用
    onPreloadEnd: () => {
      // console.log('loaded')
    },
}
let text = await vjmap3d.createTextMesh(textParams);
let mesh = text.mesh;
app.scene.add(mesh)

示例

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 textParams = {
    color: '#00ffff',
    text: `春天是一年四季中最美丽的季节之一,也是最富有生机的季节。
    在这个季节里,大自然给我们带来了很多惊喜和愉悦。
    在这个季节里,我喜欢去公园里散步,欣赏那里的美景。
    The end 。`,
    fontSize: 2,
    maxWidth: 40,
    lineHeight: 1,
    font: env.assetsPath + 'fonts/gb2312.ttf',
    // onPreloadEnd 只有同时传了 font 和 characters(要预加载的字符)时才会被调用
    onPreloadEnd: () => {
      // console.log('loaded')
    },
}
let text = await vjmap3d.createTextMesh(textParams);
let mesh = text.mesh;
app.scene.add(mesh)

TrailLine

通过 TrailLine 创建。

用法

let guideLine = new vjmap3d.TrailLine({
    positions:[[1,1,1],[-5,1,5],[-5,1,1]],        //以xz轴为平面
    width:10,                   //线宽
    placeLength:15,             //图标放置的长度
    placeSpace:10,              //图标放置间隔
    iconUrl: env.assetsPath + "textures/arrow.png",        //图标
    speed:2,        //速度
    smooth: true,
    isAnimate:true  //是否需要动画
})
guideLine.update(app)
app.scene.add(guideLine.trail)
app.signal.onAppUpdate.add(e => {
    guideLine.update(app)
})

示例

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 guideLine = new vjmap3d.TrailLine({
    positions:[[1,1,1],[-5,1,5],[-5,1,1]],        //以xz轴为平面
    width:10,                   //线宽
    placeLength:15,             //图标放置的长度
    placeSpace:10,              //图标放置间隔
    iconUrl: env.assetsPath + "textures/arrow.png",        //图标
    speed:2,        //速度
    smooth: true,
    isAnimate:true  //是否需要动画
})
guideLine.update(app)
app.scene.add(guideLine.trail)
app.signal.onAppUpdate.add(e => {
    guideLine.update(app)
})

Billboard 公告板

公告板是某一个网格它会通过旋转轴始终对着相机,这样就可以让它的正面始终是相机方向。通过 Billboard 创建公告板。对于实体,也可以通过增加公告板模块BillboardModule来设置。

用法

let torusBillboardEnt = new vjmap3d.MeshEntity(createTorusMesh())
torusBillboardEnt.position.set(1, 2, 0);
torusBillboardEnt.addTo(app);
let opts = {
    follow: true,
    lockX: false,
    lockZ: false,
    lockY: false,
    baseOnPreRotation: true,
}
torusBillboardEnt.add(vjmap3d.BillboardModule, opts);

示例

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 createTorusMesh = () => {
    const geometry = new THREE.TorusKnotGeometry(1, 0.35, 100, 32)
    const mat = new THREE.MeshStandardMaterial({
      roughness: 0,
      color: 0xffffff * Math.random(),
    })
    const torusMesh = new THREE.Mesh(geometry, mat)
    torusMesh.castShadow = true
    torusMesh.receiveShadow = true
    return torusMesh
}
// 正常的,用于对比
let torusNormalEnt = new vjmap3d.MeshEntity(createTorusMesh())
torusNormalEnt.position.set(0, 6, 0);
torusNormalEnt.addTo(app);
// 设置了公告板模式的
let torusBillboardEnt = new vjmap3d.MeshEntity(createTorusMesh())
torusBillboardEnt.position.set(1, 2, 0);
torusBillboardEnt.addTo(app);
let opts = {
    follow: true,
    lockX: false,
    lockZ: false,
    lockY: false,
    baseOnPreRotation: true,
}
torusBillboardEnt.add(vjmap3d.BillboardModule, opts);
let mod = torusBillboardEnt.getModule(vjmap3d.BillboardModule);
const ui = await app.getConfigPane()
let gui = new vjmap3d.UiPanelJsonConfig();
gui.add(opts, "follow").onChange(v => {
    mod.billboard.updateProps(opts)
})
gui.add(opts, "lockX").onChange(v => {
    mod.billboard.updateProps(opts)
})
gui.add(opts, "lockZ").onChange(v => {
    mod.billboard.updateProps(opts)
})
gui.add(opts, "lockY").onChange(v => {
    mod.billboard.updateProps(opts)
})
gui.add(opts, "baseOnPreRotation").onChange(v => {
    mod.billboard.updateProps(opts)
})
gui.toJson().forEach(c => ui.appendChild(c));