# 创建应用

## 创建3D应用

通过 `vjmap3d` 中的 [App](https://vjmap.com/map3d/api/classes/App.html) 来创建一个3d应用。`App`需要设置`container`与一个`div`容器相关联。

示例如下：

<mapFrame>
<pre>
// 创建一个服务类，与服务连接
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    // 渲染参数
    render: {
        alpha: true,
        antialias: true
    },
    // 是否显示性能统计面板
    stat: {
        show: true,
        left: "0"
    },
    // 场景设置
    scene: {
        // 是否显示坐标网格
        gridHelper: {
            visible: true
        }
    },
    // 相机设置
    camera: {
        // 是否显示视角指示器
        viewHelper: {
            enable: true
        }
    }
})
</pre>
</mapFrame>

::: tip 提示
免费试用版不提供私有化部署，直接连官网的服务进行试用，有水印。如果需要正式版本私有化部署，请联系客服。
:::

## 选择渲染引擎（WebGL / WebGPU）

`vjmap3d` v2 起支持 `WebGPU` 渲染引擎。创建应用时通过渲染参数 `render.engine` 选择：`"webgl"`（默认，`WebGLRenderer`）或 `"webgpu"`（`WebGPURenderer`）。浏览器不支持 WebGPU 时，three 的 `WebGPURenderer` 会自动降级为 WebGL2 后端（仍属 `webgpu` 引擎：`app.renderer.isWebGPURenderer === true`、仍走 TSL 材质，GLSL `ShaderMaterial` 依旧不可用）；只有 `three/webgpu`/`three/tsl` 动态加载失败（如 importmap 缺映射）或渲染器 `init()` 抛错时，才回退到 `webgl` 引擎（`WebGLRenderer`）。

::: warning 版本要求
`webgpu` 引擎与 `app.whenReady()` 均为 **v2.0.0 起**的 API——旧版 `vjmap3d@1.0.23`（webgl-only，peer `three@^0.165`）的 dist 中**没有**。1.x 用户请直接使用默认 `webgl` 引擎并跳过下例的 `await app.whenReady()`（webgl 渲染器同步就绪）。版本分界详见 [WebGPU 渲染引擎](../advance/webgpu.md)。
:::

```js
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map",
    render: {
        engine: "webgpu", // 渲染引擎: 'webgl'(默认) 或 'webgpu'
        // forceWebGL: true, // 可选: webgpu 引擎强制使用 WebGL2 后端(兼容不支持 WebGPU 的环境)
        antialias: true
    }
})
// webgpu 引擎的渲染器为异步初始化：访问 app.renderer 前先等待就绪
// （webgl 引擎下立即返回；v2.0.0 起提供，旧版 1.x 无此方法可省略）
await app.whenReady();
console.log(app.renderer.isWebGPURenderer
    ? (app.renderer.backend?.isWebGPUBackend ? "WebGPU" : "WebGL2 后端(WebGPURenderer)")
    : "WebGL(webgl 引擎)");
```

::: tip 提示
`webgpu` 引擎下，内置特效材质与后期处理（辉光/描边等）会自动切换为 TSL / WebGPU 实现，用法不变。详细说明（浏览器要求、回退规则、自定义材质注意事项）见 [WebGPU 渲染引擎](../advance/webgpu.md)。

注意：`vjmap3d` 做为唯杰地图的三维图层使用时（见下文），因需共享 2D 地图的 WebGL 上下文，不支持 `webgpu` 引擎。
:::

## 创建CAD为底图的3D应用

通过 `vjmap3d` 中的 [MapViewEntity](https://vjmap.com/map3d/api/classes/MapViewEntity.html) 来创建一个地图实体。

<mapFrame>
<pre>
// 创建一个服务类，与服务连接
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    camera: {  // 相机设置
        viewHelper: { enable: true } // 是否显示视角指示器
    }
})
let res = await app.svc.openMap({
        mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在，可上传新图形新建ID)
        mapopenway: vjmap3d.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
        style: vjmap3d.openMapDarkStyle()
});
if (res.error) {
    showError(res.error);
    return;
}
// 获取当前地图的可视范围
let clipPolygon = vjmap3d.GeoBounds.fromString(res.drawBounds).toPointArray();
let provider = new vjmap3d.MapProvider(
    [
        {
            url: app.svc.rasterTileUrl()
        }
    ],
    {
        transparent: true
    }
);
let mapBounds = vjmap3d.GeoBounds.fromString(res.bounds)
let scale = 100;
let mapviewEnt = new vjmap3d.MapViewEntity({
    provider,
    baseScale: scale,
    mapBounds: mapBounds.toArray(),
    clipPolygon //  如果透明的话，不能使用裁剪区载
});
mapviewEnt.addTo(app);
//
// 在CAD图中心上方创建一个Mesh
let cadLen = mapBounds.width() / 50; // 取cad图长度的1/50
// toDist 的 app 形参默认值是"当前全局 App"（new App 之后才有）——必须在 App 创建后调用，否则崩溃
let len = vjmap3d.toDist(cadLen); // cad长度转世界坐标长度
let box = new THREE.BoxGeometry(len, len, len)
const material = new THREE.MeshStandardMaterial({
    color: "#f00",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let cadPos = [mapBounds.center().x, mapBounds.center().y, cadLen]; // mesh在cad中坐标
//
let worldPos = vjmap3d.toWorld(cadPos);// cad坐标转世界坐标
mesh.position.copy(worldPos);
//app.scene.add(mesh) // 可以直接把 mesh 加进场景中不与实体相关联
// 创建一个实体与mesh相关联
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
// 增加一个事件模块，用来高亮提示
entity.add(vjmap3d.EventModule, {
    hoverSelect: true,
    hoverHtmlText: '我是用vjmap3d创建的Box',
    popupOptions: {
        anchor: "bottom"
    }
});
</pre>
</mapFrame>
   
## 做为唯杰地图的三维图层

 `vjmap3d`既可做为一个单独的3D引擎, 也可以基于[唯杰地图VJMAP](https://vjmap.com/doc/)做为一个`3D`图层，能够在保留二维图形信息的直观性同时，融入三维空间的深度感知和交互性，为用户带来更丰富、立体和沉浸式的视觉体验及数据分析能力。

 其步骤为：
 - 引入`vjmap`sdk,创建`vjmap.Map`地图对象，具体示例可查看 [https://vjmap.com/demo](https://vjmap.com/demo)
 - 创建 [MapThreeLayer](https://vjmap.com/map3d/api/classes/MapThreeLayer.html) 3d图层
 - 通过`map.addLayer`增加3d图层，通过3d图层获取[App](https://vjmap.com/map3d/api/classes/App.html)对象。

 示例如下:

<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;
//
// 在CAD图中心上方创建一个Mesh
let cadLen = mapBounds.width() / 50; // 取cad图长度的1/50
// toDist 的 app 形参默认值是"当前全局 App"（new App 之后才有）——必须在 App 创建后调用，否则崩溃
let len = vjmap3d.toDist(cadLen); // cad长度转世界坐标长度
let box = new THREE.BoxGeometry(len, len, len)
const material = new THREE.MeshStandardMaterial({
    color: "#f00",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let cadPos = [mapBounds.center().x, mapBounds.center().y, cadLen]; // mesh在cad中坐标
//
let worldPos = vjmap3d.toWorld(cadPos);// cad坐标转世界坐标
mesh.position.copy(worldPos);
//app.scene.add(mesh) // 可以直接把 mesh 加进场景中不与实体相关联
// 创建一个实体与mesh相关联
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
// 增加一个事件模块，用来高亮提示
entity.add(vjmap3d.EventModule, {
    hoverSelect: true,
    hoverHtmlText: '我是用vjmap3d创建的Box',
    popupOptions: {
        anchor: "bottom"
    }
});
</pre>
</mapFrame>
   
::: tip 总结
使用`vjmap3d`创建`3d应用` 和 `做为唯杰地图的三维图层` 使用，除了初始化代码不同外，业务代码基本上可复用，做到同一套代码用在不同的框架上。但要注意坐标轴差异：独立 `App` 为 Y 轴向上，三维图层模式（`app.isMapMode`）为 **Z 轴向上**（高度在 `.z`、地面为 XY 平面、旋转轴用 `(0,0,1)`），涉及高度/朝向的代码需按 `app.isMapMode` 分支，详见 [坐标转换](../graphics/coordTransform.md)。

需要说明的是：当`vjmap3d`做为唯杰地图的三维图层使用时，其相机是由`唯杰地图`控制的，`vjmap3d`的相机会和`唯杰地图`相机保持同步。因此在这种模式下，不能直接操作`vjmap3d`的相机。同时因为`唯杰地图`暂不支持后期处理，所以在这种模式下，`vjmap3d`的后期处理功能会自动禁用。
:::