# 常用函数

## webworker

在三维开发中，保持主线程尽可能空闲是很重要的，这样它就可以快速响应用户交互。UI线程应该仅用于UI工作。`WebWorkers`是一个web API，允许您在单独的线程中运行代码。为了与另一个线程通信，WebWorkers提供`postMessage` API。您可以使用myWorker.`postMessage（someObject）` 将`JavaScript`对象作为消息发送，从而触发worker内部的消息事件。`vjmap3d`通过对`webworker`的封装，使webworker调用像调用本地函数一样，使用更方便。

### 使用方法

方式一： 通过 `createWorker` 和 `workerWrap` 创建
- 1、通过 [createWorker](https://vjmap.com/map3d/api/functions/createWorker.html) 通过本地函数创建worker
- 2、通过 [workerWrap](https://vjmap.com/map3d/api/functions/workerWrap.html) 以本地函数进行包裹
- 3、调用（并且异步调用） 
- 4、终止 `terminate` woker

```js
function sum(num) {
    let result = 0
    for (let i = 0; i < num; i++) {
        result = result + i
    }
    return result;
}
let myWorker = vjmap3d.createWorker(sum);
const sumWorker = vjmap3d.workerWrap(myWorker);
let result1 = await sumWorker(100000);
console.log(result1); // 结果将输出 4999950000
let result2 = await sumWorker(5000000);
console.log(result2); // 结果将输出 12499997500000
myWorker.terminate();
```

方式二、通过 `createWorkerWrap` 将上面的的 `createWorker` 和 `workerWrap` 合成一步
- 1、通过 [createWorkerWrap](https://vjmap.com/map3d/api/functions/createWorkerWrap.html) 通过本地函数创建worker和本地函数进行包裹
- 2、调用（并且异步调用） 
- 3、终止 `terminate` woker

上面的代码用`createWorkerWrap`实现如下
```js
function sum(num) {
    let result = 0
    for (let i = 0; i < num; i++) {
        result = result + i
    }
    return result;
}
var {worker, wrap} = vjmap3d.createWorkerWrap(sum);
let result1 = await wrap(100000);
console.log(result1); // 结果将输出 4999950000
let result2 = await wrap(5000000);
console.log(result2); // 结果将输出 12499997500000
worker.terminate();
```

::: tip 
用`webworker`进行`wrap`的函数或类，不能依赖其他的函数或类，如用了外部依赖的函数或类，需要把依赖的函数或类做为上下文传入。
:::

> ⚠️ 上下文对象里的**函数值**会按 `function 名(` 的固定格式截取源码再拼进 worker，只有普通 `function xxx(){}` 声明能正确转换；**类、箭头函数、对象方法简写（`foo(){}`）、async 函数**会被截坏，worker 启动即 `SyntaxError`。这类依赖请直接传**源码字符串**（字符串值会原样拼入 worker），如 `{ helper: "const helper = (a) => a + 1;", Point: Point.toString() }`。

```js
class MyClass {
    constructor(init = 0) {
        console.log(init);
        console.log(randInt(100, 200))
        this._counter = init;
    }
    get counter() {
        return this._counter;
    }
    increment(delta = 1) {
        this._counter += delta;
    }
}
let myWorker = vjmap3d.createWorker(MyClass, {
    randInt: vjmap3d.randInt
});
let instance1, instance2;
async function showState() {
    console.log(`instance1.counter = ${await instance1.counter},instance2.counter = ${await instance2.counter}`);
}
const MyWorkerClass = vjmap3d.workerWrap(myWorker);
instance1 = await new MyWorkerClass();
instance2 = await new MyWorkerClass(42);
await showState();
console.time("increment");
for (let i = 0; i < 100000; i++) {
    await instance1.increment();
}
console.timeEnd("increment");
await showState();
myWorker.terminate();
// 输出结果
/**
0
199
42
177
instance1.counter = 0,instance2.counter = 42
increment: 7185.27685546875 ms
instance1.counter = 100000,instance2.counter = 42
 */
```


## 模型分解

对物体进行爆炸分解 [explodeGroup](https://vjmap.com/map3d/api/functions/explodeGroup.html)
```ts
 /** 对物体进行爆炸分解 */
function explodeGroup(group: Object3D) {
    /** 爆炸分解  */
    explode: (opts?: {
        /** 动画时长（毫秒），默认 3000 */
        time?: number;
        /** 爆炸分解分解距离，默认 3 */
        distance?: number;
        /** 允许旋转 */
        enableRotation?: boolean;
        /** 动画类型 */
        easing?: Easing;
    }) => Promise<any>;
    /** 还原 */
    reset: (opts?: {
        /** 动画时长（毫秒），默认 3000 */
        time?: number;
        /** 允许旋转：须与 explode 时传的一致，否则 explode 产生的旋转不会被还原 */
        enableRotation?: boolean;
        /** 动画类型 */
        easing?: Easing;
    }) => Promise<any>;
};
```

### 用法
```js
let ent = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/heart.glb", {
    anchor: "middle-bottom",
    position: [-1, 0, 1]
});
ent.addTo(app);
let ep = vjmap3d.explodeGroup(ent.node.children[0])
await ep.explode({
    time: 3000
})
await ep.reset()
```

<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 ent = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/heart.glb", {
    anchor: "middle-bottom",
    position: [-1, 0, 1]
});
ent.addTo(app);
let ep = vjmap3d.explodeGroup(ent.node.children[0])
for(let n = 0; n < 1000; n++) {
    // 动画循环
    await ep.explode({
        time: 3000
    })
    await ep.reset()
}
</pre>
</mapFrame>


### 拉伸Geojson多段线或多边形


拉伸多段线 [extrudePolyline](https://vjmap.com/map3d/api/functions/extrudePolyline.html)


拉伸多边形 [extrudePolygon](https://vjmap.com/map3d/api/functions/extrudePolygon.html)


拉伸GeoJson [extrudeGeoJSON](https://vjmap.com/map3d/api/functions/extrudeGeoJSON.html)


### 用法
```js
// 查询CAD图中的多段线进行拉伸
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" } // 是否显示视角指示器
    },
    control: { leftButtonPan: true } // 设置为左键用于平移 (同时右键将用于旋转) 和地图2d使用习惯一样
})
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 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()
});
mapviewEnt.addTo(app);
//
let cadPrj = new vjmap3d.GeoProjection(mapBounds);
let query = await svc.conditionQueryFeature({
    // https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark
    condition: ` name = "1" or name = "2"`, // 查询所有的多段线和直线
    fields: "isclosed,points", 
    limit: 100000 //设置很大，相当于把所有的都查出来。不传的话，默认只能取100条
}, pt => {
    // 查询到的每个点进行坐标处理回调
    return cadPrj.fromMercator(pt);// 转成cad的坐标
})
let polylines = [];
if (query.error) {
    showError(query.error);
    return;
} else {
    if (query.recordCount > 0) {
        for(let i = 0; i < query.result.length; i++) {
            let points = query.result[i].points;
            let lines = points.split(";;");
            for(let n = 0; n < lines.length; n++) {
                let line = lines[n].split(";");
                let pts = []
                for(let pt of line) {
                    if (!pt) continue;
                    let p = vjmap3d.GeoPoint.fromString(pt);
                    let world = vjmap3d.toWorld([p.x, p.y]);
                    pts.push( [world.x, world.z])
                }
                if (pts.length >= 2) {
                    if (query.result[i].isclosed) {
                        // 闭合
                        pts.push(pts[0])
                    }
                    polylines.push(pts);
                }
            }
        }
    }
}
const geometry = new THREE.BufferGeometry();
const material = new THREE.MeshStandardMaterial({
   // wireframe: true
});
const {position, normal, indices,uv, boundingRect} = vjmap3d.extrudePolyline(polylines, {
    depth: 1,
    lineWidth: 0.02,
    bevelSize: 0.01,
    bevelSegments: 0,
    miterLimit: 2,
});
geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normal, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
geometry.setIndex(new THREE.Uint32BufferAttribute(indices, 1));
//
const mesh = new THREE.Mesh(geometry, material);
let waveWallMat = vjmap3d.createFlowGradientMaterial(app, {
    object: mesh,
    lightScale: 0.1,
    circleTime: 1,
    lightColor: 0xffff00,
    color: 0x00ffff,
    topColor: 0x9FFCFD,
    opacity: 1,
    transparent: false,
    //topOpacity: 0.6
});
// @ts-ignore
mesh.material = waveWallMat;
app.scene.add(mesh);
```

<mapFrame>
<pre>
// 查询CAD图中的多段线进行拉伸
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" } // 是否显示视角指示器
    },
    control: { leftButtonPan: true } // 设置为左键用于平移 (同时右键将用于旋转) 和地图2d使用习惯一样
})
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 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()
});
mapviewEnt.addTo(app);
//
let cadPrj = new vjmap3d.GeoProjection(mapBounds);
let query = await svc.conditionQueryFeature({
    // https://vjmap.com/app/visual/#/query?mapid=sys_zp&version=v1&mapopenway=GeomRender&theme=dark
    condition: ` name = "1" or name = "2"`, // 查询所有的多段线和直线
    fields: "isclosed,points", 
    limit: 100000 //设置很大，相当于把所有的都查出来。不传的话，默认只能取100条
}, pt => {
    // 查询到的每个点进行坐标处理回调
    return cadPrj.fromMercator(pt);// 转成cad的坐标
})
let polylines = [];
if (query.error) {
    showError(query.error);
    return;
} else {
    if (query.recordCount > 0) {
        for(let i = 0; i < query.result.length; i++) {
            let points = query.result[i].points;
            let lines = points.split(";;");
            for(let n = 0; n < lines.length; n++) {
                let line = lines[n].split(";");
                let pts = []
                for(let pt of line) {
                    if (!pt) continue;
                    let p = vjmap3d.GeoPoint.fromString(pt);
                    let world = vjmap3d.toWorld([p.x, p.y]);
                    pts.push( [world.x, world.z])
                }
                if (pts.length >= 2) {
                    if (query.result[i].isclosed) {
                        // 闭合
                        pts.push(pts[0])
                    }
                    polylines.push(pts);
                }
            }
        }
    }
}
const geometry = new THREE.BufferGeometry();
const material = new THREE.MeshStandardMaterial({
   // wireframe: true
});
const {position, normal, indices,uv, boundingRect} = vjmap3d.extrudePolyline(polylines, {
    depth: 1,
    lineWidth: 0.02,
    bevelSize: 0.01,
    bevelSegments: 0,
    miterLimit: 2,
});
geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normal, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
geometry.setIndex(new THREE.Uint32BufferAttribute(indices, 1));
//
const mesh = new THREE.Mesh(geometry, material);
let waveWallMat = vjmap3d.createFlowGradientMaterial(app, {
    object: mesh,
    lightScale: 0.1,
    circleTime: 1,
    lightColor: 0xffff00,
    color: 0x00ffff,
    topColor: 0x9FFCFD,
    opacity: 1,
    transparent: false,
    //topOpacity: 0.6
});
// @ts-ignore
mesh.material = waveWallMat;
app.scene.add(mesh);
</pre>
</mapFrame>