Skip to content

Commit a3597fb

Browse files
deyihufuzhenn
andauthored
feat: terrain geometry tangents cal by worker (#2689)
* feat: terrain geometry tangents cal by worker * updates * fix * update * disable query terrain for map events when map is Interacting * updates * typing * updates * update * updates * updates --------- Co-authored-by: Fu Zhen <fuzhen@maptalks.org>
1 parent f39068b commit a3597fb

File tree

6 files changed

+137
-60
lines changed

6 files changed

+137
-60
lines changed

packages/gl/src/layer/raycaster/RayCaster.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,29 +66,49 @@ export default class RayCaster {
6666

6767
_testMesh(mesh, ray, map, positions, altitudes, indices, dim, matrix, count) {
6868
const coordinates = [];
69+
const glRes = map.getGLRes();
6970
for (let j = 0; j < indices.length; j += 3) {
7071
if (j > mesh.properties.skirtOffset) {
7172
break;
7273
}
7374
const a = indices[j];
7475
const b = indices[j + 1];
7576
const c = indices[j + 2];
76-
const positionsA = vec3.set(pA_VEC, positions[a * dim], positions[a * dim + 1], positions[a * dim + 2]);
77-
const pA = this._toWorldPosition(POS_A, map, positionsA, altitudes[a] / 100, matrix);
78-
const positionsB = vec3.set(pB_VEC, positions[b * dim], positions[b * dim + 1], positions[b * dim + 2]);
79-
const pB = this._toWorldPosition(POS_B, map, positionsB, altitudes[b] / 100, matrix);
80-
const positionsC = vec3.set(pC_VEC, positions[c * dim], positions[c * dim + 1], positions[c * dim + 2]);
81-
const pC = this._toWorldPosition(POS_C, map, positionsC, altitudes[c] / 100, matrix);
77+
const aindex = a * dim, bindex = b * dim, cindex = c * dim;
78+
//高频操作,尽可能的减少function(vec3.set) call,
79+
pA_VEC[0] = positions[aindex];
80+
pA_VEC[1] = positions[aindex + 1];
81+
pA_VEC[2] = positions[aindex + 2];
82+
83+
pB_VEC[0] = positions[bindex];
84+
pB_VEC[1] = positions[bindex + 1];
85+
pB_VEC[2] = positions[bindex + 2];
86+
87+
pC_VEC[0] = positions[cindex];
88+
pC_VEC[1] = positions[cindex + 1];
89+
pC_VEC[2] = positions[cindex + 2];
90+
91+
92+
const pA = this._toWorldPosition(POS_A, map, pA_VEC, altitudes[a] / 100, matrix);
93+
const pB = this._toWorldPosition(POS_B, map, pB_VEC, altitudes[b] / 100, matrix);
94+
const pC = this._toWorldPosition(POS_C, map, pC_VEC, altitudes[c] / 100, matrix);
95+
96+
// const positionsA = vec3.set(pA_VEC, positions[a * dim], positions[a * dim + 1], positions[a * dim + 2]);
97+
// const pA = this._toWorldPosition(POS_A, map, positionsA, altitudes[a] / 100, matrix);
98+
// const positionsB = vec3.set(pB_VEC, positions[b * dim], positions[b * dim + 1], positions[b * dim + 2]);
99+
// const pB = this._toWorldPosition(POS_B, map, positionsB, altitudes[b] / 100, matrix);
100+
// const positionsC = vec3.set(pC_VEC, positions[c * dim], positions[c * dim + 1], positions[c * dim + 2]);
101+
// const pC = this._toWorldPosition(POS_C, map, positionsC, altitudes[c] / 100, matrix);
82102

83103
const triangle = vec3.set(TRIANGLE, pA, pB, pC);
84104
const vAB = vec3.sub(TEMP_VEC_AB, pA, pB);
85105
const vAC = vec3.sub(TEMP_VEC_AC, pA, pC);
86106
const intersectPoint = this._testIntersection(INTERSECT_POINT, triangle, ray);
87107
if (intersectPoint) {
88-
const altitude = map.pointAtResToAltitude(intersectPoint[2], map.getGLRes());
108+
const altitude = map.pointAtResToAltitude(intersectPoint[2], glRes);
89109
TEMP_POINT.x = intersectPoint[0];
90110
TEMP_POINT.y = intersectPoint[1];
91-
const coord = map.pointAtResToCoordinate(TEMP_POINT, map.getGLRes());
111+
const coord = map.pointAtResToCoordinate(TEMP_POINT, glRes);
92112
coord.z = altitude;
93113
coordinates.push({
94114
coordinate: coord,

packages/gl/src/layer/terrain/TerrainLitPainter.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as reshader from '../../reshader';
22
import TerrainPainter from './TerrainPainter';
3-
import { extend, isNil } from '../util/util';
3+
import { extend, isNil } from '../util/util';
44
import * as ContextUtil from '../util/context';
55

66
const { getIBLResOnCanvas, getPBRUniforms, loginIBLResOnCanvas, logoutIBLResOnCanvas } = reshader.pbr.PBRUtils;
@@ -52,7 +52,7 @@ class TerrainLitPainter extends TerrainPainter {
5252

5353
createTerrainMesh(tileInfo, terrainImage) {
5454
const { mesh: terrainGeo, image: heightTexture } = terrainImage;
55-
const { positions, texcoords, triangles, leftSkirtIndex, rightSkirtIndex, bottomSkirtIndex, numVerticesWithoutSkirts } = terrainGeo;
55+
const { positions, texcoords, triangles, leftSkirtIndex, rightSkirtIndex, bottomSkirtIndex, numVerticesWithoutSkirts, tangents } = terrainGeo;
5656
const normals = new Int8Array(positions.length);
5757
for (let i = 2; i < normals.length; i += 3) {
5858
if (i < numVerticesWithoutSkirts * 3) {
@@ -72,15 +72,12 @@ class TerrainLitPainter extends TerrainPainter {
7272
aPosition: positions,
7373
aTexCoord: texcoords,
7474
aNormal: normals
75-
},
76-
triangles,
77-
0);
78-
75+
}, triangles, 0);
7976
// 共用端点时,法线值会出现错误,造成视觉上不连续,所以需要唯一化
8077
// 唯一化后,三角形数量不变,但端点数组大概会膨胀5倍以上
8178
// geo.buildUniqueVertex();
8279
// geo.createNormal();
83-
geo.createTangent();
80+
geo.createTangent('aTangent', tangents);
8481
delete geo.data.aNormal;
8582

8683
geo.generateBuffers(this.graphics);
@@ -161,7 +158,7 @@ class TerrainLitPainter extends TerrainPainter {
161158
extend(uniforms, {
162159
viewMatrix: map.viewMatrix,
163160
projMatrix: map.projMatrix,
164-
projViewMatrix : map.projViewMatrix,
161+
projViewMatrix: map.projViewMatrix,
165162
outSize: [canvas.width, canvas.height],
166163
polygonFill: [1, 1, 1, 1],
167164
terrainHeightMapResolution: [tileSize, tileSize],

packages/gl/src/layer/terrain/worker/index.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import "./zlib.min";
33
import { vec2, vec3 } from 'gl-matrix';
44
import { createMartiniData } from '../util/martini';
55
import { ColorIn } from 'colorin';
6+
import { buildTangents } from '@maptalks/tbn-packer';
7+
8+
function isNumber(val) {
9+
return (typeof val === 'number') && !isNaN(val);
10+
}
611
// 保存当前的workerId,用于告知主线程结果回传给哪个worker
712
let workerId;
813

@@ -764,6 +769,41 @@ function createColorsTexture(data, colors, tileSize) {
764769

765770
}
766771

772+
/**
773+
* create terrain geometry tangents by worker for perf
774+
* @param {*} terrainMesh
775+
* @returns
776+
*/
777+
function createTerrainGeometryTangents(terrainMesh) {
778+
if (!terrainMesh) {
779+
return;
780+
}
781+
const { positions, texcoords, triangles, leftSkirtIndex, rightSkirtIndex, bottomSkirtIndex, numVerticesWithoutSkirts } = terrainMesh;
782+
if (!positions || !texcoords || !triangles) {
783+
return;
784+
}
785+
if (!isNumber(leftSkirtIndex) || !isNumber(rightSkirtIndex) || !isNumber(bottomSkirtIndex) || !isNumber(numVerticesWithoutSkirts)) {
786+
return;
787+
}
788+
const normals = new Int8Array(positions.length);
789+
for (let i = 2; i < normals.length; i += 3) {
790+
if (i < numVerticesWithoutSkirts * 3) {
791+
normals[i] = 1;
792+
} else if (i < leftSkirtIndex / 2 * 3) {
793+
normals[i - 2] = -1;
794+
} else if (i < rightSkirtIndex / 2 * 3) {
795+
normals[i - 2] = 1;
796+
} else if (i < bottomSkirtIndex / 2 * 3) {
797+
normals[i - 1] = -1;
798+
} else {
799+
// top
800+
normals[i - 1] = 1;
801+
}
802+
}
803+
const tangents = buildTangents(positions, normals, texcoords, triangles);
804+
return new Float32Array(tangents);
805+
}
806+
767807
export const onmessage = function (message, postResponse) {
768808
const data = message.data;
769809
if (data.command === 'addLayer' || data.command === 'removeLayer') {
@@ -775,12 +815,20 @@ export const onmessage = function (message, postResponse) {
775815
const colors = (data.params || {}).colors;
776816
const tileSize = (data.params || {}).tileSize;
777817
loadTerrain(data.params, (data, transferables) => {
818+
transferables = transferables || [];
819+
//create terrain colors texture
778820
const texture = createColorsTexture(data, colors, tileSize);
779821
if (texture) {
780822
data.colorsTexture = texture;
781-
transferables = transferables || [];
782823
transferables.push(texture);
783824
}
825+
826+
//create terrain geometry tangents attribute
827+
const tangents = createTerrainGeometryTangents(data.mesh);
828+
if (tangents && data.mesh) {
829+
data.mesh.tangents = tangents;
830+
transferables.push(tangents.buffer);
831+
}
784832
postResponse(data.error, data, transferables);
785833
});
786834
} else if (data.command === 'abortTerrain') {

packages/gl/src/reshader/Geometry.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default class Geometry {
8888
}
8989
}
9090

91-
static padGPUBufferAlignment(array:TypedArray, vertexCount: number): TypedArray {
91+
static padGPUBufferAlignment(array: TypedArray, vertexCount: number): TypedArray {
9292
const itemBytes = array.byteLength / vertexCount;
9393
if (itemBytes % 4 === 0) {
9494
return array;
@@ -226,7 +226,7 @@ export default class Geometry {
226226
if (!id) {
227227
id = attribute.array.buffer['__id'] = GUID();
228228
}
229-
this.data[attr] = {
229+
this.data[attr] = {
230230
buffer: id,
231231
offset: attribute.byteOffset,
232232
stride: attribute.byteStride,
@@ -467,9 +467,15 @@ export default class Geometry {
467467
} else {
468468
ctor = this.elements.constructor;
469469
}
470-
this.indices = new ctor(elements.length);
471-
for (let i = 0; i < elements.length; i++) {
472-
this.indices[i] = elements[i];
470+
if (elements instanceof ctor) {
471+
//2x faster by new TypeArray(typearray)
472+
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array
473+
this.indices = new ctor(elements);
474+
} else {
475+
this.indices = new ctor(elements.length);
476+
for (let i = 0; i < elements.length; i++) {
477+
this.indices[i] = elements[i];
478+
}
473479
}
474480
}
475481
this.elements = this.elements.destroy ? this.elements : Geometry.createElementBuffer(device, this.elements);
@@ -495,7 +501,7 @@ export default class Geometry {
495501
if (isArray(data)) {
496502
// 因为data可能被转成regl buffer,需要保存到this._vertexCount
497503
// 在 updateData时再更新
498-
this._vertexCount = Math.ceil(data.length / positionSize);
504+
this._vertexCount = Math.ceil(data.length / positionSize);
499505
} else if (data && data.count !== undefined) {
500506
this._vertexCount = data.count;
501507
}
@@ -600,6 +606,7 @@ export default class Geometry {
600606
return this;
601607
}
602608

609+
603610
_updateGPUBuffer(buffer : GPUBuffer, data : AttributeData, offset: number, byteLength: number) {
604611
if (Array.isArray(data)) {
605612
data = new Float32Array(data);
@@ -899,7 +906,7 @@ export default class Geometry {
899906
//对于POSITION数据,为避免updateBBox时频繁创建临时数组,采用缓存tempPosArray的策略获取interleavedArray,
900907
//对于非POSITION的数据,直接readInterleavedArray读取即可
901908
if (name === this.desc.positionAttribute) {
902-
if (!this._tempPosArray || (this._tempPosArray && this._tempPosArray.length < size *count)) {
909+
if (!this._tempPosArray || (this._tempPosArray && this._tempPosArray.length < size * count)) {
903910
this._tempPosArray = new ctor(size * count);
904911
return gltf.GLTFLoader.readInterleavedArray(this._tempPosArray, attribute, count, size, stride, offset, componentType);
905912
}
@@ -915,18 +922,23 @@ export default class Geometry {
915922
}
916923
}
917924

918-
createTangent(name = 'aTangent') {
925+
createTangent(name = 'aTangent', tangentsDataArray?: Float32Array | Array<number>) {
919926
this._incrVersion();
920927
//TODO data 可能是含stride的interleaved类型
921928
const { normalAttribute, positionAttribute, uv0Attribute } = this.desc;
922929
const normals = this._getAttributeData(normalAttribute);
923930
const positions = this._getAttributeData(positionAttribute);
924-
const tangents = buildTangents(
925-
positions,
926-
normals,
927-
this.data[uv0Attribute],
928-
this.elements
929-
);
931+
let tangents;
932+
if (tangentsDataArray && tangentsDataArray.length) {
933+
tangents = tangentsDataArray;
934+
} else {
935+
tangents = buildTangents(
936+
positions,
937+
normals,
938+
this.data[uv0Attribute],
939+
this.elements
940+
);
941+
}
930942
const aTangent = this.data[name] = new Float32Array(tangents.length);
931943
const t: vec4 = [0, 0, 0, 0], n: vec3 = [0, 0, 0], q: vec4 = [0, 0, 0, 0];
932944
for (let i = 0; i < tangents.length; i += 4) {
@@ -982,7 +994,7 @@ export default class Geometry {
982994
const oldData = {};
983995

984996
let pos = data[this.desc.positionAttribute];
985-
pos = pos.length ? pos : pos.array; //存在两种结构 array或者 { array }
997+
pos = pos.length ? pos : pos.array; //存在两种结构 array或者 { array }
986998
if (!isArray(pos)) {
987999
throw new Error(this.desc.positionAttribute + ' must be array to build unique vertex.');
9881000
}
@@ -1016,7 +1028,7 @@ export default class Geometry {
10161028
indices[i] = cursor++;
10171029
}
10181030
pos = this.data[this.desc.positionAttribute];
1019-
this._vertexCount = Math.ceil(pos.length / this.desc.positionSize);
1031+
this._vertexCount = Math.ceil(pos.length / this.desc.positionSize);
10201032
delete this._reglData;
10211033
}
10221034

@@ -1048,7 +1060,7 @@ export default class Geometry {
10481060

10491061
//@internal
10501062
_forEachBuffer(fn: (buffer: any) => void) {
1051-
if (this.elements && this.elements.destroy) {
1063+
if (this.elements && this.elements.destroy) {
10521064
fn(this.elements);
10531065
}
10541066
for (const p in this.data) {
@@ -1283,7 +1295,7 @@ function createGPUBuffer(device, data, usage, label) {
12831295
data = new Float32Array(data);
12841296
}
12851297
const ctor = data.constructor;
1286-
// f32 in default
1298+
// f32 in default
12871299
const byteLength = data.byteLength;
12881300
// mappedAtCreation requires size is a multiplier of 4
12891301
// https://github.com/gpuweb/gpuweb/issues/5105

packages/maptalks/src/map/Map.DomEvents.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ Map.include(/** @lends Map.prototype */ {
404404

405405
//@internal
406406
_wrapTerrainData(eventParam: MapEventDataType) {
407-
if (this.options['queryTerrainInMapEvents'] && eventParam.containerPoint && !eventParam.terrain) {
407+
if (this.options['queryTerrainInMapEvents'] && eventParam.containerPoint && !eventParam.terrain && !this.isInteracting()) {
408408
eventParam.terrain = this._queryTerrainInfo(eventParam.containerPoint);
409409
}
410410
},

0 commit comments

Comments
 (0)