ice3 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/_types/assets/ICEGLBReader.d.ts +8 -1
- package/dist/_types/assets/ICEPreparedGLB.d.ts +26 -0
- package/dist/_types/assets/ICEPreviewGeometryPacket.d.ts +60 -0
- package/dist/_types/assets/index.d.ts +3 -0
- package/dist/_types/core/camera/ICEPerspectiveCamera.d.ts +8 -2
- package/dist/_types/core/geometry/ICEGeometry.d.ts +13 -0
- package/dist/_types/core/geometry/ICEIndexedGeometry.d.ts +3 -1
- package/dist/_types/core/index.d.ts +2 -2
- package/dist/_types/core/material/ICEBasicMaterial.d.ts +52 -0
- package/dist/_types/renderer/InstanceBatch.d.ts +7 -0
- package/dist/_types/renderer/SceneBounds.d.ts +16 -0
- package/dist/_types/renderer/ShadowFrustum.d.ts +20 -0
- package/dist/_types/renderer/WebGL2DepthMath.d.ts +22 -0
- package/dist/_types/renderer/WebGL2GeometryCache.d.ts +16 -2
- package/dist/_types/renderer/WebGL2InstanceBatch.d.ts +1 -1
- package/dist/_types/renderer/WebGL2Program.d.ts +68 -0
- package/dist/_types/renderer/WebGL2RendererBackend.d.ts +44 -0
- package/dist/_types/renderer/WebGL2SceneDepth.d.ts +19 -0
- package/dist/_types/renderer/WebGL2Shader.d.ts +11 -0
- package/dist/_types/renderer/WebGL2ShadowMap.d.ts +42 -0
- package/dist/_types/renderer/WebGL2TextureCache.d.ts +31 -0
- package/dist/_types/renderer/WebGL2Uv.d.ts +16 -0
- package/dist/_types/renderer/WebGPUDrawStorage.d.ts +1 -0
- package/dist/_types/renderer/WebGPUGeometryCache.d.ts +16 -2
- package/dist/_types/renderer/WebGPURenderPipeline.d.ts +1 -1
- package/dist/_types/renderer/WebGPURendererBackend.d.ts +2 -0
- package/dist/_types/renderer/index.d.ts +1 -1
- package/dist/_types/renderer/types.d.ts +9 -1
- package/dist/_types/runtime/ICE3.d.ts +8 -0
- package/dist/_types/runtime/asset/ICEAssetSceneBuilder.d.ts +8 -2
- package/dist/_types/runtime/asset/ICEPreviewPacketLoader.d.ts +25 -0
- package/dist/_types/runtime/asset/ICETilesetLoader.d.ts +0 -1
- package/dist/_types/runtime/index.d.ts +2 -1
- package/dist/_types/runtime/input/ICEOrbitController.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2742 -242
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -315,6 +315,7 @@ var ICEIndexedGeometry = class {
|
|
|
315
315
|
edgeIndices;
|
|
316
316
|
colors;
|
|
317
317
|
semanticIndices;
|
|
318
|
+
identity;
|
|
318
319
|
constructor(options) {
|
|
319
320
|
if (!options.id.trim()) {
|
|
320
321
|
throw new TypeError("ICE geometry ID must not be empty");
|
|
@@ -343,6 +344,11 @@ var ICEIndexedGeometry = class {
|
|
|
343
344
|
throw new TypeError("ICE semantic index count must match the vertex count");
|
|
344
345
|
}
|
|
345
346
|
this.id = options.id;
|
|
347
|
+
this.identity = Object.freeze({
|
|
348
|
+
geometryId: options.identity?.geometryId ?? options.id,
|
|
349
|
+
topologyDigest: options.identity?.topologyDigest ?? "",
|
|
350
|
+
attributeDigest: options.identity?.attributeDigest ?? ""
|
|
351
|
+
});
|
|
346
352
|
this.positions = options.positions;
|
|
347
353
|
this.indices = options.indices;
|
|
348
354
|
if (options.edgeIndices) {
|
|
@@ -376,25 +382,48 @@ var ICEIndexedGeometry = class {
|
|
|
376
382
|
edgeIndexCount: this.edgeIndices?.length ?? 0,
|
|
377
383
|
colorCount: this.colors ? this.positions.length / 3 : 0,
|
|
378
384
|
...this.semanticIndices ? { semanticIndexCount: this.semanticIndices.length } : {},
|
|
385
|
+
...this.identity.topologyDigest || this.identity.attributeDigest ? { identity: this.identity } : {},
|
|
379
386
|
bounds: this.getBounds()
|
|
380
387
|
};
|
|
381
388
|
}
|
|
382
389
|
};
|
|
383
390
|
|
|
384
391
|
// packages/core/dist/material/ICEBasicMaterial.js
|
|
392
|
+
var DEFAULT_ROUGHNESS = 0.85;
|
|
385
393
|
var ICEBasicMaterial = class {
|
|
386
394
|
typeId = "ice3:BasicMaterial";
|
|
387
395
|
color;
|
|
388
396
|
edgeColor;
|
|
397
|
+
shading;
|
|
398
|
+
map;
|
|
399
|
+
normalMap;
|
|
400
|
+
ormMap;
|
|
401
|
+
metallic;
|
|
402
|
+
roughness;
|
|
403
|
+
emissive;
|
|
389
404
|
constructor(options) {
|
|
390
405
|
this.color = options.color;
|
|
391
406
|
this.edgeColor = options.edgeColor;
|
|
407
|
+
this.shading = options.shading;
|
|
408
|
+
this.map = options.map;
|
|
409
|
+
this.normalMap = options.normalMap;
|
|
410
|
+
this.ormMap = options.ormMap;
|
|
411
|
+
this.metallic = options.metallic ?? 0;
|
|
412
|
+
this.roughness = options.roughness ?? DEFAULT_ROUGHNESS;
|
|
413
|
+
this.emissive = options.emissive;
|
|
392
414
|
}
|
|
393
415
|
toJSON() {
|
|
394
416
|
return {
|
|
395
417
|
typeId: this.typeId,
|
|
396
418
|
color: this.color,
|
|
397
|
-
...this.edgeColor ? { edgeColor: this.edgeColor } : {}
|
|
419
|
+
...this.edgeColor ? { edgeColor: this.edgeColor } : {},
|
|
420
|
+
...this.shading ? { shading: this.shading } : {},
|
|
421
|
+
...this.map ? { map: this.map } : {},
|
|
422
|
+
...this.normalMap ? { normalMap: this.normalMap } : {},
|
|
423
|
+
...this.ormMap ? { ormMap: this.ormMap } : {},
|
|
424
|
+
...this.metallic !== 0 ? { metallic: this.metallic } : {},
|
|
425
|
+
...this.roughness !== DEFAULT_ROUGHNESS ? { roughness: this.roughness } : {},
|
|
426
|
+
...this.emissive ? { emissive: this.emissive } : {}
|
|
398
427
|
};
|
|
399
428
|
}
|
|
400
429
|
};
|
|
@@ -426,16 +455,31 @@ var ICEPerspectiveCamera = class {
|
|
|
426
455
|
this.lookTarget = new ICEVector3(...target);
|
|
427
456
|
return this;
|
|
428
457
|
}
|
|
458
|
+
setClipPlanes(near, far) {
|
|
459
|
+
if (!Number.isFinite(near) || !Number.isFinite(far) || near <= 0 || far <= near) {
|
|
460
|
+
throw new RangeError("Camera near/far values are invalid");
|
|
461
|
+
}
|
|
462
|
+
this.near = near;
|
|
463
|
+
this.far = far;
|
|
464
|
+
return this;
|
|
465
|
+
}
|
|
429
466
|
getPosition() {
|
|
430
467
|
return this.eye;
|
|
431
468
|
}
|
|
432
469
|
getTarget() {
|
|
433
470
|
return this.lookTarget;
|
|
434
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* 投影矩阵。屏幕空间效果要从深度重建视空间坐标,光有 viewProjection 不够。
|
|
474
|
+
*/
|
|
475
|
+
getProjectionMatrix(aspect) {
|
|
476
|
+
return new ICEMatrix4().perspective(this.fov, aspect, this.near, this.far);
|
|
477
|
+
}
|
|
478
|
+
getViewMatrix() {
|
|
479
|
+
return new ICEMatrix4().lookAt(this.eye, this.lookTarget, new ICEVector3(0, 1, 0));
|
|
480
|
+
}
|
|
435
481
|
getViewProjectionMatrix(aspect) {
|
|
436
|
-
|
|
437
|
-
const view = new ICEMatrix4().lookAt(this.eye, this.lookTarget, new ICEVector3(0, 1, 0));
|
|
438
|
-
return projection.multiply(view);
|
|
482
|
+
return this.getProjectionMatrix(aspect).multiply(this.getViewMatrix());
|
|
439
483
|
}
|
|
440
484
|
};
|
|
441
485
|
|
|
@@ -765,6 +809,7 @@ var ICEMesh = class _ICEMesh extends ICENode {
|
|
|
765
809
|
static fromJSON(value) {
|
|
766
810
|
const geometry = value.geometry;
|
|
767
811
|
const material = value.material;
|
|
812
|
+
const shading = material.shading === "directional" || material.shading === "flat" || material.shading === "water" ? material.shading : void 0;
|
|
768
813
|
return new _ICEMesh({
|
|
769
814
|
id: value.id,
|
|
770
815
|
displayName: typeof value.displayName === "string" ? value.displayName : value.id,
|
|
@@ -781,7 +826,8 @@ var ICEMesh = class _ICEMesh extends ICENode {
|
|
|
781
826
|
}),
|
|
782
827
|
material: new ICEBasicMaterial({
|
|
783
828
|
color: String(material.color),
|
|
784
|
-
...typeof material.edgeColor === "string" ? { edgeColor: material.edgeColor } : {}
|
|
829
|
+
...typeof material.edgeColor === "string" ? { edgeColor: material.edgeColor } : {},
|
|
830
|
+
...shading ? { shading } : {}
|
|
785
831
|
})
|
|
786
832
|
});
|
|
787
833
|
}
|
|
@@ -909,17 +955,26 @@ function isRenderBatch(node) {
|
|
|
909
955
|
}
|
|
910
956
|
|
|
911
957
|
// packages/renderer/dist/WebGL2Program.js
|
|
912
|
-
var
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
958
|
+
var LIGHT_DIRECTION = [-0.35, 0.85, 0.4];
|
|
959
|
+
var LIGHTING = Object.freeze({
|
|
960
|
+
ambient: 0.72,
|
|
961
|
+
direct: 0.72,
|
|
962
|
+
sky: 0.18,
|
|
963
|
+
minimum: 0.92,
|
|
964
|
+
maximum: 1.42
|
|
965
|
+
});
|
|
966
|
+
var VERTEX_SOURCE = `#version 300 es
|
|
967
|
+
in vec3 a_position;
|
|
968
|
+
in vec3 a_color;
|
|
969
|
+
in vec4 a_instanceColumn0;
|
|
970
|
+
in vec4 a_instanceColumn1;
|
|
971
|
+
in vec4 a_instanceColumn2;
|
|
972
|
+
in vec4 a_instanceColumn3;
|
|
919
973
|
uniform mat4 u_worldViewProjection;
|
|
920
974
|
uniform vec2 u_clipOffset;
|
|
921
975
|
uniform float u_useInstancing;
|
|
922
|
-
|
|
976
|
+
out vec3 v_color;
|
|
977
|
+
out vec3 v_worldPosition;
|
|
923
978
|
|
|
924
979
|
void main() {
|
|
925
980
|
mat4 instanceMatrix = mat4(
|
|
@@ -937,17 +992,313 @@ void main() {
|
|
|
937
992
|
gl_Position = u_worldViewProjection * position;
|
|
938
993
|
gl_Position.xy += u_clipOffset * gl_Position.w;
|
|
939
994
|
v_color = a_color;
|
|
995
|
+
v_worldPosition = position.xyz;
|
|
940
996
|
}
|
|
941
997
|
`;
|
|
942
|
-
var FRAGMENT_SOURCE =
|
|
998
|
+
var FRAGMENT_SOURCE = `#version 300 es
|
|
943
999
|
precision highp float;
|
|
944
1000
|
uniform vec4 u_color;
|
|
1001
|
+
uniform vec4 u_clipPlane;
|
|
1002
|
+
uniform float u_pickMode;
|
|
945
1003
|
uniform float u_useVertexColor;
|
|
946
|
-
|
|
1004
|
+
uniform float u_useDirectionalShading;
|
|
1005
|
+
uniform float u_waterShading;
|
|
1006
|
+
uniform vec3 u_cameraPosition;
|
|
1007
|
+
uniform sampler2D u_shadowMap;
|
|
1008
|
+
uniform mat4 u_lightViewProjection;
|
|
1009
|
+
uniform float u_shadowEnabled;
|
|
1010
|
+
uniform float u_shadowNormalOffset;
|
|
1011
|
+
uniform float u_shadowDebug;
|
|
1012
|
+
uniform sampler2D u_baseColorMap;
|
|
1013
|
+
uniform float u_useBaseColorMap;
|
|
1014
|
+
uniform vec2 u_uvScale;
|
|
1015
|
+
uniform sampler2D u_normalMap;
|
|
1016
|
+
uniform float u_useNormalMap;
|
|
1017
|
+
uniform sampler2D u_ormMap;
|
|
1018
|
+
uniform float u_useOrmMap;
|
|
1019
|
+
uniform float u_ormOcclusion;
|
|
1020
|
+
uniform float u_ormMetallicRoughness;
|
|
1021
|
+
uniform float u_normalScale;
|
|
1022
|
+
uniform float u_metallic;
|
|
1023
|
+
uniform float u_roughness;
|
|
1024
|
+
uniform vec3 u_emissive;
|
|
1025
|
+
uniform sampler2D u_sceneDepth;
|
|
1026
|
+
uniform float u_useSceneDepth;
|
|
1027
|
+
uniform mat4 u_projection;
|
|
1028
|
+
uniform vec2 u_viewport;
|
|
1029
|
+
uniform vec2 u_clipRange;
|
|
1030
|
+
uniform float u_aoRadius;
|
|
1031
|
+
uniform float u_aoStrength;
|
|
1032
|
+
in vec3 v_color;
|
|
1033
|
+
in vec3 v_worldPosition;
|
|
1034
|
+
out vec4 outputColor;
|
|
1035
|
+
|
|
1036
|
+
// \u7531\u5C4F\u5E55 UV \u4E0E\u6DF1\u5EA6\u7F13\u51B2\u91CD\u5EFA\u89C6\u7A7A\u95F4\u5750\u6807\uFF08\u4E0E WebGL2DepthMath \u7684\u6362\u7B97\u4E00\u81F4\uFF09\u3002
|
|
1037
|
+
vec3 viewPositionAt(vec2 uv, float tanHalfFov, float aspect, float near, float far) {
|
|
1038
|
+
float depth = texture(u_sceneDepth, uv).r;
|
|
1039
|
+
float ndcZ = depth * 2.0 - 1.0;
|
|
1040
|
+
float linear = (2.0 * near * far) / (far + near - ndcZ * (far - near));
|
|
1041
|
+
vec2 ndc = uv * 2.0 - 1.0;
|
|
1042
|
+
return vec3(ndc.x * tanHalfFov * aspect * linear, ndc.y * tanHalfFov * linear, -linear);
|
|
1043
|
+
}
|
|
947
1044
|
|
|
948
1045
|
void main() {
|
|
949
|
-
|
|
950
|
-
|
|
1046
|
+
// Section plane: discard the half-space on the negative side. A zero plane
|
|
1047
|
+
// (0,0,0,0) evaluates to 0 and keeps everything, so it doubles as "off".
|
|
1048
|
+
if (dot(v_worldPosition, u_clipPlane.xyz) + u_clipPlane.w < 0.0) {
|
|
1049
|
+
discard;
|
|
1050
|
+
}
|
|
1051
|
+
if (u_pickMode > 0.5) {
|
|
1052
|
+
outputColor = u_color;
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
vec3 color = u_color.rgb
|
|
1056
|
+
* mix(vec3(1.0), v_color, step(0.5, u_useVertexColor));
|
|
1057
|
+
vec3 normal = normalize(cross(
|
|
1058
|
+
dFdx(v_worldPosition),
|
|
1059
|
+
dFdy(v_worldPosition)
|
|
1060
|
+
));
|
|
1061
|
+
// Box-projected UVs make the tangent frame analytic: the projection plane's
|
|
1062
|
+
// two world axes are the tangents, so no vertex tangent channel is needed.
|
|
1063
|
+
vec3 tangent = vec3(0.0);
|
|
1064
|
+
vec3 bitangent = vec3(0.0);
|
|
1065
|
+
vec3 axisWeight = abs(normal);
|
|
1066
|
+
if (axisWeight.x >= axisWeight.y && axisWeight.x >= axisWeight.z) {
|
|
1067
|
+
tangent = vec3(0.0, 0.0, -sign(normal.x));
|
|
1068
|
+
bitangent = vec3(0.0, 1.0, 0.0);
|
|
1069
|
+
} else if (axisWeight.y >= axisWeight.z) {
|
|
1070
|
+
tangent = vec3(1.0, 0.0, 0.0);
|
|
1071
|
+
bitangent = vec3(0.0, 0.0, sign(normal.y));
|
|
1072
|
+
} else {
|
|
1073
|
+
tangent = vec3(sign(normal.z), 0.0, 0.0);
|
|
1074
|
+
bitangent = vec3(0.0, 1.0, 0.0);
|
|
1075
|
+
}
|
|
1076
|
+
// \u4E09\u5F20\u8D34\u56FE\u5171\u7528\u540C\u4E00\u5957\u76D2\u6295\u5F71 UV\uFF1A\u6CD5\u7EBF\u4E3B\u5BFC\u8F74\u51B3\u5B9A\u6295\u5F71\u5E73\u9762\uFF0C\u5355\u4F4D\u662F\u7C73\u3002
|
|
1077
|
+
// \u5199\u4E00\u6B21\u5C31\u591F\u4E86\u2014\u2014\u5E95\u56FE\u3001\u6CD5\u7EBF\u56FE\u3001ORM \u5404\u5199\u4E00\u904D\u8FDF\u65E9\u4F1A\u6F02\u3002
|
|
1078
|
+
vec2 surfaceUv;
|
|
1079
|
+
if (axisWeight.x >= axisWeight.y && axisWeight.x >= axisWeight.z) {
|
|
1080
|
+
surfaceUv = vec2(-sign(normal.x) * v_worldPosition.z, v_worldPosition.y);
|
|
1081
|
+
} else if (axisWeight.y >= axisWeight.z) {
|
|
1082
|
+
surfaceUv = vec2(v_worldPosition.x, sign(normal.y) * v_worldPosition.z);
|
|
1083
|
+
} else {
|
|
1084
|
+
surfaceUv = vec2(sign(normal.z) * v_worldPosition.x, v_worldPosition.y);
|
|
1085
|
+
}
|
|
1086
|
+
surfaceUv *= u_uvScale;
|
|
1087
|
+
if (u_useNormalMap > 0.5) {
|
|
1088
|
+
vec3 sampled = texture(u_normalMap, surfaceUv).xyz * 2.0 - 1.0;
|
|
1089
|
+
sampled.xy *= u_normalScale;
|
|
1090
|
+
normal = normalize(
|
|
1091
|
+
normalize(tangent) * sampled.x
|
|
1092
|
+
+ normalize(bitangent) * sampled.y
|
|
1093
|
+
+ normal * sampled.z
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
// \u4E3B\u5149 + \u534A\u7403\u5929\u5149\u3002\u6CD5\u7EBF\u7531\u5C4F\u5E55\u7A7A\u95F4\u5BFC\u6570\u6C42\u51FA\uFF0C\u671D\u5411\u4E0E\u7ED5\u5E8F\u4E00\u81F4\uFF1A
|
|
1097
|
+
// \u8FCE\u7740\u5149\u7684\u4E00\u9762\u5403\u6EE1\u76F4\u5C04\uFF0C\u80CC\u5149\u9762\u53EA\u7559 35% \u7684\u53CD\u5F39\u5149\uFF0C\u6697\u90E8\u4E0D\u81F3\u4E8E\u7CCA\u6210\u4E00\u7247\u3002
|
|
1098
|
+
vec3 lightDirection = normalize(vec3(${LIGHT_DIRECTION.join(", ")}));
|
|
1099
|
+
float ndl = dot(normal, lightDirection);
|
|
1100
|
+
float key = max(ndl, 0.0) + 0.35 * max(-ndl, 0.0);
|
|
1101
|
+
float sky = 0.5 + 0.5 * normal.y;
|
|
1102
|
+
// ORM\uFF1AR \u906E\u853D\u3001G \u7C97\u7CD9\u5EA6\u3001B \u91D1\u5C5E\u5EA6\uFF0C\u548C glTF \u7684\u6253\u5305\u7EA6\u5B9A\u4E00\u81F4\u3002
|
|
1103
|
+
// \u906E\u853D\u53EA\u538B\u95F4\u63A5\u5149\uFF08\u73AF\u5883\u4E0E\u5929\u5149\uFF09\uFF0C\u76F4\u5C04\u7167\u5EA6\u4E0D\u53D7\u5F71\u54CD\u2014\u2014\u5426\u5219\u9634\u5F71\u548C\u8D34\u56FE\u4F1A\u4E92\u76F8\u6253\u67B6\u3002
|
|
1104
|
+
float surfaceOcclusion = 1.0;
|
|
1105
|
+
float surfaceMetallic = u_metallic;
|
|
1106
|
+
float surfaceRoughness = clamp(u_roughness, 0.04, 1.0);
|
|
1107
|
+
if (u_useOrmMap > 0.5) {
|
|
1108
|
+
vec3 orm = texture(u_ormMap, surfaceUv).rgb;
|
|
1109
|
+
if (u_ormOcclusion > 0.5) surfaceOcclusion = clamp(orm.r, 0.0, 1.0);
|
|
1110
|
+
if (u_ormMetallicRoughness > 0.5) {
|
|
1111
|
+
surfaceRoughness = clamp(surfaceRoughness * orm.g, 0.04, 1.0);
|
|
1112
|
+
surfaceMetallic = clamp(surfaceMetallic * orm.b, 0.0, 1.0);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
// \u76F4\u5C04\u5149\u5F3A\u5EA6\u6309\u771F\u5B9E\u65E5\u7167\u7ED9\uFF1A\u671D\u4E0A\u7684\u9762\u4F1A\u8D85\u8FC7 1\uFF0C\u8FC7\u66DD\u5230\u63A5\u8FD1\u767D\u8272\u662F\u5BF9\u7684\uFF0C
|
|
1116
|
+
// \u4E4B\u524D\u4E0A\u9650\u538B\u5230 1 \u9644\u8FD1\uFF0C\u6574\u4E2A\u5382\u533A\u5C31\u6C38\u8FDC\u6652\u4E0D\u5230\u592A\u9633\uFF0C\u770B\u7740\u53D1\u95F7\u53D1\u6697\u3002
|
|
1117
|
+
float shade = clamp(
|
|
1118
|
+
(${LIGHTING.ambient} + ${LIGHTING.sky} * sky) * surfaceOcclusion
|
|
1119
|
+
+ ${LIGHTING.direct} * key,
|
|
1120
|
+
${LIGHTING.minimum},
|
|
1121
|
+
${LIGHTING.maximum}
|
|
1122
|
+
);
|
|
1123
|
+
// Base-colour texture. There is no UV vertex channel: the projection is
|
|
1124
|
+
// derived from the surface normal's dominant axis, in metres, which matches
|
|
1125
|
+
// the coordinates Studio writes into exported glTF.
|
|
1126
|
+
if (u_useBaseColorMap > 0.5) {
|
|
1127
|
+
color *= texture(u_baseColorMap, surfaceUv).rgb;
|
|
1128
|
+
}
|
|
1129
|
+
vec3 lit = color * mix(1.0, shade, u_useDirectionalShading);
|
|
1130
|
+
|
|
1131
|
+
// \u8868\u9762\u9897\u7C92\uFF1A\u6309\u4E16\u754C\u5750\u6807\u53D6\u4E24\u5C42\u54C8\u5E0C\uFF0C\u6253\u6563\u6574\u7247\u7EAF\u8272\u5E26\u6765\u7684\u5851\u6599\u611F\u3002
|
|
1132
|
+
// \u5E45\u5EA6\u63A7\u5236\u5728 \xB16% \u5185\uFF0C\u8FDC\u4E86\u770B\u4E0D\u51FA\u3001\u8FD1\u4E86\u6709\u8D28\u611F\uFF0C\u4E0D\u4F1A\u55A7\u5BBE\u593A\u4E3B\u3002
|
|
1133
|
+
vec3 fine = floor(v_worldPosition * 4.5);
|
|
1134
|
+
vec3 coarse = floor(v_worldPosition * 0.42);
|
|
1135
|
+
float grain = fract(sin(dot(fine, vec3(12.9898, 78.233, 37.719))) * 43758.5453);
|
|
1136
|
+
float stain = fract(sin(dot(coarse, vec3(39.346, 11.135, 83.155))) * 24634.6345);
|
|
1137
|
+
// \u4F4E\u9891\u6C61\u6E0D\u8D1F\u8D23\u5927\u9762\u79EF\u4E0D\u5300\uFF0C\u9AD8\u9891\u9897\u7C92\u8D1F\u8D23\u8FD1\u770B\u7684\u624B\u611F\u3002
|
|
1138
|
+
lit *= 0.94 + 0.06 * grain + 0.05 * (stain - 0.5);
|
|
1139
|
+
|
|
1140
|
+
// Liquid surfaces use an analytic Fresnel reflection plus shallow ripple
|
|
1141
|
+
// normals. The effect is opt-in through material shading and remains
|
|
1142
|
+
// independent from any application-specific material naming.
|
|
1143
|
+
if (u_waterShading > 0.5) {
|
|
1144
|
+
float waveA = sin(v_worldPosition.x * 1.1
|
|
1145
|
+
+ sin(v_worldPosition.z * 0.43) * 2.0);
|
|
1146
|
+
float waveB = sin(v_worldPosition.z * 1.7
|
|
1147
|
+
+ v_worldPosition.x * 0.31);
|
|
1148
|
+
float waveC = sin((v_worldPosition.x + v_worldPosition.z) * 2.3);
|
|
1149
|
+
vec3 rippleNormal = normalize(vec3(
|
|
1150
|
+
waveA * 0.09 + waveC * 0.025,
|
|
1151
|
+
1.0,
|
|
1152
|
+
waveB * 0.09 - waveC * 0.02
|
|
1153
|
+
));
|
|
1154
|
+
vec3 viewDirection = normalize(u_cameraPosition - v_worldPosition);
|
|
1155
|
+
float facing = clamp(dot(rippleNormal, viewDirection), 0.0, 1.0);
|
|
1156
|
+
float fresnel = pow(1.0 - facing, 2.4);
|
|
1157
|
+
vec3 reflectedSky = vec3(0.22, 0.34, 0.36);
|
|
1158
|
+
vec3 surfaceColor = mix(
|
|
1159
|
+
color,
|
|
1160
|
+
reflectedSky,
|
|
1161
|
+
0.30 + 0.30 * fresnel
|
|
1162
|
+
);
|
|
1163
|
+
surfaceColor *= 0.92 + 0.09 * waveA + 0.05 * waveB;
|
|
1164
|
+
vec3 halfDirection = normalize(lightDirection + viewDirection);
|
|
1165
|
+
float sparkle = pow(
|
|
1166
|
+
max(dot(rippleNormal, halfDirection), 0.0),
|
|
1167
|
+
36.0
|
|
1168
|
+
);
|
|
1169
|
+
lit = mix(lit, surfaceColor * shade, 0.92)
|
|
1170
|
+
+ vec3(0.42, 0.52, 0.54) * sparkle * 0.55;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// \u5C4F\u5E55\u7A7A\u95F4\u906E\u853D\uFF1A\u5148\u628A\u6DF1\u5EA6\u8FD8\u539F\u6210\u89C6\u7A7A\u95F4\u5750\u6807\uFF0C\u518D\u6309**\u4E16\u754C\u534A\u5F84**\u53D6\u6837\u3002
|
|
1174
|
+
// \u76F4\u63A5\u6BD4\u8F83 NDC \u6DF1\u5EA6\u5DEE\u8868\u8FBE\u4E0D\u4E86"1 \u7C73"\u8FD9\u79CD\u91CF\uFF08\u8FDC\u7AEF\u5343\u5206\u4E4B\u51E0\u5C31\u8DE8\u4E86\u5341\u7C73\uFF09\uFF0C
|
|
1175
|
+
// \u8FD9\u4E5F\u662F SPEC-007 \u524D\u4E09\u8F6E\u5931\u8D25\u7684\u539F\u56E0\u3002
|
|
1176
|
+
if (u_useSceneDepth > 0.5) {
|
|
1177
|
+
vec2 screenUv = gl_FragCoord.xy / u_viewport;
|
|
1178
|
+
float tanHalfFov = 1.0 / u_projection[1][1];
|
|
1179
|
+
float aspect = u_projection[1][1] / u_projection[0][0];
|
|
1180
|
+
float near = u_clipRange.x;
|
|
1181
|
+
float far = u_clipRange.y;
|
|
1182
|
+
|
|
1183
|
+
vec3 centre = viewPositionAt(screenUv, tanHalfFov, aspect, near, far);
|
|
1184
|
+
// \u6CD5\u7EBF\u7531\u89C6\u7A7A\u95F4\u4F4D\u7F6E\u7684\u5C4F\u5E55\u5BFC\u6570\u6C42\u51FA\uFF1A\u51E0\u4F55\u6CA1\u6709\u6CD5\u7EBF\u5C5E\u6027\uFF0C\u4E5F\u4E0D\u9700\u8981\u3002
|
|
1185
|
+
vec3 viewNormal = normalize(cross(dFdx(centre), dFdy(centre)));
|
|
1186
|
+
float centreDepth = -centre.z;
|
|
1187
|
+
|
|
1188
|
+
// \u4E16\u754C\u534A\u5F84\u6362\u7B97\u6210\u50CF\u7D20\u534A\u5F84\uFF1A\u8D8A\u8FDC\u8D8A\u5C0F\uFF0C\u8FD1\u666F\u8FDC\u666F\u624D\u90FD\u6709\u610F\u4E49\u3002
|
|
1189
|
+
float pixelRadius = (u_aoRadius / max(1e-4, centreDepth * tanHalfFov)) * (u_viewport.y * 0.5);
|
|
1190
|
+
// \u4EA4\u9519\u68AF\u5EA6\u566A\u58F0\uFF08IGN\uFF09\u4EE3\u66FF sin \u54C8\u5E0C\uFF1A\u540C\u6837\u662F\u9010\u50CF\u7D20\u6270\u52A8\uFF0C\u4F46\u5B83\u5728\u76F8\u90BB\u50CF\u7D20\u95F4\u662F
|
|
1191
|
+
// \u6E10\u53D8\u800C\u975E\u8DF3\u53D8\uFF0C\u91C7\u6837\u7F3A\u53E3\u770B\u8D77\u6765\u50CF\u7EC6\u5BC6\u6296\u52A8\uFF0C\u4E0D\u50CF\u6912\u76D0\u566A\u70B9\u3002
|
|
1192
|
+
float rotation = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))) * 6.2831853;
|
|
1193
|
+
|
|
1194
|
+
// \u4E09\u5708\u53D6\u6837\uFF1A0.35 / 0.65 / 1.0 \u500D\u534A\u5F84\uFF0C\u6743\u91CD 1.0 / 0.7 / 0.45\uFF0C\u5206\u522B\u8D1F\u8D23
|
|
1195
|
+
// \u8D34\u5730\u63A5\u89E6\u3001\u7F1D\u9699\u4E2D\u90E8\u3001\u5927\u9762\u79EF\u5939\u89D2\u3002\u6837\u672C\u6309\u4E0E\u6CD5\u7EBF\u7684\u5939\u89D2\u52A0\u6743\u2014\u2014\u6B63\u5BF9\u7740\u7684\u5E73\u9762
|
|
1196
|
+
// \u4E0D\u8BE5\u88AB\u5468\u56F4\u540C\u6837\u5E73\u7684\u4E1C\u897F\u538B\u6697\uFF0C\u53EA\u6709"\u7ACB\u8D77\u6765\u6321\u4F4F\u6211"\u7684\u624D\u7B97\u6CD5\u3002
|
|
1197
|
+
float occlusion = 0.0;
|
|
1198
|
+
float weightSum = 0.0;
|
|
1199
|
+
for (int ring = 0; ring < 3; ring++) {
|
|
1200
|
+
// \u4E09\u5708\uFF1A\u8D34\u5730\u63A5\u89E6 / \u7F1D\u9699\u4E2D\u90E8 / \u5927\u9762\u79EF\u5939\u89D2\u3002\u6CA1\u6709\u6A21\u7CCA\u901A\u9053\u65F6\uFF0C\u9760\u5708\u5C42\u5206\u644A
|
|
1201
|
+
// \u91C7\u6837\u5BC6\u5EA6\u662F\u538B\u566A\u70B9\u6700\u76F4\u63A5\u7684\u529E\u6CD5\uFF08\u6A21\u7CCA\u8981\u989D\u5916\u7684 FBO \u4E0E\u5168\u5C4F\u901A\u9053\uFF09\u3002
|
|
1202
|
+
float ringScale = ring == 0 ? 0.35 : (ring == 1 ? 0.65 : 1.0);
|
|
1203
|
+
float ringWeight = ring == 0 ? 1.0 : (ring == 1 ? 0.7 : 0.45);
|
|
1204
|
+
for (int i = 0; i < 8; i++) {
|
|
1205
|
+
float angle = rotation + float(i) * 0.7853982 + float(ring) * 0.2618;
|
|
1206
|
+
vec2 offset = vec2(cos(angle), sin(angle)) * pixelRadius * ringScale;
|
|
1207
|
+
vec2 sampleUv = screenUv + offset / u_viewport;
|
|
1208
|
+
if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) continue;
|
|
1209
|
+
vec3 delta = viewPositionAt(sampleUv, tanHalfFov, aspect, near, far) - centre;
|
|
1210
|
+
float distance = length(delta);
|
|
1211
|
+
if (distance < 0.0001 || distance > u_aoRadius) continue;
|
|
1212
|
+
float alignment = dot(delta, viewNormal) / distance;
|
|
1213
|
+
if (alignment <= 0.15) continue;
|
|
1214
|
+
occlusion += ringWeight * alignment;
|
|
1215
|
+
weightSum += ringWeight;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (weightSum > 0.0) {
|
|
1219
|
+
occlusion /= weightSum;
|
|
1220
|
+
lit *= 1.0 - clamp(occlusion, 0.0, 1.0) * u_aoStrength;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// \u91D1\u5C5E\u6CA1\u6709\u6F2B\u53CD\u5C04\uFF1A\u5B83\u7684\u989C\u8272\u5168\u90E8\u6765\u81EA\u53CD\u5C04\u3002\u5148\u628A\u6F2B\u53CD\u5C04\u6309 metallic \u6536\u6389\uFF0C
|
|
1225
|
+
// \u518D\u7528\u53CD\u5C04\u9879\u8865\u56DE\u6765\uFF0C\u5426\u5219\u91D1\u5C5E\u548C\u5851\u6599\u5728\u540C\u4E00\u4E2A\u592A\u9633\u4E0B\u53EA\u662F"\u989C\u8272\u4E0D\u540C"\u3002
|
|
1226
|
+
{
|
|
1227
|
+
vec3 viewDirection = normalize(u_cameraPosition - v_worldPosition);
|
|
1228
|
+
vec3 halfVector = normalize(lightDirection + viewDirection);
|
|
1229
|
+
float metallic = clamp(surfaceMetallic, 0.0, 1.0);
|
|
1230
|
+
float roughness = clamp(surfaceRoughness, 0.04, 1.0);
|
|
1231
|
+
vec3 reflectance = mix(vec3(0.04), color, metallic);
|
|
1232
|
+
|
|
1233
|
+
float diffuseGain = 1.0 - metallic * 0.85;
|
|
1234
|
+
float facing = max(dot(normal, lightDirection), 0.0);
|
|
1235
|
+
|
|
1236
|
+
// \u9AD8\u5149\uFF1A\u7C97\u7CD9\u5EA6\u51B3\u5B9A\u53F6\u74E3\u5BBD\u5EA6\u4E0E\u5F3A\u5EA6\uFF08\u6307\u6570 96 \u2192 4\uFF0C\u589E\u76CA 1.0 \u2192 0.18\uFF09\u3002
|
|
1237
|
+
float shininess = mix(96.0, 4.0, roughness * roughness);
|
|
1238
|
+
float highlight = pow(max(dot(normal, halfVector), 0.0), shininess);
|
|
1239
|
+
float specularGain = mix(1.0, 0.18, roughness);
|
|
1240
|
+
|
|
1241
|
+
// \u73AF\u5883\u53CD\u5C04\uFF1A\u6309\u53CD\u5C04\u65B9\u5411\u53D6\u5929\u7A7A/\u5730\u9762\u8272\uFF0C\u63A0\u5C04\u89D2\u518D\u53E0\u4E00\u5C42 Fresnel\u3002
|
|
1242
|
+
// \u53EA\u770B\u6CD5\u7EBF\u7684\u8BDD\uFF0C\u5E73\u8EBA\u7684\u91D1\u5C5E\u4F1A\u548C\u7ACB\u7740\u7684\u91D1\u5C5E\u4E00\u4E2A\u989C\u8272\u2014\u2014\u73B0\u5B9E\u91CC\u524D\u8005\u53CD\u5C04\u5929\u9876\u3001
|
|
1243
|
+
// \u540E\u8005\u53CD\u5C04\u5730\u5E73\u7EBF\uFF0C\u6070\u6070\u662F\u91D1\u5C5E\u6700\u5BB9\u6613\u8FA8\u8BA4\u7684\u7279\u5F81\u3002
|
|
1244
|
+
vec3 reflected = reflect(-viewDirection, normal);
|
|
1245
|
+
float skyMix = clamp(reflected.y * 0.9 + 0.35, 0.0, 1.0);
|
|
1246
|
+
vec3 skyColor = mix(vec3(0.15, 0.16, 0.17), vec3(0.62, 0.72, 0.84), skyMix);
|
|
1247
|
+
float fresnel = pow(1.0 - clamp(dot(normal, viewDirection), 0.0, 1.0), 4.0);
|
|
1248
|
+
float envGain = (0.22 + 0.55 * fresnel) * mix(0.4, 1.0, metallic);
|
|
1249
|
+
|
|
1250
|
+
float litScale = diffuseGain;
|
|
1251
|
+
lit *= litScale;
|
|
1252
|
+
lit += reflectance * (highlight * specularGain * mix(0.35, 1.0, facing)
|
|
1253
|
+
+ skyColor * envGain);
|
|
1254
|
+
lit += u_emissive;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// \u9634\u5F71\uFF1A\u628A\u4E16\u754C\u5750\u6807\u6295\u5230\u5149\u6E90\u7A7A\u95F4\uFF0C\u4E0E\u6DF1\u5EA6\u56FE\u6BD4\u8F83\u3002\u53D6 3\xD73 \u91C7\u6837\u7684\u5E73\u5747\u505A\u8F7B\u5FAE\u67D4\u5316\u3002
|
|
1258
|
+
// shadowLit \u5728\u8C03\u8BD5\u901A\u9053\u91CC\u76F4\u63A5\u8F93\u51FA\uFF1A1 = \u53D7\u5149\uFF0C0 = \u88AB\u906E\u3002
|
|
1259
|
+
float shadowLit = 1.0;
|
|
1260
|
+
if (u_shadowEnabled > 0.5) {
|
|
1261
|
+
// \u91C7\u6837\u70B9\u6CBF\u6CD5\u7EBF\u63A8\u51FA 1.5 \u4E2A\u7EB9\u7D20\uFF1A\u5E73\u8EBA\u5728\u5149\u91CC\u7684\u9762\uFF08\u5C4B\u9876\u3001\u5730\u9762\uFF09\u5728\u6BCF\u4E2A\u7EB9\u7D20\u5185
|
|
1262
|
+
// \u672C\u8EAB\u5C31\u6709\u6DF1\u5EA6\u5DEE\uFF0C\u9760\u6DF1\u5EA6\u504F\u5DEE\u53BB\u8FFD\u8FD9\u4E2A\u5DEE\u53EA\u4F1A\u8BA9\u5F71\u5B50\u8131\u79BB\u7269\u4F53\uFF08peter-panning\uFF09\u3002
|
|
1263
|
+
// \u628A\u6BD4\u8F83\u70B9\u62AC\u79BB\u8868\u9762\u624D\u662F\u6B63\u89E3\uFF0C\u504F\u79FB\u91CF\u6309\u7EB9\u7D20\u6362\u7B97\uFF0C\u548C\u8D34\u56FE\u5206\u8FA8\u7387\u89E3\u8026\u3002
|
|
1264
|
+
vec3 shadowPosition = v_worldPosition + normal * u_shadowNormalOffset;
|
|
1265
|
+
vec4 lightSpace = u_lightViewProjection * vec4(shadowPosition, 1.0);
|
|
1266
|
+
vec3 projected = lightSpace.xyz / max(lightSpace.w, 1e-5) * 0.5 + 0.5;
|
|
1267
|
+
if (projected.x > 0.002 && projected.x < 0.998
|
|
1268
|
+
&& projected.y > 0.002 && projected.y < 0.998
|
|
1269
|
+
&& projected.z > 0.0 && projected.z < 1.0) {
|
|
1270
|
+
// \u89C6\u9525\u8D34\u5408\u573A\u666F\u540E\u6DF1\u5EA6\u8DE8\u5EA6\u53EA\u6709\u767E\u7C73\u91CF\u7EA7\uFF0C\u504F\u5DEE\u4E5F\u5C31\u53EF\u4EE5\u6309\u7EB9\u7406\u5355\u5143\u7ED9\u3002
|
|
1271
|
+
// \u9634\u5F71 pass \u53EA\u753B\u80CC\u9762\uFF08cull FRONT\uFF09\uFF0C\u53D7\u5149\u9762\u548C\u6DF1\u5EA6\u56FE\u5929\u7136\u9519\u5F00\uFF0C\u57FA\u6570\u53EF\u4EE5\u5F88\u5C0F\uFF1B
|
|
1272
|
+
// \u659C\u9762\u6700\u654F\u611F\uFF0C\u4ECD\u6309 N\xB7L \u653E\u5927\uFF08\u9634\u5F71\u75E4\u75AE\u4E0E\u9634\u5F71\u8131\u79BB\u4E4B\u95F4\u7684\u90A3\u6761\u7A84\u7F1D\uFF09\u3002
|
|
1273
|
+
float ndlShadow = max(dot(normal, lightDirection), 0.0);
|
|
1274
|
+
float bias = 0.0006 + 0.0012 * (1.0 - ndlShadow);
|
|
1275
|
+
float lit2 = 0.0;
|
|
1276
|
+
vec2 texel = vec2(1.0 / 2048.0);
|
|
1277
|
+
for (int dx = -1; dx <= 1; dx++) {
|
|
1278
|
+
for (int dy = -1; dy <= 1; dy++) {
|
|
1279
|
+
float depth = texture(u_shadowMap, projected.xy + vec2(float(dx), float(dy)) * texel).r;
|
|
1280
|
+
lit2 += projected.z - bias > depth ? 0.0 : 1.0;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
shadowLit = lit2 / 9.0;
|
|
1284
|
+
lit *= mix(0.68, 1.0, shadowLit);
|
|
1285
|
+
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
// \u8C03\u8BD5\u901A\u9053\uFF1A?shadowdebug=1 \u770B\u906E\u853D\u5224\u5B9A\uFF0C=2 \u770B\u6DF1\u5EA6\u56FE\u672C\u8EAB\u3002
|
|
1289
|
+
if (u_shadowDebug > 0.5) {
|
|
1290
|
+
if (u_shadowDebug < 1.5) {
|
|
1291
|
+
outputColor = vec4(shadowLit, 0.12, 0.12, 1.0);
|
|
1292
|
+
} else {
|
|
1293
|
+
float stored = texture(u_shadowMap, gl_FragCoord.xy / u_viewport).r;
|
|
1294
|
+
outputColor = vec4(vec3(stored), 1.0);
|
|
1295
|
+
}
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
// \u753B\u5E03\u6309 sRGB \u89E3\u91CA\u5199\u5165\u503C\uFF0C\u800C\u6750\u8D28\u662F\u7EBF\u6027\u7A7A\u95F4\u7684\u5206\u91CF\uFF0C\u5FC5\u987B\u505A\u4E00\u6B21\u7F16\u7801\u3002
|
|
1299
|
+
// \u4E0D\u505A\u7684\u8BDD\u7EBF\u6027 0.017 \u4F1A\u5F53\u4F5C sRGB 0.017 \u663E\u793A\uFF0C\u6DF1\u8272\u6750\u8D28\u5168\u90E8\u584C\u6210\u9ED1\u8272\u3002
|
|
1300
|
+
outputColor = vec4(
|
|
1301
|
+
pow(max(lit, vec3(0.0)), vec3(1.0 / 2.2)),
|
|
951
1302
|
u_color.a
|
|
952
1303
|
);
|
|
953
1304
|
}
|
|
@@ -963,7 +1314,37 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
963
1314
|
colorLocation;
|
|
964
1315
|
clipOffsetLocation;
|
|
965
1316
|
vertexColorLocation;
|
|
1317
|
+
directionalShadingLocation;
|
|
1318
|
+
waterShadingLocation;
|
|
1319
|
+
cameraPositionLocation;
|
|
1320
|
+
shadowMapLocation;
|
|
1321
|
+
lightMatrixLocation;
|
|
1322
|
+
shadowEnabledLocation;
|
|
1323
|
+
shadowNormalOffsetLocation;
|
|
1324
|
+
shadowDebugLocation;
|
|
966
1325
|
instancingLocation;
|
|
1326
|
+
pickModeLocation;
|
|
1327
|
+
clipPlaneLocation;
|
|
1328
|
+
baseColorMapLocation;
|
|
1329
|
+
useBaseColorMapLocation;
|
|
1330
|
+
uvScaleLocation;
|
|
1331
|
+
normalMapLocation;
|
|
1332
|
+
ormMapLocation;
|
|
1333
|
+
useOrmMapLocation;
|
|
1334
|
+
ormOcclusionLocation;
|
|
1335
|
+
ormMetallicRoughnessLocation;
|
|
1336
|
+
useNormalMapLocation;
|
|
1337
|
+
normalScaleLocation;
|
|
1338
|
+
metallicLocation;
|
|
1339
|
+
roughnessLocation;
|
|
1340
|
+
emissiveLocation;
|
|
1341
|
+
sceneDepthLocation;
|
|
1342
|
+
useSceneDepthLocation;
|
|
1343
|
+
projectionLocation;
|
|
1344
|
+
viewportLocation;
|
|
1345
|
+
clipRangeLocation;
|
|
1346
|
+
aoRadiusLocation;
|
|
1347
|
+
aoStrengthLocation;
|
|
967
1348
|
constructor(gl) {
|
|
968
1349
|
this.gl = gl;
|
|
969
1350
|
const program = this.createProgram(VERTEX_SOURCE, FRAGMENT_SOURCE);
|
|
@@ -971,8 +1352,38 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
971
1352
|
const colorLocation = gl.getUniformLocation(program, "u_color");
|
|
972
1353
|
const clipOffsetLocation = gl.getUniformLocation(program, "u_clipOffset");
|
|
973
1354
|
const vertexColorLocation = gl.getUniformLocation(program, "u_useVertexColor");
|
|
1355
|
+
const directionalShadingLocation = gl.getUniformLocation(program, "u_useDirectionalShading");
|
|
1356
|
+
const waterShadingLocation = gl.getUniformLocation(program, "u_waterShading");
|
|
1357
|
+
const cameraPositionLocation = gl.getUniformLocation(program, "u_cameraPosition");
|
|
974
1358
|
const instancingLocation = gl.getUniformLocation(program, "u_useInstancing");
|
|
975
|
-
|
|
1359
|
+
const pickModeLocation = gl.getUniformLocation(program, "u_pickMode");
|
|
1360
|
+
const clipPlaneLocation = gl.getUniformLocation(program, "u_clipPlane");
|
|
1361
|
+
const baseColorMapLocation = gl.getUniformLocation(program, "u_baseColorMap");
|
|
1362
|
+
const useBaseColorMapLocation = gl.getUniformLocation(program, "u_useBaseColorMap");
|
|
1363
|
+
const uvScaleLocation = gl.getUniformLocation(program, "u_uvScale");
|
|
1364
|
+
const normalMapLocation = gl.getUniformLocation(program, "u_normalMap");
|
|
1365
|
+
const ormMapLocation = gl.getUniformLocation(program, "u_ormMap");
|
|
1366
|
+
const useOrmMapLocation = gl.getUniformLocation(program, "u_useOrmMap");
|
|
1367
|
+
const ormOcclusionLocation = gl.getUniformLocation(program, "u_ormOcclusion");
|
|
1368
|
+
const ormMetallicRoughnessLocation = gl.getUniformLocation(program, "u_ormMetallicRoughness");
|
|
1369
|
+
const useNormalMapLocation = gl.getUniformLocation(program, "u_useNormalMap");
|
|
1370
|
+
const normalScaleLocation = gl.getUniformLocation(program, "u_normalScale");
|
|
1371
|
+
const metallicLocation = gl.getUniformLocation(program, "u_metallic");
|
|
1372
|
+
const roughnessLocation = gl.getUniformLocation(program, "u_roughness");
|
|
1373
|
+
const emissiveLocation = gl.getUniformLocation(program, "u_emissive");
|
|
1374
|
+
const sceneDepthLocation = gl.getUniformLocation(program, "u_sceneDepth");
|
|
1375
|
+
const useSceneDepthLocation = gl.getUniformLocation(program, "u_useSceneDepth");
|
|
1376
|
+
const projectionLocation = gl.getUniformLocation(program, "u_projection");
|
|
1377
|
+
const viewportLocation = gl.getUniformLocation(program, "u_viewport");
|
|
1378
|
+
const clipRangeLocation = gl.getUniformLocation(program, "u_clipRange");
|
|
1379
|
+
const aoRadiusLocation = gl.getUniformLocation(program, "u_aoRadius");
|
|
1380
|
+
const aoStrengthLocation = gl.getUniformLocation(program, "u_aoStrength");
|
|
1381
|
+
const shadowMapLocation = gl.getUniformLocation(program, "u_shadowMap");
|
|
1382
|
+
const lightMatrixLocation = gl.getUniformLocation(program, "u_lightViewProjection");
|
|
1383
|
+
const shadowEnabledLocation = gl.getUniformLocation(program, "u_shadowEnabled");
|
|
1384
|
+
const shadowNormalOffsetLocation = gl.getUniformLocation(program, "u_shadowNormalOffset");
|
|
1385
|
+
const shadowDebugLocation = gl.getUniformLocation(program, "u_shadowDebug");
|
|
1386
|
+
if (!matrixLocation || !colorLocation || !clipOffsetLocation || !vertexColorLocation || !directionalShadingLocation || !waterShadingLocation || !cameraPositionLocation || !instancingLocation || !pickModeLocation) {
|
|
976
1387
|
gl.deleteProgram(program);
|
|
977
1388
|
throw new ICEEngineError("ICE3_SHADER_LINK_FAILED", "ICE3_SHADER_LINK_FAILED: shader uniforms are missing");
|
|
978
1389
|
}
|
|
@@ -981,7 +1392,81 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
981
1392
|
this.colorLocation = colorLocation;
|
|
982
1393
|
this.clipOffsetLocation = clipOffsetLocation;
|
|
983
1394
|
this.vertexColorLocation = vertexColorLocation;
|
|
1395
|
+
this.directionalShadingLocation = directionalShadingLocation;
|
|
1396
|
+
this.waterShadingLocation = waterShadingLocation;
|
|
1397
|
+
this.cameraPositionLocation = cameraPositionLocation;
|
|
1398
|
+
this.shadowMapLocation = shadowMapLocation;
|
|
1399
|
+
this.lightMatrixLocation = lightMatrixLocation;
|
|
1400
|
+
this.shadowEnabledLocation = shadowEnabledLocation;
|
|
1401
|
+
this.shadowNormalOffsetLocation = shadowNormalOffsetLocation;
|
|
1402
|
+
this.shadowDebugLocation = shadowDebugLocation;
|
|
984
1403
|
this.instancingLocation = instancingLocation;
|
|
1404
|
+
this.pickModeLocation = pickModeLocation;
|
|
1405
|
+
this.clipPlaneLocation = clipPlaneLocation;
|
|
1406
|
+
this.baseColorMapLocation = baseColorMapLocation;
|
|
1407
|
+
this.useBaseColorMapLocation = useBaseColorMapLocation;
|
|
1408
|
+
this.uvScaleLocation = uvScaleLocation;
|
|
1409
|
+
this.normalMapLocation = normalMapLocation;
|
|
1410
|
+
this.ormMapLocation = ormMapLocation;
|
|
1411
|
+
this.useOrmMapLocation = useOrmMapLocation;
|
|
1412
|
+
this.ormOcclusionLocation = ormOcclusionLocation;
|
|
1413
|
+
this.ormMetallicRoughnessLocation = ormMetallicRoughnessLocation;
|
|
1414
|
+
this.useNormalMapLocation = useNormalMapLocation;
|
|
1415
|
+
this.normalScaleLocation = normalScaleLocation;
|
|
1416
|
+
this.metallicLocation = metallicLocation;
|
|
1417
|
+
this.roughnessLocation = roughnessLocation;
|
|
1418
|
+
this.emissiveLocation = emissiveLocation;
|
|
1419
|
+
this.sceneDepthLocation = sceneDepthLocation;
|
|
1420
|
+
this.useSceneDepthLocation = useSceneDepthLocation;
|
|
1421
|
+
this.projectionLocation = projectionLocation;
|
|
1422
|
+
this.viewportLocation = viewportLocation;
|
|
1423
|
+
this.clipRangeLocation = clipRangeLocation;
|
|
1424
|
+
this.aoRadiusLocation = aoRadiusLocation;
|
|
1425
|
+
this.aoStrengthLocation = aoStrengthLocation;
|
|
1426
|
+
if (sceneDepthLocation) {
|
|
1427
|
+
gl.useProgram(program);
|
|
1428
|
+
gl.uniform1i(sceneDepthLocation, 2);
|
|
1429
|
+
}
|
|
1430
|
+
if (useSceneDepthLocation) {
|
|
1431
|
+
gl.useProgram(program);
|
|
1432
|
+
gl.uniform1f(useSceneDepthLocation, 0);
|
|
1433
|
+
}
|
|
1434
|
+
if (aoRadiusLocation) {
|
|
1435
|
+
gl.useProgram(program);
|
|
1436
|
+
gl.uniform1f(aoRadiusLocation, 2.5);
|
|
1437
|
+
}
|
|
1438
|
+
if (emissiveLocation) {
|
|
1439
|
+
gl.useProgram(program);
|
|
1440
|
+
gl.uniform3f(emissiveLocation, 0, 0, 0);
|
|
1441
|
+
}
|
|
1442
|
+
if (normalMapLocation) {
|
|
1443
|
+
gl.useProgram(program);
|
|
1444
|
+
gl.uniform1i(normalMapLocation, 1);
|
|
1445
|
+
}
|
|
1446
|
+
if (ormMapLocation) {
|
|
1447
|
+
gl.useProgram(program);
|
|
1448
|
+
gl.uniform1i(ormMapLocation, 4);
|
|
1449
|
+
}
|
|
1450
|
+
if (useNormalMapLocation) {
|
|
1451
|
+
gl.useProgram(program);
|
|
1452
|
+
gl.uniform1f(useNormalMapLocation, 0);
|
|
1453
|
+
}
|
|
1454
|
+
if (baseColorMapLocation) {
|
|
1455
|
+
gl.useProgram(program);
|
|
1456
|
+
gl.uniform1i(baseColorMapLocation, 0);
|
|
1457
|
+
}
|
|
1458
|
+
if (useBaseColorMapLocation) {
|
|
1459
|
+
gl.useProgram(program);
|
|
1460
|
+
gl.uniform1f(useBaseColorMapLocation, 0);
|
|
1461
|
+
}
|
|
1462
|
+
if (clipPlaneLocation) {
|
|
1463
|
+
gl.useProgram(program);
|
|
1464
|
+
gl.uniform4f(clipPlaneLocation, 0, 0, 0, 0);
|
|
1465
|
+
}
|
|
1466
|
+
if (shadowMapLocation) {
|
|
1467
|
+
gl.useProgram(program);
|
|
1468
|
+
gl.uniform1i(shadowMapLocation, 3);
|
|
1469
|
+
}
|
|
985
1470
|
}
|
|
986
1471
|
use() {
|
|
987
1472
|
this.gl.useProgram(this.program);
|
|
@@ -992,15 +1477,156 @@ var WebGL2Program = class _WebGL2Program {
|
|
|
992
1477
|
setColor(color) {
|
|
993
1478
|
this.gl.uniform4f(this.colorLocation, ...color);
|
|
994
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Bind a base-colour texture to unit 0 and enable sampling. Passing `null`
|
|
1482
|
+
* restores flat shading without touching the bound texture.
|
|
1483
|
+
*/
|
|
1484
|
+
setBaseColorMap(texture, scale) {
|
|
1485
|
+
const gl = this.gl;
|
|
1486
|
+
if (!this.useBaseColorMapLocation)
|
|
1487
|
+
return;
|
|
1488
|
+
if (!texture) {
|
|
1489
|
+
gl.uniform1f(this.useBaseColorMapLocation, 0);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1493
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1494
|
+
gl.uniform1f(this.useBaseColorMapLocation, 1);
|
|
1495
|
+
if (this.uvScaleLocation) {
|
|
1496
|
+
gl.uniform2f(this.uvScaleLocation, scale[0], scale[1]);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/** Bind a tangent-space normal map to unit 1; `null` disables perturbation. */
|
|
1500
|
+
setNormalMap(texture, strength) {
|
|
1501
|
+
const gl = this.gl;
|
|
1502
|
+
if (!this.useNormalMapLocation)
|
|
1503
|
+
return;
|
|
1504
|
+
if (!texture) {
|
|
1505
|
+
gl.uniform1f(this.useNormalMapLocation, 0);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
1509
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1510
|
+
gl.uniform1f(this.useNormalMapLocation, 1);
|
|
1511
|
+
if (this.normalScaleLocation) {
|
|
1512
|
+
gl.uniform1f(this.normalScaleLocation, strength);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Bind the packed ORM map. `occlusion` / `metallicRoughness` describe which
|
|
1517
|
+
* halves of the image are meaningful; both false disables sampling.
|
|
1518
|
+
*/
|
|
1519
|
+
setOrmMap(texture, occlusion, metallicRoughness) {
|
|
1520
|
+
const gl = this.gl;
|
|
1521
|
+
if (!this.useOrmMapLocation)
|
|
1522
|
+
return;
|
|
1523
|
+
if (!texture || !occlusion && !metallicRoughness) {
|
|
1524
|
+
gl.uniform1f(this.useOrmMapLocation, 0);
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
gl.activeTexture(gl.TEXTURE4);
|
|
1528
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1529
|
+
gl.uniform1f(this.useOrmMapLocation, 1);
|
|
1530
|
+
if (this.ormOcclusionLocation) {
|
|
1531
|
+
gl.uniform1f(this.ormOcclusionLocation, occlusion ? 1 : 0);
|
|
1532
|
+
}
|
|
1533
|
+
if (this.ormMetallicRoughnessLocation) {
|
|
1534
|
+
gl.uniform1f(this.ormMetallicRoughnessLocation, metallicRoughness ? 1 : 0);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
/** Metallic-roughness factors plus emission for the current draw. */
|
|
1538
|
+
setSurface(metallic, roughness, emissive) {
|
|
1539
|
+
const gl = this.gl;
|
|
1540
|
+
if (this.metallicLocation)
|
|
1541
|
+
gl.uniform1f(this.metallicLocation, metallic);
|
|
1542
|
+
if (this.roughnessLocation)
|
|
1543
|
+
gl.uniform1f(this.roughnessLocation, roughness);
|
|
1544
|
+
if (this.emissiveLocation) {
|
|
1545
|
+
if (emissive)
|
|
1546
|
+
gl.uniform3f(this.emissiveLocation, emissive[0], emissive[1], emissive[2]);
|
|
1547
|
+
else
|
|
1548
|
+
gl.uniform3f(this.emissiveLocation, 0, 0, 0);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
/** 绑定相机深度纹理(unit 2)并传入重建视空间所需的相机参数。 */
|
|
1552
|
+
setSceneDepth(texture, projection, near, far, deviceWidth, deviceHeight) {
|
|
1553
|
+
const gl = this.gl;
|
|
1554
|
+
if (!this.useSceneDepthLocation)
|
|
1555
|
+
return;
|
|
1556
|
+
if (!texture || !projection) {
|
|
1557
|
+
gl.uniform1f(this.useSceneDepthLocation, 0);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
1561
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1562
|
+
gl.uniform1f(this.useSceneDepthLocation, 1);
|
|
1563
|
+
if (this.projectionLocation) {
|
|
1564
|
+
gl.uniformMatrix4fv(this.projectionLocation, false, projection.elements);
|
|
1565
|
+
}
|
|
1566
|
+
if (this.viewportLocation) {
|
|
1567
|
+
gl.uniform2f(this.viewportLocation, deviceWidth, deviceHeight);
|
|
1568
|
+
}
|
|
1569
|
+
if (this.clipRangeLocation) {
|
|
1570
|
+
gl.uniform2f(this.clipRangeLocation, near, far);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
/** 遮蔽半径(米)与强度,供运行时调节。强度 0 等于关闭。 */
|
|
1574
|
+
setOcclusion(radius, strength) {
|
|
1575
|
+
const gl = this.gl;
|
|
1576
|
+
if (this.aoRadiusLocation)
|
|
1577
|
+
gl.uniform1f(this.aoRadiusLocation, radius);
|
|
1578
|
+
if (this.aoStrengthLocation)
|
|
1579
|
+
gl.uniform1f(this.aoStrengthLocation, strength);
|
|
1580
|
+
}
|
|
995
1581
|
setClipOffset(x, y) {
|
|
996
1582
|
this.gl.uniform2f(this.clipOffsetLocation, x, y);
|
|
997
1583
|
}
|
|
1584
|
+
setClipPlane(plane) {
|
|
1585
|
+
if (!this.clipPlaneLocation)
|
|
1586
|
+
return;
|
|
1587
|
+
this.gl.uniform4f(this.clipPlaneLocation, plane[0], plane[1], plane[2], plane[3]);
|
|
1588
|
+
}
|
|
998
1589
|
setVertexColors(enabled) {
|
|
999
1590
|
this.gl.uniform1f(this.vertexColorLocation, enabled ? 1 : 0);
|
|
1000
1591
|
}
|
|
1592
|
+
/** 绑定阴影贴图与光源矩阵;关闭时着色器跳过采样。 */
|
|
1593
|
+
setShadow(enabled, texture, matrix, normalOffset = 0, unit = 3) {
|
|
1594
|
+
const gl = this.gl;
|
|
1595
|
+
gl.uniform1f(this.shadowEnabledLocation, enabled && texture && matrix ? 1 : 0);
|
|
1596
|
+
if (!enabled || !texture || !matrix)
|
|
1597
|
+
return;
|
|
1598
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
1599
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1600
|
+
if (this.shadowMapLocation)
|
|
1601
|
+
gl.uniform1i(this.shadowMapLocation, unit);
|
|
1602
|
+
if (this.lightMatrixLocation)
|
|
1603
|
+
gl.uniformMatrix4fv(this.lightMatrixLocation, false, matrix);
|
|
1604
|
+
if (this.shadowNormalOffsetLocation) {
|
|
1605
|
+
gl.uniform1f(this.shadowNormalOffsetLocation, normalOffset);
|
|
1606
|
+
}
|
|
1607
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1608
|
+
}
|
|
1609
|
+
/** 阴影调试通道:0 关闭,1 输出遮蔽判定,2 输出深度图。 */
|
|
1610
|
+
setShadowDebug(mode) {
|
|
1611
|
+
if (!this.shadowDebugLocation)
|
|
1612
|
+
return;
|
|
1613
|
+
this.gl.uniform1f(this.shadowDebugLocation, mode);
|
|
1614
|
+
}
|
|
1615
|
+
setDirectionalShading(enabled) {
|
|
1616
|
+
this.gl.uniform1f(this.directionalShadingLocation, enabled ? 1 : 0);
|
|
1617
|
+
}
|
|
1618
|
+
setWaterShading(enabled) {
|
|
1619
|
+
this.gl.uniform1f(this.waterShadingLocation, enabled ? 1 : 0);
|
|
1620
|
+
}
|
|
1621
|
+
setCameraPosition(position) {
|
|
1622
|
+
this.gl.uniform3f(this.cameraPositionLocation, ...position);
|
|
1623
|
+
}
|
|
1001
1624
|
setInstancing(enabled) {
|
|
1002
1625
|
this.gl.uniform1f(this.instancingLocation, enabled ? 1 : 0);
|
|
1003
1626
|
}
|
|
1627
|
+
setPickMode(enabled) {
|
|
1628
|
+
this.gl.uniform1f(this.pickModeLocation, enabled ? 1 : 0);
|
|
1629
|
+
}
|
|
1004
1630
|
dispose() {
|
|
1005
1631
|
this.gl.deleteProgram(this.program);
|
|
1006
1632
|
}
|
|
@@ -1077,12 +1703,24 @@ function collectInstanceBatches(renderables, output = []) {
|
|
|
1077
1703
|
semanticIds: group.map((renderable) => renderable.mesh.selectionId),
|
|
1078
1704
|
semanticIndices,
|
|
1079
1705
|
meshIds: group.map((renderable) => renderable.id),
|
|
1080
|
-
materialKey:
|
|
1706
|
+
materialKey: [
|
|
1707
|
+
material.color,
|
|
1708
|
+
material.edgeColor ?? "",
|
|
1709
|
+
material.shading ?? ""
|
|
1710
|
+
].join(":")
|
|
1081
1711
|
});
|
|
1082
1712
|
}
|
|
1083
1713
|
}
|
|
1084
1714
|
return output;
|
|
1085
1715
|
}
|
|
1716
|
+
function renderablesOutsideInstanceBatches(renderables, batches) {
|
|
1717
|
+
const batched = /* @__PURE__ */ new Set();
|
|
1718
|
+
for (const batch of batches) {
|
|
1719
|
+
for (const id of batch.meshIds)
|
|
1720
|
+
batched.add(id);
|
|
1721
|
+
}
|
|
1722
|
+
return renderables.filter((renderable) => !batched.has(renderable.id));
|
|
1723
|
+
}
|
|
1086
1724
|
|
|
1087
1725
|
// packages/renderer/dist/WebGL2InstanceBatch.js
|
|
1088
1726
|
var WebGL2InstanceCache = class {
|
|
@@ -1127,10 +1765,10 @@ var WebGL2InstanceCache = class {
|
|
|
1127
1765
|
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
|
|
1128
1766
|
gl.bufferData(gl.ARRAY_BUFFER, batch.instanceMatrices, gl.DYNAMIC_DRAW);
|
|
1129
1767
|
for (let column = 0; column < 4; column += 1) {
|
|
1130
|
-
const
|
|
1131
|
-
gl.enableVertexAttribArray(
|
|
1132
|
-
gl.vertexAttribPointer(
|
|
1133
|
-
gl.vertexAttribDivisor(
|
|
1768
|
+
const location2 = WebGL2Program.INSTANCE_MATRIX_ATTRIBUTE + column;
|
|
1769
|
+
gl.enableVertexAttribArray(location2);
|
|
1770
|
+
gl.vertexAttribPointer(location2, 4, gl.FLOAT, false, 64, column * 16);
|
|
1771
|
+
gl.vertexAttribDivisor(location2, 1);
|
|
1134
1772
|
}
|
|
1135
1773
|
gl.bindBuffer(gl.ARRAY_BUFFER, semanticBuffer);
|
|
1136
1774
|
gl.bufferData(gl.ARRAY_BUFFER, batch.semanticIndices, gl.DYNAMIC_DRAW);
|
|
@@ -1199,8 +1837,12 @@ var WebGL2GeometryCache = class {
|
|
|
1199
1837
|
entries = /* @__PURE__ */ new Map();
|
|
1200
1838
|
active = /* @__PURE__ */ new Set();
|
|
1201
1839
|
pinned = /* @__PURE__ */ new Set();
|
|
1840
|
+
objectKeys = /* @__PURE__ */ new WeakMap();
|
|
1841
|
+
legacyKeys = /* @__PURE__ */ new WeakMap();
|
|
1842
|
+
objectKeySequence = 0;
|
|
1202
1843
|
gl = null;
|
|
1203
1844
|
uploads = 0;
|
|
1845
|
+
updatedBytes = 0;
|
|
1204
1846
|
evictions = 0;
|
|
1205
1847
|
frameId = 0;
|
|
1206
1848
|
inactiveGraceFrames;
|
|
@@ -1214,12 +1856,24 @@ var WebGL2GeometryCache = class {
|
|
|
1214
1856
|
}
|
|
1215
1857
|
retain(gl, geometry) {
|
|
1216
1858
|
this.gl = gl;
|
|
1217
|
-
this.
|
|
1218
|
-
|
|
1859
|
+
const key = this.keyFor(geometry);
|
|
1860
|
+
this.active.add(key);
|
|
1861
|
+
const existing = this.entries.get(key);
|
|
1219
1862
|
if (existing) {
|
|
1220
|
-
existing
|
|
1221
|
-
|
|
1863
|
+
if (this.canReuse(existing, geometry)) {
|
|
1864
|
+
existing.lastUsedFrame = this.frameId;
|
|
1865
|
+
return existing;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
const next = this.createEntry(gl, geometry, key);
|
|
1869
|
+
if (existing) {
|
|
1870
|
+
this.deleteEntry(existing, false);
|
|
1222
1871
|
}
|
|
1872
|
+
this.entries.set(key, next);
|
|
1873
|
+
this.uploads += 1;
|
|
1874
|
+
return next;
|
|
1875
|
+
}
|
|
1876
|
+
createEntry(gl, geometry, key) {
|
|
1223
1877
|
const vertexArray = gl.createVertexArray();
|
|
1224
1878
|
const positionBuffer = gl.createBuffer();
|
|
1225
1879
|
const triangleIndexBuffer = gl.createBuffer();
|
|
@@ -1260,6 +1914,10 @@ var WebGL2GeometryCache = class {
|
|
|
1260
1914
|
}
|
|
1261
1915
|
gl.bindVertexArray(null);
|
|
1262
1916
|
const entry = {
|
|
1917
|
+
geometry,
|
|
1918
|
+
geometryId: geometry.identity?.geometryId ?? key,
|
|
1919
|
+
attributeDigest: geometry.identity?.attributeDigest ?? "",
|
|
1920
|
+
topologyDigest: geometry.identity?.topologyDigest ?? "",
|
|
1263
1921
|
vertexArray,
|
|
1264
1922
|
positionBuffer,
|
|
1265
1923
|
triangleIndexBuffer,
|
|
@@ -1271,28 +1929,33 @@ var WebGL2GeometryCache = class {
|
|
|
1271
1929
|
bytes: geometry.positions.byteLength + geometry.indices.byteLength + (geometry.edgeIndices?.byteLength ?? 0) + (geometry.colors?.byteLength ?? 0) + (geometry.semanticIndices?.byteLength ?? 0),
|
|
1272
1930
|
lastUsedFrame: this.frameId
|
|
1273
1931
|
};
|
|
1274
|
-
this.entries.set(geometry, entry);
|
|
1275
|
-
this.uploads += 1;
|
|
1276
1932
|
return entry;
|
|
1277
1933
|
}
|
|
1278
1934
|
beginFrame() {
|
|
1279
1935
|
this.frameId += 1;
|
|
1280
1936
|
this.active = /* @__PURE__ */ new Set();
|
|
1281
1937
|
}
|
|
1282
|
-
pin(gl, geometry) {
|
|
1283
|
-
this.
|
|
1938
|
+
pin(gl, geometry, update) {
|
|
1939
|
+
const key = this.keyFor(geometry);
|
|
1940
|
+
this.pinned.add(key);
|
|
1941
|
+
const existing = this.entries.get(key);
|
|
1942
|
+
if (existing && update && this.sameTopology(existing, geometry)) {
|
|
1943
|
+
this.gl = gl;
|
|
1944
|
+
this.applyPositionUpdate(gl, existing, geometry, update);
|
|
1945
|
+
return existing;
|
|
1946
|
+
}
|
|
1284
1947
|
const entry = this.retain(gl, geometry);
|
|
1285
1948
|
this.enforceBudget();
|
|
1286
1949
|
if (this.totalBytes > this.maxBytes) {
|
|
1287
|
-
this.pinned.delete(
|
|
1288
|
-
this.deleteEntry(entry);
|
|
1289
|
-
this.entries.delete(
|
|
1950
|
+
this.pinned.delete(key);
|
|
1951
|
+
this.deleteEntry(entry, false);
|
|
1952
|
+
this.entries.delete(key);
|
|
1290
1953
|
throw new Error("ICE3_GPU_RESOURCE_FAILED: geometry exceeds the GPU residency budget");
|
|
1291
1954
|
}
|
|
1292
1955
|
return entry;
|
|
1293
1956
|
}
|
|
1294
1957
|
unpin(geometry) {
|
|
1295
|
-
this.pinned.delete(geometry);
|
|
1958
|
+
this.pinned.delete(this.keyFor(geometry));
|
|
1296
1959
|
}
|
|
1297
1960
|
endFrame() {
|
|
1298
1961
|
for (const [geometry, entry] of this.entries) {
|
|
@@ -1327,10 +1990,13 @@ var WebGL2GeometryCache = class {
|
|
|
1327
1990
|
get uploadCount() {
|
|
1328
1991
|
return this.uploads;
|
|
1329
1992
|
}
|
|
1993
|
+
get updateBytes() {
|
|
1994
|
+
return this.updatedBytes;
|
|
1995
|
+
}
|
|
1330
1996
|
get evictionCount() {
|
|
1331
1997
|
return this.evictions;
|
|
1332
1998
|
}
|
|
1333
|
-
deleteEntry(entry) {
|
|
1999
|
+
deleteEntry(entry, eviction = true) {
|
|
1334
2000
|
if (!this.gl)
|
|
1335
2001
|
return;
|
|
1336
2002
|
this.gl.deleteBuffer(entry.positionBuffer);
|
|
@@ -1339,7 +2005,9 @@ var WebGL2GeometryCache = class {
|
|
|
1339
2005
|
this.gl.deleteBuffer(entry.colorBuffer);
|
|
1340
2006
|
this.gl.deleteBuffer(entry.semanticBuffer);
|
|
1341
2007
|
this.gl.deleteVertexArray(entry.vertexArray);
|
|
1342
|
-
|
|
2008
|
+
if (eviction) {
|
|
2009
|
+
this.evictions += 1;
|
|
2010
|
+
}
|
|
1343
2011
|
}
|
|
1344
2012
|
enforceBudget() {
|
|
1345
2013
|
let total = this.totalBytes;
|
|
@@ -1354,7 +2022,96 @@ var WebGL2GeometryCache = class {
|
|
|
1354
2022
|
total -= entry.bytes;
|
|
1355
2023
|
}
|
|
1356
2024
|
}
|
|
2025
|
+
applyPositionUpdate(gl, entry, geometry, update) {
|
|
2026
|
+
let updated = 0;
|
|
2027
|
+
try {
|
|
2028
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, entry.positionBuffer);
|
|
2029
|
+
for (const range of update.positionRanges) {
|
|
2030
|
+
if (range.byteOffset % 4 !== 0 || range.byteLength % 4 !== 0 || range.byteOffset + range.byteLength > geometry.positions.byteLength) {
|
|
2031
|
+
throw new Error("invalid position range");
|
|
2032
|
+
}
|
|
2033
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, range.byteOffset, new Uint8Array(geometry.positions.buffer, geometry.positions.byteOffset + range.byteOffset, range.byteLength));
|
|
2034
|
+
updated += range.byteLength;
|
|
2035
|
+
}
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
throw new Error("ICE3_GPU_RESOURCE_FAILED: geometry update failed", {
|
|
2038
|
+
cause: error
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
entry.geometry = geometry;
|
|
2042
|
+
entry.lastUsedFrame = this.frameId;
|
|
2043
|
+
this.updatedBytes += updated;
|
|
2044
|
+
}
|
|
2045
|
+
canReuse(existing, geometry) {
|
|
2046
|
+
if (existing.geometry === geometry) {
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2049
|
+
if (!this.sameTopology(existing, geometry)) {
|
|
2050
|
+
return false;
|
|
2051
|
+
}
|
|
2052
|
+
const existingDigest = existing.attributeDigest;
|
|
2053
|
+
const nextDigest = geometry.identity?.attributeDigest ?? "";
|
|
2054
|
+
return (existing.geometry.positions === geometry.positions || equalNumberArrays(existing.geometry.positions, geometry.positions)) && equalOptionalNumberArrays(existing.geometry.colors, geometry.colors) && equalOptionalNumberArrays(existing.geometry.semanticIndices, geometry.semanticIndices) && equalOptionalNumberArrays(existing.geometry.edgeIndices, geometry.edgeIndices) || existingDigest !== "" && existingDigest === nextDigest;
|
|
2055
|
+
}
|
|
2056
|
+
sameTopology(existing, geometry) {
|
|
2057
|
+
const existingDigest = existing.topologyDigest;
|
|
2058
|
+
const nextDigest = geometry.identity?.topologyDigest ?? "";
|
|
2059
|
+
return existing.geometry.indices.constructor === geometry.indices.constructor && existing.geometry.indices.length === geometry.indices.length && (existing.geometry.indices === geometry.indices || existingDigest !== "" && existingDigest === nextDigest || equalTypedArrays(existing.geometry.indices, geometry.indices));
|
|
2060
|
+
}
|
|
2061
|
+
keyFor(geometry) {
|
|
2062
|
+
const identity = geometry.identity?.geometryId;
|
|
2063
|
+
if (identity) {
|
|
2064
|
+
return `identity:${identity}`;
|
|
2065
|
+
}
|
|
2066
|
+
const id = geometry.id;
|
|
2067
|
+
if (typeof id === "string" && id.trim()) {
|
|
2068
|
+
const cached = this.legacyKeys.get(geometry);
|
|
2069
|
+
if (cached)
|
|
2070
|
+
return cached;
|
|
2071
|
+
const key2 = [
|
|
2072
|
+
"content",
|
|
2073
|
+
geometry.positions.length,
|
|
2074
|
+
geometry.indices.length,
|
|
2075
|
+
contentFingerprint(geometry.positions),
|
|
2076
|
+
contentFingerprint(geometry.indices),
|
|
2077
|
+
geometry.colors ? contentFingerprint(geometry.colors) : 0,
|
|
2078
|
+
geometry.edgeIndices ? contentFingerprint(geometry.edgeIndices) : 0,
|
|
2079
|
+
geometry.semanticIndices ? contentFingerprint(geometry.semanticIndices) : 0
|
|
2080
|
+
].join(":");
|
|
2081
|
+
this.legacyKeys.set(geometry, key2);
|
|
2082
|
+
return key2;
|
|
2083
|
+
}
|
|
2084
|
+
const existing = this.objectKeys.get(geometry);
|
|
2085
|
+
if (existing) {
|
|
2086
|
+
return existing;
|
|
2087
|
+
}
|
|
2088
|
+
const key = `object:${this.objectKeySequence}`;
|
|
2089
|
+
this.objectKeySequence += 1;
|
|
2090
|
+
this.objectKeys.set(geometry, key);
|
|
2091
|
+
return key;
|
|
2092
|
+
}
|
|
1357
2093
|
};
|
|
2094
|
+
function equalTypedArrays(left, right) {
|
|
2095
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
2096
|
+
}
|
|
2097
|
+
function equalNumberArrays(left, right) {
|
|
2098
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
2099
|
+
}
|
|
2100
|
+
function equalOptionalNumberArrays(left, right) {
|
|
2101
|
+
if (left === void 0 || right === void 0) {
|
|
2102
|
+
return left === right;
|
|
2103
|
+
}
|
|
2104
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
2105
|
+
}
|
|
2106
|
+
function contentFingerprint(values) {
|
|
2107
|
+
let hash = 2166136261;
|
|
2108
|
+
const bytes = new Uint8Array(values.buffer, values.byteOffset, values.byteLength);
|
|
2109
|
+
for (const byte of bytes) {
|
|
2110
|
+
hash ^= byte;
|
|
2111
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
2112
|
+
}
|
|
2113
|
+
return hash;
|
|
2114
|
+
}
|
|
1358
2115
|
|
|
1359
2116
|
// packages/renderer/dist/WebGL2PickCodec.js
|
|
1360
2117
|
var MAX_SELECTION_INDEX = 16777215;
|
|
@@ -1561,6 +2318,7 @@ var WebGL2Picker = class {
|
|
|
1561
2318
|
gl.depthFunc(gl.LEQUAL);
|
|
1562
2319
|
gl.disable(gl.BLEND);
|
|
1563
2320
|
this.program.use();
|
|
2321
|
+
this.program.setPickMode(true);
|
|
1564
2322
|
this.program.setClipOffset(0, 0);
|
|
1565
2323
|
this.program.setInstancing(false);
|
|
1566
2324
|
const aspect = frame.viewport.width / Math.max(frame.viewport.height, 1);
|
|
@@ -1706,13 +2464,529 @@ var WebGL2Picker = class {
|
|
|
1706
2464
|
}
|
|
1707
2465
|
};
|
|
1708
2466
|
|
|
2467
|
+
// packages/renderer/dist/WebGL2Shader.js
|
|
2468
|
+
function createShaderProgram(gl, vertexSource, fragmentSource, label) {
|
|
2469
|
+
const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource, `${label}:vertex`);
|
|
2470
|
+
const fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource, `${label}:fragment`);
|
|
2471
|
+
const program = gl.createProgram();
|
|
2472
|
+
if (!program) {
|
|
2473
|
+
throw new ICEEngineError("ICE3_SHADER_LINK_FAILED", `ICE3_SHADER_LINK_FAILED: ${label}: program allocation failed`);
|
|
2474
|
+
}
|
|
2475
|
+
gl.attachShader(program, vertex);
|
|
2476
|
+
gl.attachShader(program, fragment);
|
|
2477
|
+
gl.linkProgram(program);
|
|
2478
|
+
gl.deleteShader(vertex);
|
|
2479
|
+
gl.deleteShader(fragment);
|
|
2480
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
2481
|
+
const log = gl.getProgramInfoLog(program) ?? "unknown link error";
|
|
2482
|
+
gl.deleteProgram(program);
|
|
2483
|
+
throw new ICEEngineError("ICE3_SHADER_LINK_FAILED", `ICE3_SHADER_LINK_FAILED: ${label}: ${log}`);
|
|
2484
|
+
}
|
|
2485
|
+
return program;
|
|
2486
|
+
}
|
|
2487
|
+
function compileShader(gl, type, source, label) {
|
|
2488
|
+
const shader = gl.createShader(type);
|
|
2489
|
+
if (!shader) {
|
|
2490
|
+
throw new ICEEngineError("ICE3_SHADER_COMPILE_FAILED", `ICE3_SHADER_COMPILE_FAILED: ${label}: allocation failed`);
|
|
2491
|
+
}
|
|
2492
|
+
gl.shaderSource(shader, source);
|
|
2493
|
+
gl.compileShader(shader);
|
|
2494
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
2495
|
+
const log = gl.getShaderInfoLog(shader) ?? "unknown compile error";
|
|
2496
|
+
gl.deleteShader(shader);
|
|
2497
|
+
throw new ICEEngineError("ICE3_SHADER_COMPILE_FAILED", `ICE3_SHADER_COMPILE_FAILED: ${label}: ${log}`);
|
|
2498
|
+
}
|
|
2499
|
+
return shader;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// packages/renderer/dist/ShadowFrustum.js
|
|
2503
|
+
var SHADOW_LIGHT_DIRECTION = [-0.35, 0.85, 0.4];
|
|
2504
|
+
var BOUNDS_PADDING = 0.08;
|
|
2505
|
+
function shadowFrustum(bounds, mapSize = 2048) {
|
|
2506
|
+
const box = bounds ?? { min: [-50, -5, -50], max: [650, 60, 650] };
|
|
2507
|
+
const centre = [
|
|
2508
|
+
(box.min[0] + box.max[0]) / 2,
|
|
2509
|
+
(box.min[1] + box.max[1]) / 2,
|
|
2510
|
+
(box.min[2] + box.max[2]) / 2
|
|
2511
|
+
];
|
|
2512
|
+
const radius = Math.max(1, 0.5 * Math.hypot(box.max[0] - box.min[0], box.max[1] - box.min[1], box.max[2] - box.min[2]));
|
|
2513
|
+
const pad = radius * BOUNDS_PADDING;
|
|
2514
|
+
const direction = normalize2(SHADOW_LIGHT_DIRECTION);
|
|
2515
|
+
const distance2 = radius * 2 + pad * 2;
|
|
2516
|
+
const eye = [
|
|
2517
|
+
centre[0] + direction[0] * distance2,
|
|
2518
|
+
centre[1] + direction[1] * distance2,
|
|
2519
|
+
centre[2] + direction[2] * distance2
|
|
2520
|
+
];
|
|
2521
|
+
const view = new ICEMatrix4().lookAt(new ICEVector3(eye[0], eye[1], eye[2]), new ICEVector3(centre[0], centre[1], centre[2]), new ICEVector3(0, 1, 0));
|
|
2522
|
+
let minX = Infinity;
|
|
2523
|
+
let maxX = -Infinity;
|
|
2524
|
+
let minY = Infinity;
|
|
2525
|
+
let maxY = -Infinity;
|
|
2526
|
+
let minZ = Infinity;
|
|
2527
|
+
let maxZ = -Infinity;
|
|
2528
|
+
for (let corner = 0; corner < 8; corner += 1) {
|
|
2529
|
+
const point = new ICEVector3(corner & 1 ? box.max[0] : box.min[0], corner & 2 ? box.max[1] : box.min[1], corner & 4 ? box.max[2] : box.min[2]);
|
|
2530
|
+
const projected = view.transformPoint(point);
|
|
2531
|
+
minX = Math.min(minX, projected[0]);
|
|
2532
|
+
maxX = Math.max(maxX, projected[0]);
|
|
2533
|
+
minY = Math.min(minY, projected[1]);
|
|
2534
|
+
maxY = Math.max(maxY, projected[1]);
|
|
2535
|
+
minZ = Math.min(minZ, projected[2]);
|
|
2536
|
+
maxZ = Math.max(maxZ, projected[2]);
|
|
2537
|
+
}
|
|
2538
|
+
const projection = orthographic(minX - pad, maxX + pad, minY - pad, maxY + pad, -maxZ - pad, -minZ + pad);
|
|
2539
|
+
const width = maxX - minX + pad * 2;
|
|
2540
|
+
const height = maxY - minY + pad * 2;
|
|
2541
|
+
return {
|
|
2542
|
+
matrix: multiply(projection, view.elements),
|
|
2543
|
+
texelWorld: Math.max(width, height) / mapSize
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
function normalize2(value) {
|
|
2547
|
+
const length = Math.hypot(value[0], value[1], value[2]) || 1;
|
|
2548
|
+
return [value[0] / length, value[1] / length, value[2] / length];
|
|
2549
|
+
}
|
|
2550
|
+
function orthographic(left, right, bottom, top, near, far) {
|
|
2551
|
+
const lr = 1 / (left - right);
|
|
2552
|
+
const bt = 1 / (bottom - top);
|
|
2553
|
+
const nf = 1 / (near - far);
|
|
2554
|
+
return [
|
|
2555
|
+
-2 * lr,
|
|
2556
|
+
0,
|
|
2557
|
+
0,
|
|
2558
|
+
0,
|
|
2559
|
+
0,
|
|
2560
|
+
-2 * bt,
|
|
2561
|
+
0,
|
|
2562
|
+
0,
|
|
2563
|
+
0,
|
|
2564
|
+
0,
|
|
2565
|
+
2 * nf,
|
|
2566
|
+
0,
|
|
2567
|
+
(left + right) * lr,
|
|
2568
|
+
(top + bottom) * bt,
|
|
2569
|
+
(far + near) * nf,
|
|
2570
|
+
1
|
|
2571
|
+
];
|
|
2572
|
+
}
|
|
2573
|
+
function multiply(a, b) {
|
|
2574
|
+
const out = new Float32Array(16);
|
|
2575
|
+
for (let column = 0; column < 4; column += 1) {
|
|
2576
|
+
for (let row = 0; row < 4; row += 1) {
|
|
2577
|
+
let sum = 0;
|
|
2578
|
+
for (let k = 0; k < 4; k += 1) {
|
|
2579
|
+
sum += (a[k * 4 + row] ?? 0) * (b[column * 4 + k] ?? 0);
|
|
2580
|
+
}
|
|
2581
|
+
out[column * 4 + row] = sum;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
return out;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
// packages/renderer/dist/WebGL2ShadowMap.js
|
|
2588
|
+
var VERTEX_SOURCE3 = `#version 300 es
|
|
2589
|
+
layout(location = 0) in vec3 a_position;
|
|
2590
|
+
layout(location = 3) in mat4 a_instanceMatrix;
|
|
2591
|
+
uniform mat4 u_matrix;
|
|
2592
|
+
uniform float u_useInstancing;
|
|
2593
|
+
void main() {
|
|
2594
|
+
vec4 local = vec4(a_position, 1.0);
|
|
2595
|
+
vec4 world = mix(local, a_instanceMatrix * local, u_useInstancing);
|
|
2596
|
+
gl_Position = u_matrix * world;
|
|
2597
|
+
}
|
|
2598
|
+
`;
|
|
2599
|
+
var FRAGMENT_SOURCE3 = `#version 300 es
|
|
2600
|
+
precision highp float;
|
|
2601
|
+
void main() {
|
|
2602
|
+
// \u53EA\u5199\u6DF1\u5EA6\uFF0C\u989C\u8272\u4E0D\u9700\u8981\u3002
|
|
2603
|
+
}
|
|
2604
|
+
`;
|
|
2605
|
+
var SHADOW_SIZE = 2048;
|
|
2606
|
+
var WebGL2ShadowMap = class {
|
|
2607
|
+
gl;
|
|
2608
|
+
/** 最近一次着色器编译/链接失败的原因,供诊断读取。 */
|
|
2609
|
+
lastError = null;
|
|
2610
|
+
framebuffer = null;
|
|
2611
|
+
depthTexture = null;
|
|
2612
|
+
program = null;
|
|
2613
|
+
matrixLocation = null;
|
|
2614
|
+
instancingLocation = null;
|
|
2615
|
+
matrixScratch = new Float32Array(16);
|
|
2616
|
+
texelWorldValue = 1;
|
|
2617
|
+
constructor(gl) {
|
|
2618
|
+
this.gl = gl;
|
|
2619
|
+
this.createResources();
|
|
2620
|
+
}
|
|
2621
|
+
/** 深度纹理,供主着色器采样。 */
|
|
2622
|
+
get texture() {
|
|
2623
|
+
return this.depthTexture;
|
|
2624
|
+
}
|
|
2625
|
+
get matrix() {
|
|
2626
|
+
return this.matrixScratch;
|
|
2627
|
+
}
|
|
2628
|
+
/** 一个深度纹素在世界空间的边长(米)。法线偏移按它换算,别用魔数。 */
|
|
2629
|
+
get texelWorld() {
|
|
2630
|
+
return this.texelWorldValue;
|
|
2631
|
+
}
|
|
2632
|
+
/**
|
|
2633
|
+
* 按场景包围盒拟合视锥。每帧调用没有意义——场景内容不变时矩阵也不会变,
|
|
2634
|
+
* 所以由调用方决定何时重算(包围盒变化或首帧)。
|
|
2635
|
+
*/
|
|
2636
|
+
setBounds(bounds) {
|
|
2637
|
+
const frustum = shadowFrustum(bounds, SHADOW_SIZE);
|
|
2638
|
+
this.matrixScratch.set(frustum.matrix);
|
|
2639
|
+
this.texelWorldValue = frustum.texelWorld;
|
|
2640
|
+
}
|
|
2641
|
+
/** 绑定阴影帧缓冲,返回是否成功。 */
|
|
2642
|
+
begin() {
|
|
2643
|
+
const gl = this.gl;
|
|
2644
|
+
if (!this.framebuffer || !this.program)
|
|
2645
|
+
return false;
|
|
2646
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
2647
|
+
gl.viewport(0, 0, SHADOW_SIZE, SHADOW_SIZE);
|
|
2648
|
+
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
2649
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
2650
|
+
gl.enable(gl.DEPTH_TEST);
|
|
2651
|
+
gl.disable(gl.CULL_FACE);
|
|
2652
|
+
gl.useProgram(this.program);
|
|
2653
|
+
gl.uniformMatrix4fv(this.matrixLocation, false, this.matrixScratch);
|
|
2654
|
+
gl.uniform1f(this.instancingLocation, 1);
|
|
2655
|
+
return true;
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* 非实例化绘制:矩阵换成 `lightViewProjection × world`,顶点位置直接当世界坐标用。
|
|
2659
|
+
*
|
|
2660
|
+
* 阴影通道原先只会画实例化批次,瓦片(渲染批次)与落单网格因此完全不投影。
|
|
2661
|
+
* 补这两条路径靠的就是这个入口——和 `WebGL2SceneDepth` 同一套写法。
|
|
2662
|
+
*/
|
|
2663
|
+
setMatrix(matrix) {
|
|
2664
|
+
this.gl.uniformMatrix4fv(this.matrixLocation, false, matrix);
|
|
2665
|
+
}
|
|
2666
|
+
setInstancing(enabled) {
|
|
2667
|
+
this.gl.uniform1f(this.instancingLocation, enabled ? 1 : 0);
|
|
2668
|
+
}
|
|
2669
|
+
/** 解绑,恢复默认帧缓冲。 */
|
|
2670
|
+
end(width, height) {
|
|
2671
|
+
const gl = this.gl;
|
|
2672
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2673
|
+
gl.disable(gl.CULL_FACE);
|
|
2674
|
+
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
2675
|
+
gl.viewport(0, 0, width, height);
|
|
2676
|
+
}
|
|
2677
|
+
dispose() {
|
|
2678
|
+
const gl = this.gl;
|
|
2679
|
+
if (this.depthTexture)
|
|
2680
|
+
gl.deleteTexture(this.depthTexture);
|
|
2681
|
+
if (this.framebuffer)
|
|
2682
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
2683
|
+
if (this.program)
|
|
2684
|
+
gl.deleteProgram(this.program);
|
|
2685
|
+
this.depthTexture = null;
|
|
2686
|
+
this.framebuffer = null;
|
|
2687
|
+
this.program = null;
|
|
2688
|
+
}
|
|
2689
|
+
createResources() {
|
|
2690
|
+
const gl = this.gl;
|
|
2691
|
+
this.depthTexture = gl.createTexture();
|
|
2692
|
+
gl.bindTexture(gl.TEXTURE_2D, this.depthTexture);
|
|
2693
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, SHADOW_SIZE, SHADOW_SIZE, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
|
|
2694
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
2695
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
2696
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2697
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2698
|
+
this.framebuffer = gl.createFramebuffer();
|
|
2699
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
2700
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.depthTexture, 0);
|
|
2701
|
+
gl.drawBuffers([gl.NONE]);
|
|
2702
|
+
gl.readBuffer(gl.NONE);
|
|
2703
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2704
|
+
this.program = this.createProgram(VERTEX_SOURCE3, FRAGMENT_SOURCE3);
|
|
2705
|
+
this.matrixLocation = this.program ? gl.getUniformLocation(this.program, "u_matrix") : null;
|
|
2706
|
+
this.instancingLocation = this.program ? gl.getUniformLocation(this.program, "u_useInstancing") : null;
|
|
2707
|
+
this.setBounds(null);
|
|
2708
|
+
}
|
|
2709
|
+
createProgram(vertex, fragment) {
|
|
2710
|
+
try {
|
|
2711
|
+
return createShaderProgram(this.gl, vertex, fragment, "shadow");
|
|
2712
|
+
} catch (error) {
|
|
2713
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
2714
|
+
return null;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
|
|
2719
|
+
// packages/renderer/dist/SceneBounds.js
|
|
2720
|
+
var localBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
2721
|
+
function geometryLocalBounds(geometry) {
|
|
2722
|
+
const cached = localBoundsCache.get(geometry);
|
|
2723
|
+
if (cached)
|
|
2724
|
+
return cached;
|
|
2725
|
+
const positions = geometry.positions;
|
|
2726
|
+
if (!positions || positions.length < 3)
|
|
2727
|
+
return null;
|
|
2728
|
+
let minX = Infinity;
|
|
2729
|
+
let minY = Infinity;
|
|
2730
|
+
let minZ = Infinity;
|
|
2731
|
+
let maxX = -Infinity;
|
|
2732
|
+
let maxY = -Infinity;
|
|
2733
|
+
let maxZ = -Infinity;
|
|
2734
|
+
for (let index = 0; index + 2 < positions.length; index += 3) {
|
|
2735
|
+
const x = positions[index];
|
|
2736
|
+
const y = positions[index + 1];
|
|
2737
|
+
const z = positions[index + 2];
|
|
2738
|
+
if (x < minX)
|
|
2739
|
+
minX = x;
|
|
2740
|
+
if (y < minY)
|
|
2741
|
+
minY = y;
|
|
2742
|
+
if (z < minZ)
|
|
2743
|
+
minZ = z;
|
|
2744
|
+
if (x > maxX)
|
|
2745
|
+
maxX = x;
|
|
2746
|
+
if (y > maxY)
|
|
2747
|
+
maxY = y;
|
|
2748
|
+
if (z > maxZ)
|
|
2749
|
+
maxZ = z;
|
|
2750
|
+
}
|
|
2751
|
+
const bounds = { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] };
|
|
2752
|
+
localBoundsCache.set(geometry, bounds);
|
|
2753
|
+
return bounds;
|
|
2754
|
+
}
|
|
2755
|
+
function accumulateSceneBounds(renderables) {
|
|
2756
|
+
let minX = Infinity;
|
|
2757
|
+
let minY = Infinity;
|
|
2758
|
+
let minZ = Infinity;
|
|
2759
|
+
let maxX = -Infinity;
|
|
2760
|
+
let maxY = -Infinity;
|
|
2761
|
+
let maxZ = -Infinity;
|
|
2762
|
+
let seen = false;
|
|
2763
|
+
for (const renderable of renderables) {
|
|
2764
|
+
const local = geometryLocalBounds(renderable.mesh.geometry);
|
|
2765
|
+
if (!local)
|
|
2766
|
+
continue;
|
|
2767
|
+
const matrix = renderable.worldMatrix;
|
|
2768
|
+
for (let corner = 0; corner < 8; corner += 1) {
|
|
2769
|
+
const x = corner & 1 ? local.max[0] : local.min[0];
|
|
2770
|
+
const y = corner & 2 ? local.max[1] : local.min[1];
|
|
2771
|
+
const z = corner & 4 ? local.max[2] : local.min[2];
|
|
2772
|
+
const worldX = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12];
|
|
2773
|
+
const worldY = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13];
|
|
2774
|
+
const worldZ = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14];
|
|
2775
|
+
if (worldX < minX)
|
|
2776
|
+
minX = worldX;
|
|
2777
|
+
if (worldY < minY)
|
|
2778
|
+
minY = worldY;
|
|
2779
|
+
if (worldZ < minZ)
|
|
2780
|
+
minZ = worldZ;
|
|
2781
|
+
if (worldX > maxX)
|
|
2782
|
+
maxX = worldX;
|
|
2783
|
+
if (worldY > maxY)
|
|
2784
|
+
maxY = worldY;
|
|
2785
|
+
if (worldZ > maxZ)
|
|
2786
|
+
maxZ = worldZ;
|
|
2787
|
+
seen = true;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return seen ? { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] } : null;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// packages/renderer/dist/WebGL2SceneDepth.js
|
|
2794
|
+
var VERTEX_SOURCE4 = `#version 300 es
|
|
2795
|
+
layout(location = 0) in vec3 a_position;
|
|
2796
|
+
layout(location = 3) in mat4 a_instanceMatrix;
|
|
2797
|
+
uniform mat4 u_matrix;
|
|
2798
|
+
uniform float u_useInstancing;
|
|
2799
|
+
void main() {
|
|
2800
|
+
vec4 local = vec4(a_position, 1.0);
|
|
2801
|
+
vec4 world = mix(local, a_instanceMatrix * local, u_useInstancing);
|
|
2802
|
+
gl_Position = u_matrix * world;
|
|
2803
|
+
}
|
|
2804
|
+
`;
|
|
2805
|
+
var FRAGMENT_SOURCE4 = `#version 300 es
|
|
2806
|
+
precision highp float;
|
|
2807
|
+
void main() {}
|
|
2808
|
+
`;
|
|
2809
|
+
var WebGL2SceneDepth = class {
|
|
2810
|
+
gl;
|
|
2811
|
+
framebuffer;
|
|
2812
|
+
depthTexture = null;
|
|
2813
|
+
program;
|
|
2814
|
+
matrixLocation;
|
|
2815
|
+
instancingLocation;
|
|
2816
|
+
width = 0;
|
|
2817
|
+
height = 0;
|
|
2818
|
+
constructor(gl) {
|
|
2819
|
+
this.gl = gl;
|
|
2820
|
+
this.program = createShaderProgram(gl, VERTEX_SOURCE4, FRAGMENT_SOURCE4, "scene-depth");
|
|
2821
|
+
this.matrixLocation = gl.getUniformLocation(this.program, "u_matrix");
|
|
2822
|
+
this.instancingLocation = gl.getUniformLocation(this.program, "u_useInstancing");
|
|
2823
|
+
const framebuffer = gl.createFramebuffer();
|
|
2824
|
+
if (!framebuffer)
|
|
2825
|
+
throw new Error("ICE3_FRAMEBUFFER_FAILED: scene depth");
|
|
2826
|
+
this.framebuffer = framebuffer;
|
|
2827
|
+
}
|
|
2828
|
+
get texture() {
|
|
2829
|
+
return this.depthTexture;
|
|
2830
|
+
}
|
|
2831
|
+
begin(viewProjection, width, height) {
|
|
2832
|
+
const gl = this.gl;
|
|
2833
|
+
this.ensureTexture(Math.max(1, width), Math.max(1, height));
|
|
2834
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
2835
|
+
gl.viewport(0, 0, this.width, this.height);
|
|
2836
|
+
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
2837
|
+
gl.disable(gl.BLEND);
|
|
2838
|
+
gl.depthMask(true);
|
|
2839
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
2840
|
+
gl.enable(gl.DEPTH_TEST);
|
|
2841
|
+
gl.useProgram(this.program);
|
|
2842
|
+
gl.uniformMatrix4fv(this.matrixLocation, false, viewProjection);
|
|
2843
|
+
gl.uniform1f(this.instancingLocation, 0);
|
|
2844
|
+
}
|
|
2845
|
+
setMatrix(matrix) {
|
|
2846
|
+
this.gl.uniformMatrix4fv(this.matrixLocation, false, matrix);
|
|
2847
|
+
}
|
|
2848
|
+
setInstancing(enabled) {
|
|
2849
|
+
this.gl.uniform1f(this.instancingLocation, enabled ? 1 : 0);
|
|
2850
|
+
}
|
|
2851
|
+
end(width, height) {
|
|
2852
|
+
const gl = this.gl;
|
|
2853
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2854
|
+
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
2855
|
+
gl.viewport(0, 0, width, height);
|
|
2856
|
+
}
|
|
2857
|
+
dispose() {
|
|
2858
|
+
const gl = this.gl;
|
|
2859
|
+
if (this.depthTexture)
|
|
2860
|
+
gl.deleteTexture(this.depthTexture);
|
|
2861
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
2862
|
+
gl.deleteProgram(this.program);
|
|
2863
|
+
this.depthTexture = null;
|
|
2864
|
+
}
|
|
2865
|
+
ensureTexture(width, height) {
|
|
2866
|
+
const gl = this.gl;
|
|
2867
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
2868
|
+
if (this.depthTexture && this.width === width && this.height === height) {
|
|
2869
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.depthTexture, 0);
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
if (this.depthTexture)
|
|
2873
|
+
gl.deleteTexture(this.depthTexture);
|
|
2874
|
+
this.width = width;
|
|
2875
|
+
this.height = height;
|
|
2876
|
+
const texture = gl.createTexture();
|
|
2877
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
2878
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
|
|
2879
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
2880
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
2881
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2882
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2883
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, texture, 0);
|
|
2884
|
+
gl.drawBuffers([gl.NONE]);
|
|
2885
|
+
gl.readBuffer(gl.NONE);
|
|
2886
|
+
this.depthTexture = texture;
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
|
|
2890
|
+
// packages/renderer/dist/WebGL2TextureCache.js
|
|
2891
|
+
var REPEAT = 10497;
|
|
2892
|
+
var fetchImageLoader = async (url) => {
|
|
2893
|
+
const response = await fetch(url);
|
|
2894
|
+
if (!response.ok) {
|
|
2895
|
+
throw new Error(`ICE3_TEXTURE_FETCH_FAILED: ${response.status} ${url}`);
|
|
2896
|
+
}
|
|
2897
|
+
return createImageBitmap(await response.blob());
|
|
2898
|
+
};
|
|
2899
|
+
var WebGL2TextureCache = class {
|
|
2900
|
+
gl;
|
|
2901
|
+
load;
|
|
2902
|
+
onUpload;
|
|
2903
|
+
entries = /* @__PURE__ */ new Map();
|
|
2904
|
+
disposed = false;
|
|
2905
|
+
constructor(gl, load = fetchImageLoader, onUpload) {
|
|
2906
|
+
this.gl = gl;
|
|
2907
|
+
this.load = load;
|
|
2908
|
+
this.onUpload = onUpload;
|
|
2909
|
+
}
|
|
2910
|
+
/** Texture for `url`, or `null` while it is still loading (or after failure). */
|
|
2911
|
+
texture(url) {
|
|
2912
|
+
if (this.disposed)
|
|
2913
|
+
return null;
|
|
2914
|
+
const existing = this.entries.get(url);
|
|
2915
|
+
if (existing) {
|
|
2916
|
+
return existing.state === "ready" ? existing.texture : null;
|
|
2917
|
+
}
|
|
2918
|
+
this.entries.set(url, { state: "pending" });
|
|
2919
|
+
void this.load(url).then((image) => {
|
|
2920
|
+
if (this.disposed) {
|
|
2921
|
+
image.close?.();
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
const texture = this.upload(image);
|
|
2925
|
+
if (this.disposed) {
|
|
2926
|
+
this.gl.deleteTexture(texture);
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
this.entries.set(url, { state: "ready", texture });
|
|
2930
|
+
this.onUpload?.();
|
|
2931
|
+
}).catch(() => {
|
|
2932
|
+
if (!this.disposed)
|
|
2933
|
+
this.entries.set(url, { state: "failed" });
|
|
2934
|
+
});
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
stats() {
|
|
2938
|
+
let loaded = 0;
|
|
2939
|
+
let pending = 0;
|
|
2940
|
+
let failed2 = 0;
|
|
2941
|
+
for (const entry of this.entries.values()) {
|
|
2942
|
+
if (entry.state === "ready")
|
|
2943
|
+
loaded += 1;
|
|
2944
|
+
else if (entry.state === "pending")
|
|
2945
|
+
pending += 1;
|
|
2946
|
+
else
|
|
2947
|
+
failed2 += 1;
|
|
2948
|
+
}
|
|
2949
|
+
return { loaded, pending, failed: failed2 };
|
|
2950
|
+
}
|
|
2951
|
+
dispose() {
|
|
2952
|
+
if (this.disposed)
|
|
2953
|
+
return;
|
|
2954
|
+
this.disposed = true;
|
|
2955
|
+
for (const entry of this.entries.values()) {
|
|
2956
|
+
if (entry.state === "ready")
|
|
2957
|
+
this.gl.deleteTexture(entry.texture);
|
|
2958
|
+
}
|
|
2959
|
+
this.entries.clear();
|
|
2960
|
+
}
|
|
2961
|
+
upload(image) {
|
|
2962
|
+
const gl = this.gl;
|
|
2963
|
+
const texture = gl.createTexture();
|
|
2964
|
+
if (!texture) {
|
|
2965
|
+
throw new Error("ICE3_TEXTURE_CREATE_FAILED");
|
|
2966
|
+
}
|
|
2967
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
2968
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
2969
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, REPEAT);
|
|
2970
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, REPEAT);
|
|
2971
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
2972
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2973
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
2974
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2975
|
+
image.close?.();
|
|
2976
|
+
return texture;
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
|
|
2980
|
+
// packages/renderer/dist/WebGL2Uv.js
|
|
2981
|
+
var DEFAULT_UV_SCALE = [1, 1];
|
|
2982
|
+
|
|
1709
2983
|
// packages/renderer/dist/Color.js
|
|
1710
2984
|
var COLOR_CACHE_LIMIT = 128;
|
|
1711
2985
|
var COLOR_CACHE = /* @__PURE__ */ new Map();
|
|
1712
2986
|
function colorToRgba(color) {
|
|
1713
2987
|
const normalized = color.startsWith("#") ? color.slice(1) : color;
|
|
1714
2988
|
const expanded = normalized.length === 3 ? normalized.split("").map((part) => part + part).join("") : normalized;
|
|
1715
|
-
if (!/^[0-9a-fA-F]{6}
|
|
2989
|
+
if (!/^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(expanded)) {
|
|
1716
2990
|
throw new Error(`Unsupported ICE basic material color: ${color}`);
|
|
1717
2991
|
}
|
|
1718
2992
|
const key = expanded.toLowerCase();
|
|
@@ -1723,7 +2997,7 @@ function colorToRgba(color) {
|
|
|
1723
2997
|
Number.parseInt(expanded.slice(0, 2), 16) / 255,
|
|
1724
2998
|
Number.parseInt(expanded.slice(2, 4), 16) / 255,
|
|
1725
2999
|
Number.parseInt(expanded.slice(4, 6), 16) / 255,
|
|
1726
|
-
1
|
|
3000
|
+
expanded.length === 8 ? Number.parseInt(expanded.slice(6, 8), 16) / 255 : 1
|
|
1727
3001
|
]);
|
|
1728
3002
|
if (COLOR_CACHE.size >= COLOR_CACHE_LIMIT) {
|
|
1729
3003
|
const oldest = COLOR_CACHE.keys().next().value;
|
|
@@ -1781,6 +3055,9 @@ var EDGE_PIXEL_OFFSETS = [
|
|
|
1781
3055
|
];
|
|
1782
3056
|
var SELECTION_RGBA = [255 / 255, 208 / 255, 0];
|
|
1783
3057
|
var SELECTION_TINT_ALPHA = 0.65;
|
|
3058
|
+
function isTransparentColor(color) {
|
|
3059
|
+
return colorToRgba(color)[3] < 0.999;
|
|
3060
|
+
}
|
|
1784
3061
|
var WebGL2RendererBackend = class {
|
|
1785
3062
|
capabilities = {
|
|
1786
3063
|
backend: "webgl2",
|
|
@@ -1794,6 +3071,12 @@ var WebGL2RendererBackend = class {
|
|
|
1794
3071
|
onContextLost;
|
|
1795
3072
|
onContextRestored;
|
|
1796
3073
|
program = null;
|
|
3074
|
+
clipPlane = [0, 0, 0, 0];
|
|
3075
|
+
shadowMap = null;
|
|
3076
|
+
/** 方向光阴影开关:默认关闭,由 `initialize({ shadows })` 或页面地址 `?shadows=` 打开。 */
|
|
3077
|
+
shadowEnabled = false;
|
|
3078
|
+
/** `?shadowdebug=1` 输出遮蔽判定,`=2` 输出深度图;排查阴影问题的唯一可信手段。 */
|
|
3079
|
+
shadowDebug = 0;
|
|
1797
3080
|
semanticProgram = null;
|
|
1798
3081
|
geometryCache = null;
|
|
1799
3082
|
instanceCache = null;
|
|
@@ -1821,10 +3104,108 @@ var WebGL2RendererBackend = class {
|
|
|
1821
3104
|
gpuEvictions: 0,
|
|
1822
3105
|
gpuBytes: 0
|
|
1823
3106
|
};
|
|
3107
|
+
textures = null;
|
|
3108
|
+
sceneDepth = null;
|
|
3109
|
+
occlusionRadius = 2.5;
|
|
3110
|
+
/**
|
|
3111
|
+
* 三圈采样比双圈多找到约 1.7 个亮度点的遮蔽(同参数 96.36 → 94.65),原样沿用到
|
|
3112
|
+
* 默认值就把厂区压得发闷。0.30 是回调后的值:整帧 95.70(无遮蔽 99.59),
|
|
3113
|
+
* 高光像素 878(无遮蔽 1088),接触暗部仍看得见。详见 SPEC-007 第 5.5 节。
|
|
3114
|
+
*/
|
|
3115
|
+
occlusionStrength = 0.3;
|
|
3116
|
+
options = null;
|
|
3117
|
+
/**
|
|
3118
|
+
* Bind the material's colour and (if it has one) its base-colour texture.
|
|
3119
|
+
* Called for every opaque/shaded draw so a batched draw and a single mesh draw
|
|
3120
|
+
* cannot drift apart.
|
|
3121
|
+
*/
|
|
3122
|
+
applyMaterial(material) {
|
|
3123
|
+
const program = this.program;
|
|
3124
|
+
if (!program)
|
|
3125
|
+
return;
|
|
3126
|
+
program.setColor(colorToRgba(material.color));
|
|
3127
|
+
const map = material.map;
|
|
3128
|
+
const texture = map && this.textures ? this.textures.texture(map.url) : null;
|
|
3129
|
+
program.setBaseColorMap(texture, map?.scale ?? material.ormMap?.scale ?? DEFAULT_UV_SCALE);
|
|
3130
|
+
const normalMap = material.normalMap;
|
|
3131
|
+
const normalTexture = normalMap && this.textures ? this.textures.texture(normalMap.url) : null;
|
|
3132
|
+
program.setNormalMap(normalTexture, normalMap?.strength ?? 1);
|
|
3133
|
+
const ormMap = material.ormMap;
|
|
3134
|
+
const ormTexture = ormMap && this.textures ? this.textures.texture(ormMap.url) : null;
|
|
3135
|
+
program.setOrmMap(ormTexture, ormMap?.occlusion !== false, ormMap?.metallicRoughness !== false);
|
|
3136
|
+
program.setSurface(material.metallic, material.roughness, material.emissive);
|
|
3137
|
+
}
|
|
3138
|
+
/** Edge overlays are line work: never sampled, always flat. */
|
|
3139
|
+
applyEdgeMaterial(material) {
|
|
3140
|
+
const program = this.program;
|
|
3141
|
+
if (!program)
|
|
3142
|
+
return;
|
|
3143
|
+
program.setColor(colorToRgba(material.edgeColor ?? material.color));
|
|
3144
|
+
program.setBaseColorMap(null, DEFAULT_UV_SCALE);
|
|
3145
|
+
program.setNormalMap(null, 1);
|
|
3146
|
+
program.setOrmMap(null, false, false);
|
|
3147
|
+
program.setSurface(0, 0.85, void 0);
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* `?ao=<radius>,<strength>` 覆盖遮蔽参数(`ao=off` 关闭)。
|
|
3151
|
+
*
|
|
3152
|
+
* 屏幕空间遮蔽的合适参数与场景尺度强相关,靠改代码重编译来找太慢。这里只读一次
|
|
3153
|
+
* 页面地址,纯属调参通道,不参与渲染决策。
|
|
3154
|
+
*/
|
|
3155
|
+
readOcclusionOverride() {
|
|
3156
|
+
if (typeof location === "undefined")
|
|
3157
|
+
return;
|
|
3158
|
+
const value = new URLSearchParams(location.search).get("ao");
|
|
3159
|
+
if (!value)
|
|
3160
|
+
return;
|
|
3161
|
+
if (value === "off") {
|
|
3162
|
+
this.occlusionStrength = 0;
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
const parts = value.split(",").map(Number);
|
|
3166
|
+
const radius = parts[0];
|
|
3167
|
+
const strength = parts[1];
|
|
3168
|
+
if (radius !== void 0 && Number.isFinite(radius) && radius > 0) {
|
|
3169
|
+
this.occlusionRadius = radius;
|
|
3170
|
+
}
|
|
3171
|
+
if (strength !== void 0 && Number.isFinite(strength) && strength >= 0) {
|
|
3172
|
+
this.occlusionStrength = strength;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
/**
|
|
3176
|
+
* `?shadows=1` / `?shadows=0` 覆盖阴影开关,与 `?ao` 同属调参通道。
|
|
3177
|
+
*
|
|
3178
|
+
* 阴影是一次额外全场景 pass,默认不开;演示与对比时靠地址栏切换,
|
|
3179
|
+
* 不必为看清"有影子/没影子"重编译一次。
|
|
3180
|
+
*/
|
|
3181
|
+
readShadowOverride() {
|
|
3182
|
+
if (typeof location === "undefined")
|
|
3183
|
+
return;
|
|
3184
|
+
const params = new URLSearchParams(location.search);
|
|
3185
|
+
const value = params.get("shadows");
|
|
3186
|
+
if (value !== null) {
|
|
3187
|
+
this.shadowEnabled = value !== "0" && value !== "off";
|
|
3188
|
+
}
|
|
3189
|
+
const debug = Number(params.get("shadowdebug"));
|
|
3190
|
+
this.shadowDebug = Number.isFinite(debug) ? Math.max(0, Math.min(2, Math.trunc(debug))) : 0;
|
|
3191
|
+
if (this.shadowDebug > 0)
|
|
3192
|
+
this.shadowEnabled = true;
|
|
3193
|
+
}
|
|
3194
|
+
ensureTextureCache() {
|
|
3195
|
+
if (this.textures || !this.gl)
|
|
3196
|
+
return;
|
|
3197
|
+
this.textures = new WebGL2TextureCache(this.gl, void 0, () => {
|
|
3198
|
+
this.options?.onUploadReady?.();
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
1824
3201
|
handleContextLost = (event) => {
|
|
1825
3202
|
event.preventDefault();
|
|
1826
3203
|
this.picker = null;
|
|
1827
3204
|
this.program = null;
|
|
3205
|
+
this.sceneDepth?.dispose();
|
|
3206
|
+
this.sceneDepth = null;
|
|
3207
|
+
this.shadowMap?.dispose();
|
|
3208
|
+
this.shadowMap = null;
|
|
1828
3209
|
this.geometryCache?.clear();
|
|
1829
3210
|
this.geometryCache = null;
|
|
1830
3211
|
this.instanceCache?.clear();
|
|
@@ -1843,6 +3224,7 @@ var WebGL2RendererBackend = class {
|
|
|
1843
3224
|
this.onContextRestored?.();
|
|
1844
3225
|
};
|
|
1845
3226
|
initialize(options) {
|
|
3227
|
+
this.options = options;
|
|
1846
3228
|
const gl = options.canvas.getContext("webgl2", {
|
|
1847
3229
|
alpha: true,
|
|
1848
3230
|
antialias: true,
|
|
@@ -1866,6 +3248,11 @@ var WebGL2RendererBackend = class {
|
|
|
1866
3248
|
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
1867
3249
|
gl.polygonOffset(2, 4);
|
|
1868
3250
|
this.createProgram(gl);
|
|
3251
|
+
this.ensureTextureCache();
|
|
3252
|
+
this.sceneDepth = new WebGL2SceneDepth(gl);
|
|
3253
|
+
this.readOcclusionOverride();
|
|
3254
|
+
this.shadowEnabled = options.shadows ?? false;
|
|
3255
|
+
this.readShadowOverride();
|
|
1869
3256
|
this.canvas.addEventListener("webglcontextlost", this.handleContextLost);
|
|
1870
3257
|
this.canvas.addEventListener("webglcontextrestored", this.handleContextRestored);
|
|
1871
3258
|
}
|
|
@@ -1888,9 +3275,21 @@ var WebGL2RendererBackend = class {
|
|
|
1888
3275
|
}
|
|
1889
3276
|
this.geometryCache.pin(this.gl, geometry);
|
|
1890
3277
|
}
|
|
3278
|
+
prepareGeometry(geometry, update) {
|
|
3279
|
+
if (!this.gl || !this.geometryCache) {
|
|
3280
|
+
throw new ICEEngineError("ICE3_ENGINE_NOT_READY", "Renderer is not initialized");
|
|
3281
|
+
}
|
|
3282
|
+
this.geometryCache.pin(this.gl, geometry, update);
|
|
3283
|
+
}
|
|
3284
|
+
releaseGeometry(geometry) {
|
|
3285
|
+
this.geometryCache?.unpin(geometry);
|
|
3286
|
+
}
|
|
1891
3287
|
releaseBatchGeometry(geometry) {
|
|
1892
3288
|
this.geometryCache?.unpin(geometry);
|
|
1893
3289
|
}
|
|
3290
|
+
setClipPlane(plane) {
|
|
3291
|
+
this.clipPlane = plane ? [plane[0], plane[1], plane[2], plane[3]] : [0, 0, 0, 0];
|
|
3292
|
+
}
|
|
1894
3293
|
render(frame) {
|
|
1895
3294
|
if (!this.gl || !this.program || !this.geometryCache) {
|
|
1896
3295
|
throw new ICEEngineError("ICE3_ENGINE_NOT_READY", "Renderer is not initialized");
|
|
@@ -1913,7 +3312,12 @@ var WebGL2RendererBackend = class {
|
|
|
1913
3312
|
gpuBytes: 0
|
|
1914
3313
|
};
|
|
1915
3314
|
this.program.use();
|
|
3315
|
+
this.program.setClipPlane(this.clipPlane);
|
|
1916
3316
|
this.program.setClipOffset(0, 0);
|
|
3317
|
+
this.program.setDirectionalShading(false);
|
|
3318
|
+
this.program.setWaterShading(false);
|
|
3319
|
+
const cameraPosition = frame.camera.getPosition().toArray();
|
|
3320
|
+
this.program.setCameraPosition(cameraPosition);
|
|
1917
3321
|
this.geometryCache.beginFrame();
|
|
1918
3322
|
this.instanceCache.beginFrame();
|
|
1919
3323
|
this.instancedMeshIds.clear();
|
|
@@ -1923,94 +3327,228 @@ var WebGL2RendererBackend = class {
|
|
|
1923
3327
|
const renderables = collectRenderables(frame.scene, this.renderablesScratch);
|
|
1924
3328
|
const instances = collectInstanceBatches(renderables, this.instancesScratch);
|
|
1925
3329
|
const batches = collectRenderBatches(frame.scene, this.batchesScratch);
|
|
3330
|
+
const standalone = renderablesOutsideInstanceBatches(renderables, instances);
|
|
1926
3331
|
this.profiler.mark("collect");
|
|
1927
|
-
|
|
1928
|
-
this.
|
|
1929
|
-
|
|
1930
|
-
const
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
for (const
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
this.frameStats.drawCalls += 1;
|
|
1943
|
-
if (batch.material.edgeColor && instance.edgeCount > 0) {
|
|
1944
|
-
this.program.setVertexColors(false);
|
|
1945
|
-
this.program.setColor(colorToRgba(batch.material.edgeColor));
|
|
1946
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.edgeIndexBuffer);
|
|
1947
|
-
for (const [offsetX, offsetY] of EDGE_PIXEL_OFFSETS) {
|
|
1948
|
-
this.program.setClipOffset(offsetX * clipPixelX, offsetY * clipPixelY);
|
|
1949
|
-
gl.drawElementsInstanced(gl.LINES, instance.edgeCount, batch.geometry.edgeIndices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0, instance.instanceCount);
|
|
1950
|
-
this.frameStats.drawCalls += 1;
|
|
1951
|
-
}
|
|
3332
|
+
let aoReady = false;
|
|
3333
|
+
if (this.sceneDepth) {
|
|
3334
|
+
const deviceWidth = Math.max(1, Math.round(frame.viewport.width * this.dpr));
|
|
3335
|
+
const deviceHeight = Math.max(1, Math.round(frame.viewport.height * this.dpr));
|
|
3336
|
+
this.sceneDepth.begin(viewProjection.elements, deviceWidth, deviceHeight);
|
|
3337
|
+
this.sceneDepth.setInstancing(true);
|
|
3338
|
+
for (const batch of instances) {
|
|
3339
|
+
if (isTransparentColor(batch.material.color))
|
|
3340
|
+
continue;
|
|
3341
|
+
const instance = this.instanceCache.retain(gl, this.geometryCache, batch);
|
|
3342
|
+
if (!instance)
|
|
3343
|
+
continue;
|
|
3344
|
+
gl.bindVertexArray(instance.vertexArray);
|
|
3345
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.triangleIndexBuffer);
|
|
3346
|
+
gl.drawElementsInstanced(gl.TRIANGLES, instance.triangleCount, batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0, instance.instanceCount);
|
|
1952
3347
|
}
|
|
3348
|
+
this.sceneDepth.setInstancing(false);
|
|
3349
|
+
for (const draw of batches) {
|
|
3350
|
+
if (isTransparentColor(draw.batch.material.color))
|
|
3351
|
+
continue;
|
|
3352
|
+
world.elements.set(draw.worldMatrix);
|
|
3353
|
+
mvp.elements.set(viewProjection.elements);
|
|
3354
|
+
mvp.multiply(world);
|
|
3355
|
+
this.sceneDepth.setMatrix(mvp.elements);
|
|
3356
|
+
const geometry = this.geometryCache.retain(gl, draw.batch.geometry);
|
|
3357
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3358
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3359
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, draw.batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3360
|
+
}
|
|
3361
|
+
for (const renderable of standalone) {
|
|
3362
|
+
if (isTransparentColor(renderable.mesh.material.color))
|
|
3363
|
+
continue;
|
|
3364
|
+
world.elements.set(renderable.worldMatrix);
|
|
3365
|
+
mvp.elements.set(viewProjection.elements);
|
|
3366
|
+
mvp.multiply(world);
|
|
3367
|
+
this.sceneDepth.setMatrix(mvp.elements);
|
|
3368
|
+
const geometry = this.geometryCache.retain(gl, renderable.mesh.geometry);
|
|
3369
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3370
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3371
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, renderable.mesh.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3372
|
+
}
|
|
3373
|
+
this.sceneDepth.end(deviceWidth, deviceHeight);
|
|
3374
|
+
aoReady = true;
|
|
3375
|
+
this.program.use();
|
|
1953
3376
|
}
|
|
1954
|
-
this.
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
this.
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
3377
|
+
if (this.shadowEnabled && this.shadowMap) {
|
|
3378
|
+
this.shadowMap.setBounds(accumulateSceneBounds(renderables));
|
|
3379
|
+
}
|
|
3380
|
+
if (this.shadowEnabled && this.shadowMap?.begin()) {
|
|
3381
|
+
const lightViewProjection = this.shadowMap.matrix;
|
|
3382
|
+
for (const batch of instances) {
|
|
3383
|
+
if (isTransparentColor(batch.material.color))
|
|
3384
|
+
continue;
|
|
3385
|
+
const instance = this.instanceCache.retain(gl, this.geometryCache, batch);
|
|
3386
|
+
if (!instance)
|
|
3387
|
+
continue;
|
|
3388
|
+
gl.bindVertexArray(instance.vertexArray);
|
|
3389
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.triangleIndexBuffer);
|
|
3390
|
+
gl.drawElementsInstanced(gl.TRIANGLES, instance.triangleCount, batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0, instance.instanceCount);
|
|
3391
|
+
}
|
|
3392
|
+
this.shadowMap.setInstancing(false);
|
|
3393
|
+
for (const draw of batches) {
|
|
3394
|
+
if (isTransparentColor(draw.batch.material.color))
|
|
3395
|
+
continue;
|
|
3396
|
+
world.elements.set(draw.worldMatrix);
|
|
3397
|
+
mvp.elements.set(lightViewProjection);
|
|
3398
|
+
mvp.multiply(world);
|
|
3399
|
+
this.shadowMap.setMatrix(mvp.elements);
|
|
3400
|
+
const geometry = this.geometryCache.retain(gl, draw.batch.geometry);
|
|
3401
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3402
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3403
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, draw.batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3404
|
+
}
|
|
3405
|
+
for (const renderable of standalone) {
|
|
3406
|
+
if (isTransparentColor(renderable.mesh.material.color))
|
|
3407
|
+
continue;
|
|
3408
|
+
world.elements.set(renderable.worldMatrix);
|
|
3409
|
+
mvp.elements.set(lightViewProjection);
|
|
3410
|
+
mvp.multiply(world);
|
|
3411
|
+
this.shadowMap.setMatrix(mvp.elements);
|
|
3412
|
+
const geometry = this.geometryCache.retain(gl, renderable.mesh.geometry);
|
|
3413
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3414
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3415
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, renderable.mesh.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
1979
3416
|
}
|
|
3417
|
+
this.shadowMap.end(frame.viewport.width * this.dpr, frame.viewport.height * this.dpr);
|
|
3418
|
+
this.program.use();
|
|
1980
3419
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
3420
|
+
if (this.shadowMap) {
|
|
3421
|
+
this.program.use();
|
|
3422
|
+
this.program.setShadow(
|
|
3423
|
+
this.shadowEnabled,
|
|
3424
|
+
this.shadowMap.texture,
|
|
3425
|
+
this.shadowMap.matrix,
|
|
3426
|
+
// 1.5 个纹素:够把平躺在光里的面抬离自身深度,又不会让影子明显离开物体。
|
|
3427
|
+
this.shadowMap.texelWorld * 1.5
|
|
3428
|
+
);
|
|
3429
|
+
this.program.setShadowDebug(this.shadowDebug);
|
|
3430
|
+
}
|
|
3431
|
+
this.program.setSceneDepth(aoReady ? this.sceneDepth.texture : null, frame.camera.getProjectionMatrix(aspect), frame.camera.near, frame.camera.far, Math.max(1, frame.viewport.width * this.dpr), Math.max(1, frame.viewport.height * this.dpr));
|
|
3432
|
+
this.program.setOcclusion(this.occlusionRadius, this.occlusionStrength);
|
|
3433
|
+
for (const transparentPass of [false, true]) {
|
|
3434
|
+
if (transparentPass) {
|
|
3435
|
+
gl.enable(gl.BLEND);
|
|
3436
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
3437
|
+
gl.depthMask(false);
|
|
3438
|
+
} else {
|
|
3439
|
+
gl.disable(gl.BLEND);
|
|
3440
|
+
gl.depthMask(true);
|
|
3441
|
+
}
|
|
3442
|
+
this.program.setInstancing(true);
|
|
3443
|
+
this.profiler.mark("prepareAndUpload");
|
|
3444
|
+
for (const batch of instances) {
|
|
3445
|
+
if (isTransparentColor(batch.material.color) !== transparentPass)
|
|
3446
|
+
continue;
|
|
3447
|
+
const instance = this.instanceCache.retain(gl, this.geometryCache, batch);
|
|
3448
|
+
if (!instance)
|
|
3449
|
+
continue;
|
|
3450
|
+
for (const id of batch.meshIds)
|
|
3451
|
+
this.instancedMeshIds.add(id);
|
|
3452
|
+
mvp.elements.set(viewProjection.elements);
|
|
3453
|
+
this.program.setMatrix(mvp);
|
|
3454
|
+
this.applyMaterial(batch.material);
|
|
3455
|
+
this.program.setVertexColors(Boolean(batch.geometry.colors));
|
|
3456
|
+
this.program.setDirectionalShading(batch.material.shading !== "flat");
|
|
3457
|
+
this.program.setWaterShading(batch.material.shading === "water");
|
|
3458
|
+
gl.bindVertexArray(instance.vertexArray);
|
|
3459
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.triangleIndexBuffer);
|
|
3460
|
+
gl.drawElementsInstanced(gl.TRIANGLES, instance.triangleCount, batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0, instance.instanceCount);
|
|
3461
|
+
this.frameStats.drawCalls += 1;
|
|
3462
|
+
if (batch.material.edgeColor && instance.edgeCount > 0) {
|
|
3463
|
+
this.program.setVertexColors(false);
|
|
3464
|
+
this.program.setDirectionalShading(false);
|
|
3465
|
+
this.program.setWaterShading(false);
|
|
3466
|
+
this.applyEdgeMaterial(batch.material);
|
|
3467
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, instance.edgeIndexBuffer);
|
|
3468
|
+
for (const [offsetX, offsetY] of EDGE_PIXEL_OFFSETS) {
|
|
3469
|
+
this.program.setClipOffset(offsetX * clipPixelX, offsetY * clipPixelY);
|
|
3470
|
+
gl.drawElementsInstanced(gl.LINES, instance.edgeCount, batch.geometry.edgeIndices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0, instance.instanceCount);
|
|
3471
|
+
this.frameStats.drawCalls += 1;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
this.program.setInstancing(false);
|
|
1984
3476
|
this.program.setClipOffset(0, 0);
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
this.program.
|
|
1998
|
-
|
|
1999
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
3477
|
+
for (const draw of batches) {
|
|
3478
|
+
if (isTransparentColor(draw.batch.material.color) !== transparentPass)
|
|
3479
|
+
continue;
|
|
3480
|
+
this.program.setClipOffset(0, 0);
|
|
3481
|
+
mvp.elements.set(viewProjection.elements);
|
|
3482
|
+
world.elements.set(draw.worldMatrix);
|
|
3483
|
+
mvp.multiply(world);
|
|
3484
|
+
this.program.setMatrix(mvp);
|
|
3485
|
+
this.applyMaterial(draw.batch.material);
|
|
3486
|
+
const geometry = this.geometryCache.retain(gl, draw.batch.geometry);
|
|
3487
|
+
this.program.setVertexColors(Boolean(draw.batch.geometry.colors));
|
|
3488
|
+
this.program.setDirectionalShading(draw.batch.material.shading !== "flat");
|
|
3489
|
+
this.program.setWaterShading(draw.batch.material.shading === "water");
|
|
3490
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3491
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3492
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, draw.batch.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3493
|
+
this.frameStats.drawCalls += 1;
|
|
3494
|
+
this.frameStats.renderedBatches += 1;
|
|
3495
|
+
if (draw.batch.material.edgeColor) {
|
|
3496
|
+
this.program.setVertexColors(false);
|
|
3497
|
+
this.program.setDirectionalShading(false);
|
|
3498
|
+
this.program.setWaterShading(false);
|
|
3499
|
+
this.applyEdgeMaterial(draw.batch.material);
|
|
3500
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.edgeIndexBuffer);
|
|
3501
|
+
for (const [offsetX, offsetY] of EDGE_PIXEL_OFFSETS) {
|
|
3502
|
+
this.program.setClipOffset(offsetX * clipPixelX, offsetY * clipPixelY);
|
|
3503
|
+
gl.drawElements(gl.LINES, geometry.edgeCount, draw.batch.geometry.edgeIndices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3504
|
+
this.frameStats.drawCalls += 1;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
for (const renderable of renderables) {
|
|
3509
|
+
if (isTransparentColor(renderable.mesh.material.color) !== transparentPass)
|
|
3510
|
+
continue;
|
|
3511
|
+
if (this.instancedMeshIds.has(renderable.id))
|
|
3512
|
+
continue;
|
|
3513
|
+
this.program.setClipOffset(0, 0);
|
|
3514
|
+
mvp.elements.set(viewProjection.elements);
|
|
3515
|
+
world.elements.set(renderable.worldMatrix);
|
|
3516
|
+
mvp.multiply(world);
|
|
3517
|
+
this.program.setMatrix(mvp);
|
|
3518
|
+
this.applyMaterial(renderable.mesh.material);
|
|
3519
|
+
const geometry = this.geometryCache.retain(gl, renderable.mesh.geometry);
|
|
3520
|
+
this.program.setVertexColors(Boolean(renderable.mesh.geometry.colors));
|
|
3521
|
+
this.program.setDirectionalShading(renderable.mesh.material.shading !== "flat");
|
|
3522
|
+
this.program.setWaterShading(renderable.mesh.material.shading === "water");
|
|
3523
|
+
gl.bindVertexArray(geometry.vertexArray);
|
|
3524
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.triangleIndexBuffer);
|
|
3525
|
+
gl.drawElements(gl.TRIANGLES, geometry.triangleCount, renderable.mesh.geometry.indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3526
|
+
this.frameStats.drawCalls += 1;
|
|
3527
|
+
if (renderable.mesh.material.edgeColor) {
|
|
3528
|
+
this.program.setVertexColors(false);
|
|
3529
|
+
this.program.setDirectionalShading(false);
|
|
3530
|
+
this.program.setWaterShading(false);
|
|
3531
|
+
this.applyEdgeMaterial(renderable.mesh.material);
|
|
3532
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.edgeIndexBuffer);
|
|
3533
|
+
for (const [offsetX, offsetY] of EDGE_PIXEL_OFFSETS) {
|
|
3534
|
+
this.program.setClipOffset(offsetX * clipPixelX, offsetY * clipPixelY);
|
|
3535
|
+
gl.drawElements(gl.LINES, geometry.edgeCount, renderable.mesh.geometry.edgeIndices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT, 0);
|
|
3536
|
+
this.frameStats.drawCalls += 1;
|
|
3537
|
+
}
|
|
2004
3538
|
}
|
|
3539
|
+
this.frameStats.renderedMeshes += 1;
|
|
2005
3540
|
}
|
|
2006
|
-
this.frameStats.renderedMeshes += 1;
|
|
2007
3541
|
}
|
|
3542
|
+
gl.depthMask(true);
|
|
3543
|
+
gl.disable(gl.BLEND);
|
|
2008
3544
|
if (frame.selection.length > 0) {
|
|
2009
3545
|
const selected = new Set(frame.selection);
|
|
2010
3546
|
gl.enable(gl.BLEND);
|
|
2011
3547
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
2012
3548
|
gl.depthMask(false);
|
|
3549
|
+
gl.polygonOffset(-1, -4);
|
|
2013
3550
|
this.program.setVertexColors(false);
|
|
3551
|
+
this.program.setDirectionalShading(false);
|
|
2014
3552
|
this.program.setColor([
|
|
2015
3553
|
SELECTION_RGBA[0],
|
|
2016
3554
|
SELECTION_RGBA[1],
|
|
@@ -2056,6 +3594,7 @@ var WebGL2RendererBackend = class {
|
|
|
2056
3594
|
if (this.semanticProgram) {
|
|
2057
3595
|
this.semanticProgram.use();
|
|
2058
3596
|
this.semanticProgram.setInstancing(false);
|
|
3597
|
+
gl.polygonOffset(-1, -4);
|
|
2059
3598
|
for (const draw of batches) {
|
|
2060
3599
|
for (let semanticIndex = 0; semanticIndex < draw.batch.semanticSelectionIds.length; semanticIndex += 1) {
|
|
2061
3600
|
if (!selected.has(draw.batch.semanticSelectionIds[semanticIndex])) {
|
|
@@ -2073,6 +3612,7 @@ var WebGL2RendererBackend = class {
|
|
|
2073
3612
|
}
|
|
2074
3613
|
}
|
|
2075
3614
|
this.semanticProgram.setInstancing(true);
|
|
3615
|
+
gl.polygonOffset(-1, -4);
|
|
2076
3616
|
mvp.elements.set(viewProjection.elements);
|
|
2077
3617
|
this.semanticProgram.setMatrix(mvp);
|
|
2078
3618
|
for (const batch of instances) {
|
|
@@ -2089,6 +3629,7 @@ var WebGL2RendererBackend = class {
|
|
|
2089
3629
|
}
|
|
2090
3630
|
}
|
|
2091
3631
|
gl.depthMask(true);
|
|
3632
|
+
gl.polygonOffset(2, 4);
|
|
2092
3633
|
gl.disable(gl.BLEND);
|
|
2093
3634
|
this.program.use();
|
|
2094
3635
|
this.program.setClipOffset(0, 0);
|
|
@@ -2173,6 +3714,7 @@ var WebGL2RendererBackend = class {
|
|
|
2173
3714
|
this.geometryCache = new WebGL2GeometryCache();
|
|
2174
3715
|
this.instanceCache = new WebGL2InstanceCache();
|
|
2175
3716
|
this.picker = new WebGL2Picker(gl);
|
|
3717
|
+
this.shadowMap = new WebGL2ShadowMap(gl);
|
|
2176
3718
|
}
|
|
2177
3719
|
};
|
|
2178
3720
|
|
|
@@ -2310,8 +3852,8 @@ async function executeProbe(gpu, options) {
|
|
|
2310
3852
|
primitive: { topology: "triangle-list" }
|
|
2311
3853
|
});
|
|
2312
3854
|
const texture = context.getCurrentTexture();
|
|
2313
|
-
const
|
|
2314
|
-
const pass =
|
|
3855
|
+
const encoder2 = device.createCommandEncoder();
|
|
3856
|
+
const pass = encoder2.beginRenderPass({
|
|
2315
3857
|
colorAttachments: [{
|
|
2316
3858
|
view: texture.createView(),
|
|
2317
3859
|
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
@@ -2322,7 +3864,7 @@ async function executeProbe(gpu, options) {
|
|
|
2322
3864
|
pass.setPipeline(pipeline);
|
|
2323
3865
|
pass.draw(3);
|
|
2324
3866
|
pass.end();
|
|
2325
|
-
device.queue.submit([
|
|
3867
|
+
device.queue.submit([encoder2.finish()]);
|
|
2326
3868
|
const lost = await Promise.race([
|
|
2327
3869
|
device.lost.then((info) => info.message || info.reason),
|
|
2328
3870
|
new Promise((resolve) => {
|
|
@@ -2428,9 +3970,13 @@ var WebGPUGeometryCache = class {
|
|
|
2428
3970
|
entries = /* @__PURE__ */ new Map();
|
|
2429
3971
|
active = /* @__PURE__ */ new Set();
|
|
2430
3972
|
pinned = /* @__PURE__ */ new Set();
|
|
3973
|
+
objectKeys = /* @__PURE__ */ new WeakMap();
|
|
3974
|
+
legacyKeys = /* @__PURE__ */ new WeakMap();
|
|
3975
|
+
objectKeySequence = 0;
|
|
2431
3976
|
inactiveGraceFrames;
|
|
2432
3977
|
maxBytes;
|
|
2433
3978
|
uploads = 0;
|
|
3979
|
+
updatedBytes = 0;
|
|
2434
3980
|
evictions = 0;
|
|
2435
3981
|
frameId = 0;
|
|
2436
3982
|
resourceGeneration = 0;
|
|
@@ -2443,12 +3989,23 @@ var WebGPUGeometryCache = class {
|
|
|
2443
3989
|
}
|
|
2444
3990
|
}
|
|
2445
3991
|
retain(geometry) {
|
|
2446
|
-
this.
|
|
2447
|
-
|
|
3992
|
+
const key = this.keyFor(geometry);
|
|
3993
|
+
this.active.add(key);
|
|
3994
|
+
const existing = this.entries.get(key);
|
|
2448
3995
|
if (existing) {
|
|
2449
|
-
existing
|
|
2450
|
-
|
|
3996
|
+
if (this.canReuse(existing, geometry)) {
|
|
3997
|
+
existing.lastUsedFrame = this.frameId;
|
|
3998
|
+
return existing;
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
const next = this.createEntry(geometry, key);
|
|
4002
|
+
if (existing) {
|
|
4003
|
+
this.deleteEntry(existing, false);
|
|
2451
4004
|
}
|
|
4005
|
+
this.entries.set(key, next);
|
|
4006
|
+
return next;
|
|
4007
|
+
}
|
|
4008
|
+
createEntry(geometry, key) {
|
|
2452
4009
|
const buffers = [];
|
|
2453
4010
|
try {
|
|
2454
4011
|
const vertexCount = geometry.positions.length / 3;
|
|
@@ -2473,6 +4030,10 @@ var WebGPUGeometryCache = class {
|
|
|
2473
4030
|
if (semanticBuffer)
|
|
2474
4031
|
buffers.push(semanticBuffer);
|
|
2475
4032
|
const entry = {
|
|
4033
|
+
geometry,
|
|
4034
|
+
geometryId: geometry.identity?.geometryId ?? key,
|
|
4035
|
+
attributeDigest: geometry.identity?.attributeDigest ?? "",
|
|
4036
|
+
topologyDigest: geometry.identity?.topologyDigest ?? "",
|
|
2476
4037
|
vertexBuffer,
|
|
2477
4038
|
triangleIndexBuffer,
|
|
2478
4039
|
edgeIndexBuffer,
|
|
@@ -2484,7 +4045,6 @@ var WebGPUGeometryCache = class {
|
|
|
2484
4045
|
bytes: buffers.reduce((sum, buffer) => sum + buffer.size, 0),
|
|
2485
4046
|
lastUsedFrame: this.frameId
|
|
2486
4047
|
};
|
|
2487
|
-
this.entries.set(geometry, entry);
|
|
2488
4048
|
this.uploads += buffers.length;
|
|
2489
4049
|
this.resourceGeneration += 1;
|
|
2490
4050
|
return entry;
|
|
@@ -2496,19 +4056,25 @@ var WebGPUGeometryCache = class {
|
|
|
2496
4056
|
throw new ICEEngineError("ICE3_GPU_RESOURCE_FAILED", "ICE3_GPU_RESOURCE_FAILED: geometry upload failed", { cause: error });
|
|
2497
4057
|
}
|
|
2498
4058
|
}
|
|
2499
|
-
pin(geometry) {
|
|
2500
|
-
this.
|
|
4059
|
+
pin(geometry, update) {
|
|
4060
|
+
const key = this.keyFor(geometry);
|
|
4061
|
+
this.pinned.add(key);
|
|
4062
|
+
const existing = this.entries.get(key);
|
|
4063
|
+
if (existing && update && this.sameTopology(existing, geometry)) {
|
|
4064
|
+
this.applyPositionUpdate(existing, geometry, update);
|
|
4065
|
+
return existing;
|
|
4066
|
+
}
|
|
2501
4067
|
const entry = this.retain(geometry);
|
|
2502
4068
|
this.enforceBudget();
|
|
2503
4069
|
if (this.totalBytes > this.maxBytes) {
|
|
2504
|
-
this.pinned.delete(
|
|
2505
|
-
this.deleteEntry(
|
|
4070
|
+
this.pinned.delete(key);
|
|
4071
|
+
this.deleteEntry(entry);
|
|
2506
4072
|
throw new ICEEngineError("ICE3_GPU_RESOURCE_FAILED", "ICE3_GPU_RESOURCE_FAILED: geometry exceeds the GPU residency budget");
|
|
2507
4073
|
}
|
|
2508
4074
|
return entry;
|
|
2509
4075
|
}
|
|
2510
4076
|
unpin(geometry) {
|
|
2511
|
-
this.pinned.delete(geometry);
|
|
4077
|
+
this.pinned.delete(this.keyFor(geometry));
|
|
2512
4078
|
}
|
|
2513
4079
|
beginFrame() {
|
|
2514
4080
|
this.frameId += 1;
|
|
@@ -2521,13 +4087,13 @@ var WebGPUGeometryCache = class {
|
|
|
2521
4087
|
if (this.frameId - entry.lastUsedFrame < this.inactiveGraceFrames) {
|
|
2522
4088
|
continue;
|
|
2523
4089
|
}
|
|
2524
|
-
this.deleteEntry(
|
|
4090
|
+
this.deleteEntry(entry);
|
|
2525
4091
|
}
|
|
2526
4092
|
this.enforceBudget();
|
|
2527
4093
|
}
|
|
2528
4094
|
clear() {
|
|
2529
4095
|
for (const [geometry, entry] of this.entries) {
|
|
2530
|
-
this.deleteEntry(
|
|
4096
|
+
this.deleteEntry(entry);
|
|
2531
4097
|
}
|
|
2532
4098
|
this.active.clear();
|
|
2533
4099
|
this.pinned.clear();
|
|
@@ -2541,6 +4107,9 @@ var WebGPUGeometryCache = class {
|
|
|
2541
4107
|
get uploadCount() {
|
|
2542
4108
|
return this.uploads;
|
|
2543
4109
|
}
|
|
4110
|
+
get updateBytes() {
|
|
4111
|
+
return this.updatedBytes;
|
|
4112
|
+
}
|
|
2544
4113
|
get evictionCount() {
|
|
2545
4114
|
return this.evictions;
|
|
2546
4115
|
}
|
|
@@ -2555,20 +4124,120 @@ var WebGPUGeometryCache = class {
|
|
|
2555
4124
|
for (const [geometry, entry] of candidates) {
|
|
2556
4125
|
if (total <= this.maxBytes)
|
|
2557
4126
|
break;
|
|
2558
|
-
this.deleteEntry(
|
|
4127
|
+
this.deleteEntry(entry);
|
|
2559
4128
|
total -= entry.bytes;
|
|
2560
4129
|
}
|
|
2561
4130
|
}
|
|
2562
|
-
deleteEntry(
|
|
4131
|
+
deleteEntry(entry, eviction = true) {
|
|
2563
4132
|
entry.vertexBuffer.destroy();
|
|
2564
4133
|
entry.triangleIndexBuffer.destroy();
|
|
2565
4134
|
entry.edgeIndexBuffer?.destroy();
|
|
2566
4135
|
entry.semanticBuffer?.destroy();
|
|
2567
|
-
this.entries
|
|
2568
|
-
|
|
4136
|
+
for (const [key, candidate] of this.entries) {
|
|
4137
|
+
if (candidate === entry) {
|
|
4138
|
+
this.entries.delete(key);
|
|
4139
|
+
break;
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
if (eviction) {
|
|
4143
|
+
this.evictions += 1;
|
|
4144
|
+
}
|
|
2569
4145
|
this.resourceGeneration += 1;
|
|
2570
4146
|
}
|
|
4147
|
+
applyPositionUpdate(entry, geometry, update) {
|
|
4148
|
+
let updated = 0;
|
|
4149
|
+
try {
|
|
4150
|
+
for (const range of update.positionRanges) {
|
|
4151
|
+
if (range.byteOffset % 12 !== 0 || range.byteLength % 12 !== 0 || range.byteOffset + range.byteLength > geometry.positions.byteLength) {
|
|
4152
|
+
throw new Error("invalid position range");
|
|
4153
|
+
}
|
|
4154
|
+
const vertexOffset = range.byteOffset / 12;
|
|
4155
|
+
const vertexCount = range.byteLength / 12;
|
|
4156
|
+
const vertices = new Float32Array(vertexCount * 6);
|
|
4157
|
+
const totalVertices = geometry.positions.length / 3;
|
|
4158
|
+
const colorStride = geometry.colors ? geometry.colors.length / totalVertices : 0;
|
|
4159
|
+
for (let index = 0; index < vertexCount; index += 1) {
|
|
4160
|
+
const sourceVertex = vertexOffset + index;
|
|
4161
|
+
vertices[index * 6] = geometry.positions[sourceVertex * 3];
|
|
4162
|
+
vertices[index * 6 + 1] = geometry.positions[sourceVertex * 3 + 1];
|
|
4163
|
+
vertices[index * 6 + 2] = geometry.positions[sourceVertex * 3 + 2];
|
|
4164
|
+
vertices[index * 6 + 3] = geometry.colors?.[sourceVertex * colorStride] ?? 1;
|
|
4165
|
+
vertices[index * 6 + 4] = geometry.colors?.[sourceVertex * colorStride + 1] ?? 1;
|
|
4166
|
+
vertices[index * 6 + 5] = geometry.colors?.[sourceVertex * colorStride + 2] ?? 1;
|
|
4167
|
+
}
|
|
4168
|
+
this.device.queue.writeBuffer(entry.vertexBuffer, range.byteOffset * 2, vertices);
|
|
4169
|
+
updated += range.byteLength;
|
|
4170
|
+
}
|
|
4171
|
+
} catch (error) {
|
|
4172
|
+
throw new ICEEngineError("ICE3_GPU_RESOURCE_FAILED", "ICE3_GPU_RESOURCE_FAILED: geometry update failed", { cause: error });
|
|
4173
|
+
}
|
|
4174
|
+
entry.geometry = geometry;
|
|
4175
|
+
entry.lastUsedFrame = this.frameId;
|
|
4176
|
+
this.updatedBytes += updated;
|
|
4177
|
+
}
|
|
4178
|
+
canReuse(existing, geometry) {
|
|
4179
|
+
if (existing.geometry === geometry) {
|
|
4180
|
+
return true;
|
|
4181
|
+
}
|
|
4182
|
+
if (!this.sameTopology(existing, geometry)) {
|
|
4183
|
+
return false;
|
|
4184
|
+
}
|
|
4185
|
+
return (existing.geometry.positions === geometry.positions || equalNumberArrays2(existing.geometry.positions, geometry.positions)) && equalOptionalNumberArrays2(existing.geometry.colors, geometry.colors) && equalOptionalNumberArrays2(existing.geometry.semanticIndices, geometry.semanticIndices) && equalOptionalNumberArrays2(existing.geometry.edgeIndices, geometry.edgeIndices) || existing.attributeDigest !== "" && existing.attributeDigest === geometry.identity?.attributeDigest;
|
|
4186
|
+
}
|
|
4187
|
+
sameTopology(existing, geometry) {
|
|
4188
|
+
return existing.geometry.indices.constructor === geometry.indices.constructor && existing.geometry.indices.length === geometry.indices.length && (existing.geometry.indices === geometry.indices || existing.topologyDigest !== "" && existing.topologyDigest === geometry.identity?.topologyDigest);
|
|
4189
|
+
}
|
|
4190
|
+
keyFor(geometry) {
|
|
4191
|
+
const identity = geometry.identity?.geometryId;
|
|
4192
|
+
if (identity) {
|
|
4193
|
+
return `identity:${identity}`;
|
|
4194
|
+
}
|
|
4195
|
+
const id = geometry.id;
|
|
4196
|
+
if (typeof id === "string" && id.trim()) {
|
|
4197
|
+
const cached = this.legacyKeys.get(geometry);
|
|
4198
|
+
if (cached)
|
|
4199
|
+
return cached;
|
|
4200
|
+
const key2 = [
|
|
4201
|
+
"content",
|
|
4202
|
+
geometry.positions.length,
|
|
4203
|
+
geometry.indices.length,
|
|
4204
|
+
contentFingerprint2(geometry.positions),
|
|
4205
|
+
contentFingerprint2(geometry.indices),
|
|
4206
|
+
geometry.colors ? contentFingerprint2(geometry.colors) : 0,
|
|
4207
|
+
geometry.edgeIndices ? contentFingerprint2(geometry.edgeIndices) : 0,
|
|
4208
|
+
geometry.semanticIndices ? contentFingerprint2(geometry.semanticIndices) : 0
|
|
4209
|
+
].join(":");
|
|
4210
|
+
this.legacyKeys.set(geometry, key2);
|
|
4211
|
+
return key2;
|
|
4212
|
+
}
|
|
4213
|
+
const existing = this.objectKeys.get(geometry);
|
|
4214
|
+
if (existing) {
|
|
4215
|
+
return existing;
|
|
4216
|
+
}
|
|
4217
|
+
const key = `object:${this.objectKeySequence}`;
|
|
4218
|
+
this.objectKeySequence += 1;
|
|
4219
|
+
this.objectKeys.set(geometry, key);
|
|
4220
|
+
return key;
|
|
4221
|
+
}
|
|
2571
4222
|
};
|
|
4223
|
+
function contentFingerprint2(values) {
|
|
4224
|
+
let hash = 2166136261;
|
|
4225
|
+
const bytes = new Uint8Array(values.buffer, values.byteOffset, values.byteLength);
|
|
4226
|
+
for (const byte of bytes) {
|
|
4227
|
+
hash ^= byte;
|
|
4228
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
4229
|
+
}
|
|
4230
|
+
return hash;
|
|
4231
|
+
}
|
|
4232
|
+
function equalNumberArrays2(left, right) {
|
|
4233
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
4234
|
+
}
|
|
4235
|
+
function equalOptionalNumberArrays2(left, right) {
|
|
4236
|
+
if (left === void 0 || right === void 0) {
|
|
4237
|
+
return left === right;
|
|
4238
|
+
}
|
|
4239
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
4240
|
+
}
|
|
2572
4241
|
|
|
2573
4242
|
// packages/renderer/dist/WebGPUInstanceCache.js
|
|
2574
4243
|
var WebGPUInstanceCache = class {
|
|
@@ -2664,7 +4333,7 @@ function sameBytes(left, right) {
|
|
|
2664
4333
|
|
|
2665
4334
|
// packages/renderer/dist/WebGPURenderPipeline.js
|
|
2666
4335
|
var WEBGPU_UNIFORM_STRIDE = 256;
|
|
2667
|
-
var WEBGPU_UNIFORM_BYTES =
|
|
4336
|
+
var WEBGPU_UNIFORM_BYTES = 144;
|
|
2668
4337
|
var SHADER_SOURCE = (selectionMask, storageInstances, indirectInstances) => `
|
|
2669
4338
|
struct FrameUniforms {
|
|
2670
4339
|
worldViewProjection: mat4x4<f32>,
|
|
@@ -2680,6 +4349,7 @@ struct FrameUniforms {
|
|
|
2680
4349
|
useSelectionMask: f32,
|
|
2681
4350
|
pageBase: f32,
|
|
2682
4351
|
useIndirect: f32,
|
|
4352
|
+
useLighting: f32,
|
|
2683
4353
|
}
|
|
2684
4354
|
|
|
2685
4355
|
${storageInstances ? `
|
|
@@ -2696,6 +4366,7 @@ struct VertexOutput {
|
|
|
2696
4366
|
@builtin(position) position: vec4<f32>,
|
|
2697
4367
|
@location(0) color: vec4<f32>,
|
|
2698
4368
|
@location(1) @interpolate(flat) semantic: u32,
|
|
4369
|
+
@location(2) worldPosition: vec4<f32>,
|
|
2699
4370
|
}
|
|
2700
4371
|
|
|
2701
4372
|
@group(0) @binding(0) var<uniform> frame: FrameUniforms;
|
|
@@ -2748,6 +4419,7 @@ fn vertexMain(
|
|
|
2748
4419
|
mix(vec3<f32>(1.0), color, frame.useVertexColor) * frame.color.rgb,
|
|
2749
4420
|
frame.color.a
|
|
2750
4421
|
);
|
|
4422
|
+
output.worldPosition = worldPosition;
|
|
2751
4423
|
output.semantic = select(
|
|
2752
4424
|
vertexSemantic,
|
|
2753
4425
|
instanceSemantic,
|
|
@@ -2791,7 +4463,24 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
|
2791
4463
|
);
|
|
2792
4464
|
}
|
|
2793
4465
|
}` : ""}
|
|
2794
|
-
|
|
4466
|
+
var outputColor = input.color;
|
|
4467
|
+
if (frame.useLighting > 0.5) {
|
|
4468
|
+
let normal = normalize(cross(
|
|
4469
|
+
dpdx(input.worldPosition.xyz),
|
|
4470
|
+
dpdy(input.worldPosition.xyz)
|
|
4471
|
+
));
|
|
4472
|
+
let lightDirection = normalize(vec3<f32>(-0.35, 0.85, 0.4));
|
|
4473
|
+
let diffuse = dot(normal, lightDirection);
|
|
4474
|
+
let key = max(diffuse, 0.0) + 0.35 * max(-diffuse, 0.0);
|
|
4475
|
+
let sky = 0.5 + 0.5 * normal.y;
|
|
4476
|
+
let shade = clamp(
|
|
4477
|
+
0.72 + 0.72 * key + 0.18 * sky,
|
|
4478
|
+
0.92,
|
|
4479
|
+
1.42
|
|
4480
|
+
);
|
|
4481
|
+
outputColor = vec4<f32>(outputColor.rgb * shade, outputColor.a);
|
|
4482
|
+
}
|
|
4483
|
+
return outputColor;
|
|
2795
4484
|
}
|
|
2796
4485
|
`;
|
|
2797
4486
|
var VERTEX_LAYOUT = [
|
|
@@ -2946,7 +4635,14 @@ var WebGPURenderPipeline = class _WebGPURenderPipeline {
|
|
|
2946
4635
|
depthStencil: {
|
|
2947
4636
|
format: "depth24plus",
|
|
2948
4637
|
depthCompare: "less-equal",
|
|
2949
|
-
depthWriteEnabled: !selection
|
|
4638
|
+
depthWriteEnabled: !selection,
|
|
4639
|
+
// Selected triangles are redrawn after opaque geometry. Pull that
|
|
4640
|
+
// overlay slightly forward instead of competing at an equal depth.
|
|
4641
|
+
...selection && topology === "triangle-list" ? {
|
|
4642
|
+
depthBias: -1,
|
|
4643
|
+
depthBiasSlopeScale: -1,
|
|
4644
|
+
depthBiasClamp: 0
|
|
4645
|
+
} : {}
|
|
2950
4646
|
},
|
|
2951
4647
|
multisample: { count: 1 }
|
|
2952
4648
|
});
|
|
@@ -2995,12 +4691,12 @@ var WebGPUTimestampProfiler = class {
|
|
|
2995
4691
|
querySetFor(frameId) {
|
|
2996
4692
|
return this.slotFor(frameId).querySet;
|
|
2997
4693
|
}
|
|
2998
|
-
resolveFrame(
|
|
4694
|
+
resolveFrame(encoder2, frameId) {
|
|
2999
4695
|
const slot = this.slotFor(frameId);
|
|
3000
4696
|
if (slot.pending) {
|
|
3001
4697
|
throw new Error("ICE3_TIMESTAMP_SLOT_BUSY");
|
|
3002
4698
|
}
|
|
3003
|
-
|
|
4699
|
+
encoder2.resolveQuerySet(slot.querySet, 0, 2, slot.buffer, 0);
|
|
3004
4700
|
this.pending.push(this.slotIndex(frameId));
|
|
3005
4701
|
slot.pending = true;
|
|
3006
4702
|
}
|
|
@@ -3168,6 +4864,7 @@ var WebGPUDrawStorage = class {
|
|
|
3168
4864
|
this.uniformBuffer[offset + 29] = draw.useSelectionMask ? 1 : 0;
|
|
3169
4865
|
this.uniformBuffer[offset + 30] = draw.pageBase ?? 0;
|
|
3170
4866
|
this.uniformBuffer[offset + 31] = draw.useIndirect ? 1 : 0;
|
|
4867
|
+
this.uniformBuffer[offset + 32] = draw.useLighting ? 1 : 0;
|
|
3171
4868
|
this.count += 1;
|
|
3172
4869
|
return index;
|
|
3173
4870
|
}
|
|
@@ -3252,13 +4949,13 @@ var WebGPUBundleCache = class {
|
|
|
3252
4949
|
get(key, record3) {
|
|
3253
4950
|
if (this.key === key && this.bundle)
|
|
3254
4951
|
return this.bundle;
|
|
3255
|
-
const
|
|
4952
|
+
const encoder2 = this.device.createRenderBundleEncoder({
|
|
3256
4953
|
label: `ice3:bundle:${key}`,
|
|
3257
4954
|
colorFormats: [this.format],
|
|
3258
4955
|
depthStencilFormat: "depth24plus"
|
|
3259
4956
|
});
|
|
3260
|
-
record3(
|
|
3261
|
-
const bundle =
|
|
4957
|
+
record3(encoder2);
|
|
4958
|
+
const bundle = encoder2.finish();
|
|
3262
4959
|
this.key = key;
|
|
3263
4960
|
this.bundle = bundle;
|
|
3264
4961
|
this.recreations += 1;
|
|
@@ -3596,8 +5293,8 @@ var WebGPUVisibilityEncoder = class _WebGPUVisibilityEncoder {
|
|
|
3596
5293
|
this.entries.set(page.buffer, entries);
|
|
3597
5294
|
return entry;
|
|
3598
5295
|
}
|
|
3599
|
-
encode(
|
|
3600
|
-
|
|
5296
|
+
encode(encoder2, entry, page, uniformBuffer, uniformOffset) {
|
|
5297
|
+
encoder2.clearBuffer(entry.indirectBuffer, 4, 4);
|
|
3601
5298
|
const bindGroup = this.device.createBindGroup({
|
|
3602
5299
|
label: "ice3:visibility-bindings",
|
|
3603
5300
|
layout: this.bindGroupLayout,
|
|
@@ -3628,7 +5325,7 @@ var WebGPUVisibilityEncoder = class _WebGPUVisibilityEncoder {
|
|
|
3628
5325
|
}
|
|
3629
5326
|
]
|
|
3630
5327
|
});
|
|
3631
|
-
const pass =
|
|
5328
|
+
const pass = encoder2.beginComputePass({ label: "ice3:visibility-pass" });
|
|
3632
5329
|
pass.setPipeline(this.pipeline);
|
|
3633
5330
|
pass.setBindGroup(0, bindGroup, [uniformOffset]);
|
|
3634
5331
|
pass.dispatchWorkgroups(Math.ceil(page.instanceCount / 64));
|
|
@@ -3640,9 +5337,9 @@ var WebGPUVisibilityEncoder = class _WebGPUVisibilityEncoder {
|
|
|
3640
5337
|
size: 4,
|
|
3641
5338
|
usage: 1 | 8
|
|
3642
5339
|
});
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3645
|
-
this.device.queue.submit([
|
|
5340
|
+
const encoder2 = this.device.createCommandEncoder();
|
|
5341
|
+
encoder2.copyBufferToBuffer(entry.indirectBuffer, 4, readback, 0, 4);
|
|
5342
|
+
this.device.queue.submit([encoder2.finish()]);
|
|
3646
5343
|
try {
|
|
3647
5344
|
await readback.mapAsync(1);
|
|
3648
5345
|
const visibleInstances = new Uint32Array(readback.getMappedRange())[0];
|
|
@@ -3815,8 +5512,8 @@ var WebGPUPicker = class {
|
|
|
3815
5512
|
}
|
|
3816
5513
|
}]
|
|
3817
5514
|
});
|
|
3818
|
-
const
|
|
3819
|
-
const pass =
|
|
5515
|
+
const encoder2 = this.device.createCommandEncoder();
|
|
5516
|
+
const pass = encoder2.beginRenderPass({
|
|
3820
5517
|
colorAttachments: [{
|
|
3821
5518
|
view: this.target.createView(),
|
|
3822
5519
|
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
@@ -3848,7 +5545,7 @@ var WebGPUPicker = class {
|
|
|
3848
5545
|
size: 256,
|
|
3849
5546
|
usage: COPY_DST | MAP_READ
|
|
3850
5547
|
});
|
|
3851
|
-
|
|
5548
|
+
encoder2.copyTextureToBuffer({
|
|
3852
5549
|
texture: this.target,
|
|
3853
5550
|
origin: {
|
|
3854
5551
|
x: pixelX,
|
|
@@ -3860,7 +5557,7 @@ var WebGPUPicker = class {
|
|
|
3860
5557
|
bytesPerRow: 256,
|
|
3861
5558
|
rowsPerImage: 1
|
|
3862
5559
|
}, { width: 1, height: 1, depthOrArrayLayers: 1 });
|
|
3863
|
-
this.device.queue.submit([
|
|
5560
|
+
this.device.queue.submit([encoder2.finish()]);
|
|
3864
5561
|
let pixel;
|
|
3865
5562
|
try {
|
|
3866
5563
|
await readback.mapAsync(MAP_READ);
|
|
@@ -4214,6 +5911,12 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4214
5911
|
prepareBatchGeometry(geometry) {
|
|
4215
5912
|
this.requireGeometryCache().pin(geometry);
|
|
4216
5913
|
}
|
|
5914
|
+
prepareGeometry(geometry, update) {
|
|
5915
|
+
this.requireGeometryCache().pin(geometry, update);
|
|
5916
|
+
}
|
|
5917
|
+
releaseGeometry(geometry) {
|
|
5918
|
+
this.geometryCache?.unpin(geometry);
|
|
5919
|
+
}
|
|
4217
5920
|
releaseBatchGeometry(geometry) {
|
|
4218
5921
|
this.geometryCache?.unpin(geometry);
|
|
4219
5922
|
}
|
|
@@ -4341,6 +6044,7 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4341
6044
|
color: colorToRgba(batch.material.color),
|
|
4342
6045
|
useVertexColor: Boolean(batch.geometry.colors),
|
|
4343
6046
|
useInstancing: true,
|
|
6047
|
+
useLighting: batch.material.shading === "directional" || batch.material.shading === "water",
|
|
4344
6048
|
pipeline: pagePipeline.triangle,
|
|
4345
6049
|
useSelectionMask: selectionMaskEnabled,
|
|
4346
6050
|
storagePage: page,
|
|
@@ -4368,6 +6072,7 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4368
6072
|
color: colorToRgba(batch.material.color),
|
|
4369
6073
|
useVertexColor: Boolean(batch.geometry.colors),
|
|
4370
6074
|
useInstancing: true,
|
|
6075
|
+
useLighting: batch.material.shading === "directional" || batch.material.shading === "water",
|
|
4371
6076
|
pipeline: this.pipeline.triangle,
|
|
4372
6077
|
useSelectionMask: selectionMaskEnabled
|
|
4373
6078
|
}, viewProjection.elements, batch.semanticIds);
|
|
@@ -4412,6 +6117,7 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4412
6117
|
color: colorToRgba(draw.batch.material.color),
|
|
4413
6118
|
useVertexColor: Boolean(draw.batch.geometry.colors),
|
|
4414
6119
|
useInstancing: false,
|
|
6120
|
+
useLighting: draw.batch.material.shading === "directional" || draw.batch.material.shading === "water",
|
|
4415
6121
|
pipeline: this.pipeline.triangle,
|
|
4416
6122
|
useSelectionMask: selectionMaskEnabled
|
|
4417
6123
|
}, matrix, draw.batch.semanticSelectionIds);
|
|
@@ -4459,6 +6165,7 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4459
6165
|
color: colorToRgba(renderable.mesh.material.color),
|
|
4460
6166
|
useVertexColor: Boolean(renderable.mesh.geometry.colors),
|
|
4461
6167
|
useInstancing: false,
|
|
6168
|
+
useLighting: renderable.mesh.material.shading === "directional" || renderable.mesh.material.shading === "water",
|
|
4462
6169
|
pipeline: this.pipeline.triangle,
|
|
4463
6170
|
useSelectionMask: selectionMaskEnabled
|
|
4464
6171
|
}, matrix, [renderable.mesh.selectionId]);
|
|
@@ -4527,26 +6234,26 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4527
6234
|
this.frameBindGroupKey = bindGroupKey;
|
|
4528
6235
|
}
|
|
4529
6236
|
const bindGroup = this.frameBindGroup;
|
|
4530
|
-
const encodeDraws = (
|
|
6237
|
+
const encodeDraws = (encoder3) => {
|
|
4531
6238
|
for (let index = 0; index < drawList.length; index += 1) {
|
|
4532
6239
|
const draw = drawList[index];
|
|
4533
|
-
|
|
6240
|
+
encoder3.setPipeline(draw.pipeline);
|
|
4534
6241
|
const drawBindGroup = draw.storagePage ? this.storageBindGroupFor(draw.storagePage, draw.visibilityEntry?.visibleBuffer) : bindGroup;
|
|
4535
|
-
|
|
6242
|
+
encoder3.setBindGroup(0, drawBindGroup, [
|
|
4536
6243
|
index * WEBGPU_UNIFORM_STRIDE
|
|
4537
6244
|
]);
|
|
4538
|
-
|
|
6245
|
+
encoder3.setVertexBuffer(0, draw.geometry.vertexBuffer);
|
|
4539
6246
|
if (draw.storagePage) {
|
|
4540
|
-
|
|
6247
|
+
encoder3.setVertexBuffer(1, draw.semanticBuffer);
|
|
4541
6248
|
} else {
|
|
4542
|
-
|
|
4543
|
-
|
|
6249
|
+
encoder3.setVertexBuffer(1, draw.instanceBuffer);
|
|
6250
|
+
encoder3.setVertexBuffer(2, draw.semanticBuffer);
|
|
4544
6251
|
}
|
|
4545
|
-
|
|
6252
|
+
encoder3.setIndexBuffer(draw.edge ? draw.geometry.edgeIndexBuffer : draw.geometry.triangleIndexBuffer, draw.geometry.indexFormat);
|
|
4546
6253
|
if (draw.visibilityEntry) {
|
|
4547
|
-
|
|
6254
|
+
encoder3.drawIndexedIndirect(draw.visibilityEntry.indirectBuffer, 0);
|
|
4548
6255
|
} else {
|
|
4549
|
-
|
|
6256
|
+
encoder3.drawIndexed(draw.edge ? draw.geometry.edgeCount : draw.geometry.triangleCount, draw.instanceCount, 0, 0, draw.storagePage?.firstInstance ?? 0);
|
|
4550
6257
|
}
|
|
4551
6258
|
}
|
|
4552
6259
|
};
|
|
@@ -4572,7 +6279,7 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4572
6279
|
}
|
|
4573
6280
|
}
|
|
4574
6281
|
const targetTexture = context.getCurrentTexture();
|
|
4575
|
-
const
|
|
6282
|
+
const encoder2 = this.device.createCommandEncoder();
|
|
4576
6283
|
if (this.gpuDrivenActive) {
|
|
4577
6284
|
const encodedVisibility = /* @__PURE__ */ new Set();
|
|
4578
6285
|
for (let index = 0; index < drawList.length; index += 1) {
|
|
@@ -4581,12 +6288,12 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4581
6288
|
continue;
|
|
4582
6289
|
}
|
|
4583
6290
|
encodedVisibility.add(draw.visibilityEntry);
|
|
4584
|
-
this.visibilityEncoder.encode(
|
|
6291
|
+
this.visibilityEncoder.encode(encoder2, draw.visibilityEntry, draw.storagePage, this.uniformBuffer, index * WEBGPU_UNIFORM_STRIDE);
|
|
4585
6292
|
}
|
|
4586
6293
|
}
|
|
4587
6294
|
const timestampProfiler = this.captureRequest || !this.timestampProfilingEnabled ? null : this.timestampProfiler;
|
|
4588
6295
|
const timestampQuerySet = timestampProfiler?.querySetFor(frame.frameId);
|
|
4589
|
-
const pass =
|
|
6296
|
+
const pass = encoder2.beginRenderPass({
|
|
4590
6297
|
colorAttachments: [{
|
|
4591
6298
|
view: targetTexture.createView(),
|
|
4592
6299
|
clearValue: {
|
|
@@ -4627,10 +6334,10 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4627
6334
|
this.frameStats.drawCalls += drawList.length;
|
|
4628
6335
|
pass.end();
|
|
4629
6336
|
if (timestampProfiler) {
|
|
4630
|
-
timestampProfiler.resolveFrame(
|
|
6337
|
+
timestampProfiler.resolveFrame(encoder2, frame.frameId);
|
|
4631
6338
|
}
|
|
4632
6339
|
if (this.captureRequest) {
|
|
4633
|
-
|
|
6340
|
+
encoder2.copyTextureToBuffer({ texture: targetTexture }, {
|
|
4634
6341
|
buffer: this.captureRequest.buffer,
|
|
4635
6342
|
bytesPerRow: this.captureRequest.bytesPerRow,
|
|
4636
6343
|
rowsPerImage: this.canvas.height
|
|
@@ -4641,7 +6348,7 @@ var WebGPURendererBackend = class _WebGPURendererBackend {
|
|
|
4641
6348
|
});
|
|
4642
6349
|
this.captureRequest = null;
|
|
4643
6350
|
}
|
|
4644
|
-
const commandBuffer =
|
|
6351
|
+
const commandBuffer = encoder2.finish();
|
|
4645
6352
|
this.profiler.mark("encode");
|
|
4646
6353
|
this.device.queue.submit([commandBuffer]);
|
|
4647
6354
|
this.profiler.mark("submit");
|
|
@@ -5493,18 +7200,18 @@ var ICEInputRouter = class {
|
|
|
5493
7200
|
function pickSceneNode(scene, camera, viewport, point) {
|
|
5494
7201
|
const eye = camera.getPosition();
|
|
5495
7202
|
const target = camera.getTarget();
|
|
5496
|
-
const forward =
|
|
7203
|
+
const forward = normalize3([
|
|
5497
7204
|
target.x - eye.x,
|
|
5498
7205
|
target.y - eye.y,
|
|
5499
7206
|
target.z - eye.z
|
|
5500
7207
|
]);
|
|
5501
|
-
const right =
|
|
5502
|
-
const up =
|
|
7208
|
+
const right = normalize3(cross2(forward, [0, 1, 0]));
|
|
7209
|
+
const up = normalize3(cross2(right, forward));
|
|
5503
7210
|
const aspect = viewport.width / Math.max(viewport.height, 1);
|
|
5504
7211
|
const tangent = Math.tan(camera.fov * Math.PI / 360);
|
|
5505
7212
|
const ndcX = point.x / viewport.width * 2 - 1;
|
|
5506
7213
|
const ndcY = 1 - point.y / viewport.height * 2;
|
|
5507
|
-
const direction =
|
|
7214
|
+
const direction = normalize3([
|
|
5508
7215
|
forward[0] + right[0] * ndcX * aspect * tangent + up[0] * ndcY * tangent,
|
|
5509
7216
|
forward[1] + right[1] * ndcX * aspect * tangent + up[1] * ndcY * tangent,
|
|
5510
7217
|
forward[2] + right[2] * ndcX * aspect * tangent + up[2] * ndcY * tangent
|
|
@@ -5537,7 +7244,7 @@ function pickSceneNode(scene, camera, viewport, point) {
|
|
|
5537
7244
|
visit2(scene, true);
|
|
5538
7245
|
return nearest;
|
|
5539
7246
|
}
|
|
5540
|
-
var
|
|
7247
|
+
var normalize3 = (value) => {
|
|
5541
7248
|
const length = Math.hypot(...value);
|
|
5542
7249
|
return length > 0 ? [value[0] / length, value[1] / length, value[2] / length] : [0, 0, 0];
|
|
5543
7250
|
};
|
|
@@ -5847,6 +7554,7 @@ var ICE3 = class extends ICEEventDispatcher {
|
|
|
5847
7554
|
generatedOverlayCanvas = false;
|
|
5848
7555
|
scene = null;
|
|
5849
7556
|
camera = null;
|
|
7557
|
+
clipPlane = [0, 0, 0, 0];
|
|
5850
7558
|
viewport = { x: 0, y: 0, width: 1, height: 1 };
|
|
5851
7559
|
dpr = 1;
|
|
5852
7560
|
frameId = 0;
|
|
@@ -5942,6 +7650,10 @@ var ICE3 = class extends ICEEventDispatcher {
|
|
|
5942
7650
|
if (this.state === "destroyed")
|
|
5943
7651
|
return;
|
|
5944
7652
|
this.recordDiagnostic(error.code, error.message, "error");
|
|
7653
|
+
},
|
|
7654
|
+
onUploadReady: () => {
|
|
7655
|
+
if (this.state === "running")
|
|
7656
|
+
this.requestRender("upload-ready");
|
|
5945
7657
|
}
|
|
5946
7658
|
});
|
|
5947
7659
|
window.addEventListener("resize", this.handleWindowResize);
|
|
@@ -5986,6 +7698,17 @@ var ICE3 = class extends ICEEventDispatcher {
|
|
|
5986
7698
|
this.requestRender("background-color");
|
|
5987
7699
|
return this;
|
|
5988
7700
|
}
|
|
7701
|
+
/**
|
|
7702
|
+
* Section plane `[nx, ny, nz, d]`: fragments with `dot(world, n) + d < 0`
|
|
7703
|
+
* are discarded. `null` (or a zero plane) disables clipping.
|
|
7704
|
+
*/
|
|
7705
|
+
setClipPlane(plane) {
|
|
7706
|
+
this.assertActive();
|
|
7707
|
+
this.clipPlane = plane ? [plane[0], plane[1], plane[2], plane[3]] : [0, 0, 0, 0];
|
|
7708
|
+
this.backend.setClipPlane?.(this.clipPlane);
|
|
7709
|
+
this.requestRender("clip-plane");
|
|
7710
|
+
return this;
|
|
7711
|
+
}
|
|
5989
7712
|
prepareRenderBatchGeometry(geometry) {
|
|
5990
7713
|
if (!this.backend.prepareBatchGeometry) {
|
|
5991
7714
|
throw new ICEEngineError("ICE3_RENDER_BATCH_ADAPTER_UNAVAILABLE", "The active renderer cannot stage adaptive batch geometry");
|
|
@@ -5997,6 +7720,17 @@ var ICE3 = class extends ICEEventDispatcher {
|
|
|
5997
7720
|
this.backend.releaseBatchGeometry?.(geometry);
|
|
5998
7721
|
return this;
|
|
5999
7722
|
}
|
|
7723
|
+
prepareGeometry(geometry, update) {
|
|
7724
|
+
if (!this.backend.prepareGeometry) {
|
|
7725
|
+
throw new ICEEngineError("ICE3_RENDER_GEOMETRY_UNAVAILABLE", "The active renderer cannot stage geometry updates");
|
|
7726
|
+
}
|
|
7727
|
+
this.backend.prepareGeometry(geometry, update);
|
|
7728
|
+
return this;
|
|
7729
|
+
}
|
|
7730
|
+
releaseGeometry(geometry) {
|
|
7731
|
+
this.backend.releaseGeometry?.(geometry);
|
|
7732
|
+
return this;
|
|
7733
|
+
}
|
|
6000
7734
|
addLayer(layer) {
|
|
6001
7735
|
this.assertActive();
|
|
6002
7736
|
if (this.layers.some((item) => item.id === layer.id)) {
|
|
@@ -6460,7 +8194,11 @@ var MAX_FRAME_DELTA_MS = 64;
|
|
|
6460
8194
|
var SETTLE_EPSILON = 1e-5;
|
|
6461
8195
|
var WHEEL_LINE_HEIGHT_PX = 16;
|
|
6462
8196
|
var MAX_WHEEL_STEP_PX = 240;
|
|
6463
|
-
var
|
|
8197
|
+
var ZOOM_DISTANCE_PER_HOME_PIXEL = 1e-3;
|
|
8198
|
+
var PAN_SPEED_PER_HOME_DISTANCE = 0.75;
|
|
8199
|
+
var MIN_KEY_TAP_MS = 16;
|
|
8200
|
+
var MIN_ZOOM_FRACTION = 5e-3;
|
|
8201
|
+
var ABSOLUTE_MIN_ZOOM_DISTANCE = 0.2;
|
|
6464
8202
|
var NAVIGATION_KEYS = /* @__PURE__ */ new Set([
|
|
6465
8203
|
"KeyW",
|
|
6466
8204
|
"KeyA",
|
|
@@ -6478,6 +8216,8 @@ var ICEOrbitController = class {
|
|
|
6478
8216
|
yaw;
|
|
6479
8217
|
pitch;
|
|
6480
8218
|
distance;
|
|
8219
|
+
minDistance;
|
|
8220
|
+
maxDistance;
|
|
6481
8221
|
targetYaw;
|
|
6482
8222
|
targetPitch;
|
|
6483
8223
|
targetDistance;
|
|
@@ -6559,7 +8299,12 @@ var ICEOrbitController = class {
|
|
|
6559
8299
|
return;
|
|
6560
8300
|
}
|
|
6561
8301
|
this.prepareMotion();
|
|
8302
|
+
const alreadyPressed = this.pressedKeys.has(input.code);
|
|
6562
8303
|
this.pressedKeys.add(input.code);
|
|
8304
|
+
if (!alreadyPressed && !input.repeat) {
|
|
8305
|
+
this.applyKeyboardMovement(MIN_KEY_TAP_MS);
|
|
8306
|
+
this.applyPosition();
|
|
8307
|
+
}
|
|
6563
8308
|
this.engine.requestRender("camera-keyboard-movement");
|
|
6564
8309
|
};
|
|
6565
8310
|
onKeyUp = (event) => {
|
|
@@ -6603,7 +8348,9 @@ var ICEOrbitController = class {
|
|
|
6603
8348
|
const offsetY = eye.y - target.y;
|
|
6604
8349
|
const offsetZ = eye.z - target.z;
|
|
6605
8350
|
this.target = target.toArray();
|
|
6606
|
-
this.distance = Math.max(
|
|
8351
|
+
this.distance = Math.max(ABSOLUTE_MIN_ZOOM_DISTANCE, Math.hypot(offsetX, offsetY, offsetZ));
|
|
8352
|
+
this.maxDistance = this.distance;
|
|
8353
|
+
this.minDistance = Math.min(this.maxDistance, Math.max(ABSOLUTE_MIN_ZOOM_DISTANCE, this.maxDistance * MIN_ZOOM_FRACTION));
|
|
6607
8354
|
this.yaw = Math.atan2(offsetX, offsetZ);
|
|
6608
8355
|
this.pitch = Math.asin(clamp(offsetY / this.distance, -1, 1));
|
|
6609
8356
|
this.targetYaw = this.yaw;
|
|
@@ -6623,7 +8370,7 @@ var ICEOrbitController = class {
|
|
|
6623
8370
|
];
|
|
6624
8371
|
const radius = Math.max(0.1, Math.hypot(target.bounds.max[0] - target.bounds.min[0], target.bounds.max[1] - target.bounds.min[1], target.bounds.max[2] - target.bounds.min[2]) / 2);
|
|
6625
8372
|
this.target = center;
|
|
6626
|
-
this.targetDistance = radius / Math.sin(this.camera.fov * Math.PI / 360) * padding;
|
|
8373
|
+
this.targetDistance = clamp(radius / Math.sin(this.camera.fov * Math.PI / 360) * padding, this.minDistance, this.maxDistance);
|
|
6627
8374
|
if (options.animate === false) {
|
|
6628
8375
|
this.distance = this.targetDistance;
|
|
6629
8376
|
this.applyPosition();
|
|
@@ -6642,7 +8389,7 @@ var ICEOrbitController = class {
|
|
|
6642
8389
|
zoomBy(delta) {
|
|
6643
8390
|
this.assertActive();
|
|
6644
8391
|
this.prepareMotion();
|
|
6645
|
-
this.targetDistance = clamp(this.targetDistance *
|
|
8392
|
+
this.targetDistance = clamp(this.targetDistance + delta * this.maxDistance * ZOOM_DISTANCE_PER_HOME_PIXEL, this.minDistance, this.maxDistance);
|
|
6646
8393
|
this.engine.requestRender("camera-zoom");
|
|
6647
8394
|
return this;
|
|
6648
8395
|
}
|
|
@@ -6688,7 +8435,7 @@ var ICEOrbitController = class {
|
|
|
6688
8435
|
if (magnitude === 0) {
|
|
6689
8436
|
return;
|
|
6690
8437
|
}
|
|
6691
|
-
const distance2 = this.
|
|
8438
|
+
const distance2 = this.maxDistance * PAN_SPEED_PER_HOME_DISTANCE * (deltaMs / 1e3) / magnitude;
|
|
6692
8439
|
const sinYaw = Math.sin(this.yaw);
|
|
6693
8440
|
const cosYaw = Math.cos(this.yaw);
|
|
6694
8441
|
const sinPitch = Math.sin(this.pitch);
|
|
@@ -6754,7 +8501,14 @@ function buildAssetScene(snapshot) {
|
|
|
6754
8501
|
}));
|
|
6755
8502
|
const materials = snapshot.materials.map((material) => new ICEBasicMaterial({
|
|
6756
8503
|
color: material.baseColor,
|
|
6757
|
-
...material.edgeColor ? { edgeColor: material.edgeColor } : {}
|
|
8504
|
+
...material.edgeColor ? { edgeColor: material.edgeColor } : {},
|
|
8505
|
+
...material.shading ? { shading: material.shading } : {},
|
|
8506
|
+
...material.map ? { map: material.map } : {},
|
|
8507
|
+
...material.normalMap ? { normalMap: material.normalMap } : {},
|
|
8508
|
+
...material.ormMap ? { ormMap: material.ormMap } : {},
|
|
8509
|
+
...material.metallic !== void 0 ? { metallic: material.metallic } : {},
|
|
8510
|
+
...material.roughness !== void 0 ? { roughness: material.roughness } : {},
|
|
8511
|
+
...material.emissive ? { emissive: material.emissive } : {}
|
|
6758
8512
|
}));
|
|
6759
8513
|
const meshesByNode = /* @__PURE__ */ new Map();
|
|
6760
8514
|
for (const mesh of snapshot.meshes) {
|
|
@@ -6829,8 +8583,123 @@ function buildAssetScene(snapshot) {
|
|
|
6829
8583
|
stats: snapshot.stats
|
|
6830
8584
|
};
|
|
6831
8585
|
}
|
|
8586
|
+
function buildPreviewPacketScene(packet, previousGeometries = /* @__PURE__ */ new Map()) {
|
|
8587
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
8588
|
+
const geometryUpdates = /* @__PURE__ */ new Map();
|
|
8589
|
+
for (const source of packet.geometries) {
|
|
8590
|
+
const previous = previousGeometries.get(source.geometryId);
|
|
8591
|
+
if (previous && previous.identity.topologyDigest === source.topologyDigest && previous.identity.attributeDigest === source.attributeDigest) {
|
|
8592
|
+
geometries.set(source.geometryId, previous);
|
|
8593
|
+
continue;
|
|
8594
|
+
}
|
|
8595
|
+
const identity = Object.freeze({
|
|
8596
|
+
geometryId: source.geometryId,
|
|
8597
|
+
topologyDigest: source.topologyDigest,
|
|
8598
|
+
attributeDigest: source.attributeDigest
|
|
8599
|
+
});
|
|
8600
|
+
const geometry = new ICEIndexedGeometry({
|
|
8601
|
+
id: source.residentGeometryId ?? source.geometryId,
|
|
8602
|
+
identity,
|
|
8603
|
+
positions: source.positions,
|
|
8604
|
+
indices: source.indices
|
|
8605
|
+
});
|
|
8606
|
+
geometries.set(source.geometryId, geometry);
|
|
8607
|
+
const attributeRanges = source.ranges.filter((range) => range.usage === "vertex_attributes");
|
|
8608
|
+
const attributeStart = Math.min(...attributeRanges.map((range) => range.byteOffset));
|
|
8609
|
+
geometryUpdates.set(source.geometryId, Object.freeze({
|
|
8610
|
+
positionRanges: Object.freeze(attributeRanges.map((range) => Object.freeze({
|
|
8611
|
+
byteOffset: range.byteOffset - attributeStart,
|
|
8612
|
+
byteLength: range.byteLength
|
|
8613
|
+
})))
|
|
8614
|
+
}));
|
|
8615
|
+
}
|
|
8616
|
+
const materials = new Map(packet.materials.map((material) => [
|
|
8617
|
+
material.materialId,
|
|
8618
|
+
new ICEBasicMaterial({
|
|
8619
|
+
color: colorHex(material.baseColor)
|
|
8620
|
+
})
|
|
8621
|
+
]));
|
|
8622
|
+
const defaultMaterial = new ICEBasicMaterial({
|
|
8623
|
+
color: "#9aa6b2"
|
|
8624
|
+
});
|
|
8625
|
+
const scene = new ICEScene({ id: `packet:${packet.revision}:scene` });
|
|
8626
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
8627
|
+
for (const source of packet.nodes) {
|
|
8628
|
+
const geometry = source.geometryId ? geometries.get(source.geometryId) : void 0;
|
|
8629
|
+
const material = source.materialId ? materials.get(source.materialId) ?? defaultMaterial : defaultMaterial;
|
|
8630
|
+
const options = {
|
|
8631
|
+
id: source.nodeId,
|
|
8632
|
+
displayName: source.name,
|
|
8633
|
+
selectionId: source.nodeId,
|
|
8634
|
+
matrix: source.matrix
|
|
8635
|
+
};
|
|
8636
|
+
const node = geometry ? new ICEMesh({
|
|
8637
|
+
...options,
|
|
8638
|
+
geometry,
|
|
8639
|
+
material
|
|
8640
|
+
}) : new ICENode(options);
|
|
8641
|
+
node.visible = source.visible;
|
|
8642
|
+
nodes.set(source.nodeId, node);
|
|
8643
|
+
}
|
|
8644
|
+
for (const source of packet.nodes) {
|
|
8645
|
+
const node = nodes.get(source.nodeId);
|
|
8646
|
+
const parent = source.parentId ? nodes.get(source.parentId) : void 0;
|
|
8647
|
+
if (source.parentId && !parent) {
|
|
8648
|
+
throw new Error(`ICE3_PREVIEW_PACKET_INVALID: parent node ${source.parentId} is missing`);
|
|
8649
|
+
}
|
|
8650
|
+
(parent ?? scene).addChild(node);
|
|
8651
|
+
}
|
|
8652
|
+
return {
|
|
8653
|
+
scene,
|
|
8654
|
+
geometries,
|
|
8655
|
+
geometryUpdates
|
|
8656
|
+
};
|
|
8657
|
+
}
|
|
8658
|
+
function colorHex(color) {
|
|
8659
|
+
const channel = (value) => Math.round(Math.max(0, Math.min(1, value)) * 255).toString(16).padStart(2, "0");
|
|
8660
|
+
return `#${channel(color[0])}${channel(color[1])}${channel(color[2])}`;
|
|
8661
|
+
}
|
|
6832
8662
|
|
|
6833
8663
|
// packages/assets/dist/ICEProfileValidator.js
|
|
8664
|
+
function validateTextures(json) {
|
|
8665
|
+
const images = records(json.images);
|
|
8666
|
+
const textures = records(json.textures);
|
|
8667
|
+
const samplers = records(json.samplers);
|
|
8668
|
+
for (const image of images) {
|
|
8669
|
+
const uri = image.uri;
|
|
8670
|
+
if (typeof uri !== "string" || !uri || uri.startsWith("/") || uri.includes("..") || uri.includes("://") || image.bufferView !== void 0) {
|
|
8671
|
+
throw assetError("ICE3_ASSET_UNSUPPORTED_FEATURE", "images must reference a relative file path");
|
|
8672
|
+
}
|
|
8673
|
+
}
|
|
8674
|
+
for (const texture of textures) {
|
|
8675
|
+
const source = Number(texture.source);
|
|
8676
|
+
if (!Number.isInteger(source) || source < 0 || source >= images.length) {
|
|
8677
|
+
throw assetError("ICE3_ASSET_INVALID_GLB", "texture source is out of range");
|
|
8678
|
+
}
|
|
8679
|
+
if (texture.sampler !== void 0) {
|
|
8680
|
+
const sampler = Number(texture.sampler);
|
|
8681
|
+
if (!Number.isInteger(sampler) || sampler < 0 || sampler >= samplers.length) {
|
|
8682
|
+
throw assetError("ICE3_ASSET_INVALID_GLB", "texture sampler is out of range");
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
}
|
|
8686
|
+
for (const material of records(json.materials)) {
|
|
8687
|
+
const slots = [
|
|
8688
|
+
["baseColorTexture", record(record(material.pbrMetallicRoughness)?.baseColorTexture)],
|
|
8689
|
+
["normalTexture", record(material.normalTexture)],
|
|
8690
|
+
["metallicRoughnessTexture", record(record(material.pbrMetallicRoughness)?.metallicRoughnessTexture)],
|
|
8691
|
+
["occlusionTexture", record(material.occlusionTexture)]
|
|
8692
|
+
];
|
|
8693
|
+
for (const [slot, reference] of slots) {
|
|
8694
|
+
if (!reference)
|
|
8695
|
+
continue;
|
|
8696
|
+
const index = Number(reference.index);
|
|
8697
|
+
if (!Number.isInteger(index) || index < 0 || index >= textures.length) {
|
|
8698
|
+
throw assetError("ICE3_ASSET_INVALID_GLB", `${slot} index is out of range`);
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
}
|
|
8702
|
+
}
|
|
6834
8703
|
function validatePreparedProfile(json, binary) {
|
|
6835
8704
|
const asset = record(json.asset);
|
|
6836
8705
|
const profile = record(record(asset?.extras)?.ice3);
|
|
@@ -6858,15 +8727,13 @@ function validatePreparedProfile(json, binary) {
|
|
|
6858
8727
|
"animations",
|
|
6859
8728
|
"skins",
|
|
6860
8729
|
"cameras",
|
|
6861
|
-
"textures",
|
|
6862
|
-
"images",
|
|
6863
|
-
"samplers",
|
|
6864
8730
|
"lights"
|
|
6865
8731
|
]) {
|
|
6866
8732
|
if (Array.isArray(json[field]) && json[field].length > 0) {
|
|
6867
8733
|
throw assetError("ICE3_ASSET_UNSUPPORTED_FEATURE", `${field} are unsupported by the M1 profile`);
|
|
6868
8734
|
}
|
|
6869
8735
|
}
|
|
8736
|
+
validateTextures(json);
|
|
6870
8737
|
if (records(json.scenes).length !== 1 || json.scene !== 0 || records(json.buffers).length !== 1 || records(json.buffers)[0]?.uri !== void 0 || Number(records(json.buffers)[0]?.byteLength) > binary.byteLength) {
|
|
6871
8738
|
throw assetError("ICE3_ASSET_INVALID_GLB", "profile container fields are invalid");
|
|
6872
8739
|
}
|
|
@@ -6948,9 +8815,9 @@ function validatePreparedStats(json, profile) {
|
|
|
6948
8815
|
let primitiveCount = 0;
|
|
6949
8816
|
let triangleCount = 0;
|
|
6950
8817
|
let edgeSegmentCount = 0;
|
|
6951
|
-
const
|
|
8818
|
+
const selectionIds = /* @__PURE__ */ new Set();
|
|
6952
8819
|
const referencedMeshes = /* @__PURE__ */ new Set();
|
|
6953
|
-
for (const
|
|
8820
|
+
for (const node of nodes) {
|
|
6954
8821
|
if (node.mesh === void 0)
|
|
6955
8822
|
continue;
|
|
6956
8823
|
const mesh = meshes[Number(node.mesh)];
|
|
@@ -6960,7 +8827,10 @@ function validatePreparedStats(json, profile) {
|
|
|
6960
8827
|
for (const primitive of records(mesh.primitives)) {
|
|
6961
8828
|
const mode = Number(primitive.mode ?? 4);
|
|
6962
8829
|
if (mode === 4) {
|
|
6963
|
-
|
|
8830
|
+
const nodeProfile = record(record(node.extras)?.ice3);
|
|
8831
|
+
const selectionId = nodeProfile?.selectionId ?? nodeProfile?.id;
|
|
8832
|
+
if (typeof selectionId === "string")
|
|
8833
|
+
selectionIds.add(selectionId);
|
|
6964
8834
|
}
|
|
6965
8835
|
}
|
|
6966
8836
|
}
|
|
@@ -6986,7 +8856,7 @@ function validatePreparedStats(json, profile) {
|
|
|
6986
8856
|
triangleCount,
|
|
6987
8857
|
edgeSegmentCount,
|
|
6988
8858
|
materialCount: records(json.materials).length,
|
|
6989
|
-
selectionGroupCount: Array.isArray(batchingSelectionIds) ? new Set(batchingSelectionIds).size :
|
|
8859
|
+
selectionGroupCount: Array.isArray(batchingSelectionIds) ? new Set(batchingSelectionIds).size : selectionIds.size
|
|
6990
8860
|
};
|
|
6991
8861
|
for (const [key, value] of Object.entries(expected)) {
|
|
6992
8862
|
if (stats?.[key] !== value) {
|
|
@@ -7021,13 +8891,15 @@ function assetError(code, message) {
|
|
|
7021
8891
|
}
|
|
7022
8892
|
|
|
7023
8893
|
// packages/assets/dist/ICEGLBReader.js
|
|
7024
|
-
function readPreparedGLB(bytes) {
|
|
8894
|
+
function readPreparedGLB(bytes, options = {}) {
|
|
7025
8895
|
const { json, binary, profile } = parseGLB(bytes);
|
|
7026
8896
|
const accessors = records(json.accessors);
|
|
7027
8897
|
const views = records(json.bufferViews);
|
|
7028
8898
|
const sourceNodes = records(json.nodes);
|
|
7029
8899
|
const sourceMeshes = records(json.meshes);
|
|
7030
8900
|
const sourceMaterials = records(json.materials);
|
|
8901
|
+
const sourceTextures = records(json.textures);
|
|
8902
|
+
const sourceImages = records(json.images);
|
|
7031
8903
|
const activeScene = records(json.scenes)[Number(json.scene ?? 0)];
|
|
7032
8904
|
const sceneRoots = integers(activeScene?.nodes);
|
|
7033
8905
|
const parents = new Array(sourceNodes.length).fill(null);
|
|
@@ -7076,7 +8948,7 @@ function readPreparedGLB(bytes) {
|
|
|
7076
8948
|
const geometries = [];
|
|
7077
8949
|
const materials = [];
|
|
7078
8950
|
const meshes = [];
|
|
7079
|
-
const
|
|
8951
|
+
const selectionIds = /* @__PURE__ */ new Set();
|
|
7080
8952
|
let primitiveCount = 0;
|
|
7081
8953
|
let triangleCount = 0;
|
|
7082
8954
|
let edgeSegmentCount = 0;
|
|
@@ -7108,11 +8980,20 @@ function readPreparedGLB(bytes) {
|
|
|
7108
8980
|
edgeSegmentCount += (edgeIndices?.length ?? 0) / 2;
|
|
7109
8981
|
const baseMaterial = sourceMaterials[Number(primitive.material)];
|
|
7110
8982
|
const edgeMaterial = edgePrimitive ? sourceMaterials[Number(edgePrimitive.material)] : void 0;
|
|
8983
|
+
const shading = record(record(baseMaterial?.extras)?.ice3)?.shading;
|
|
7111
8984
|
const materialIndex = materials.length;
|
|
8985
|
+
const map = materialMap(baseMaterial, sourceTextures, sourceImages, options.baseUrl);
|
|
8986
|
+
const normalMap = normalMaterialMap(baseMaterial, sourceTextures, sourceImages, options.baseUrl);
|
|
8987
|
+
const ormMap = ormMaterialMap(baseMaterial, sourceTextures, sourceImages, options.baseUrl);
|
|
7112
8988
|
materials.push({
|
|
7113
8989
|
id: `${meshId}/material/${primitiveIndex}`,
|
|
7114
8990
|
baseColor: materialColor(baseMaterial),
|
|
7115
|
-
...edgeMaterial ? { edgeColor: materialColor(edgeMaterial) } : {}
|
|
8991
|
+
...edgeMaterial ? { edgeColor: materialColor(edgeMaterial) } : {},
|
|
8992
|
+
...shading === "directional" || shading === "water" ? { shading } : {},
|
|
8993
|
+
...map ? { map } : {},
|
|
8994
|
+
...normalMap ? { normalMap } : {},
|
|
8995
|
+
...ormMap ? { ormMap } : {},
|
|
8996
|
+
...surfaceFactors(baseMaterial)
|
|
7116
8997
|
});
|
|
7117
8998
|
const bounds = boundsFromAccessor(accessors[positionIndex]);
|
|
7118
8999
|
const geometryIndex = geometries.length;
|
|
@@ -7126,7 +9007,7 @@ function readPreparedGLB(bytes) {
|
|
|
7126
9007
|
bounds
|
|
7127
9008
|
});
|
|
7128
9009
|
for (const entry of nodesForMesh) {
|
|
7129
|
-
|
|
9010
|
+
selectionIds.add(nodes[entry.index].selectionId);
|
|
7130
9011
|
meshes.push({
|
|
7131
9012
|
id: `${meshId}/primitive/${primitiveIndex}/${entry.index}`,
|
|
7132
9013
|
node: entry.index,
|
|
@@ -7144,7 +9025,7 @@ function readPreparedGLB(bytes) {
|
|
|
7144
9025
|
triangleCount,
|
|
7145
9026
|
edgeSegmentCount,
|
|
7146
9027
|
materialCount: sourceMaterials.length,
|
|
7147
|
-
selectionGroupCount:
|
|
9028
|
+
selectionGroupCount: batching ? new Set(batching.semanticSelectionIds).size : selectionIds.size
|
|
7148
9029
|
};
|
|
7149
9030
|
const contract = preparedContract(profile);
|
|
7150
9031
|
return {
|
|
@@ -7168,7 +9049,7 @@ function readBatchingMetadata(profile, sourceNodes, sourceMeshes, sourceMaterial
|
|
|
7168
9049
|
}
|
|
7169
9050
|
const semanticIds = stringArray(raw.semanticIds, "semanticIds");
|
|
7170
9051
|
const semanticNodeIds = stringArray(raw.semanticNodeIds, "semanticNodeIds");
|
|
7171
|
-
const semanticSelectionIds = stringArray(raw.semanticSelectionIds, "semanticSelectionIds");
|
|
9052
|
+
const semanticSelectionIds = stringArray(raw.semanticSelectionIds, "semanticSelectionIds", false);
|
|
7172
9053
|
if (semanticIds.length === 0 || semanticNodeIds.length !== semanticIds.length || semanticSelectionIds.length !== semanticIds.length) {
|
|
7173
9054
|
throw assetError2("ICE3_ASSET_BATCH_INVALID", "semantic arrays are inconsistent");
|
|
7174
9055
|
}
|
|
@@ -7235,8 +9116,8 @@ function readBatchingMetadata(profile, sourceNodes, sourceMeshes, sourceMaterial
|
|
|
7235
9116
|
batches
|
|
7236
9117
|
};
|
|
7237
9118
|
}
|
|
7238
|
-
function stringArray(value, field) {
|
|
7239
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || !entry.trim()) || new Set(value).size !== value.length) {
|
|
9119
|
+
function stringArray(value, field, unique = true) {
|
|
9120
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || !entry.trim()) || unique && new Set(value).size !== value.length) {
|
|
7240
9121
|
throw assetError2("ICE3_ASSET_BATCH_INVALID", `${field} is invalid`);
|
|
7241
9122
|
}
|
|
7242
9123
|
return value;
|
|
@@ -7422,13 +9303,102 @@ function boundsFromProfile(value) {
|
|
|
7422
9303
|
max: [Number(max[0]), Number(max[1]), Number(max[2])]
|
|
7423
9304
|
};
|
|
7424
9305
|
}
|
|
9306
|
+
function materialMap(material, textures, images, baseUrl) {
|
|
9307
|
+
const reference = record(record(material?.pbrMetallicRoughness)?.baseColorTexture);
|
|
9308
|
+
if (!reference)
|
|
9309
|
+
return void 0;
|
|
9310
|
+
const texture = textures[Number(reference.index)];
|
|
9311
|
+
const uri = record(images[Number(record(texture)?.source)])?.uri;
|
|
9312
|
+
if (typeof uri !== "string" || uri.length === 0)
|
|
9313
|
+
return void 0;
|
|
9314
|
+
const transform = record(record(reference.extensions)?.KHR_texture_transform);
|
|
9315
|
+
const scale = numbers(transform?.scale, 2);
|
|
9316
|
+
const su = scale?.[0];
|
|
9317
|
+
const sv = scale?.[1];
|
|
9318
|
+
return {
|
|
9319
|
+
url: resolveTextureUrl(uri, baseUrl),
|
|
9320
|
+
...typeof su === "number" && typeof sv === "number" && su > 0 && sv > 0 ? { scale: [su, sv] } : {}
|
|
9321
|
+
};
|
|
9322
|
+
}
|
|
9323
|
+
function surfaceFactors(material) {
|
|
9324
|
+
const pbr = record(material?.pbrMetallicRoughness);
|
|
9325
|
+
const metallic = unitOrUndefined(pbr?.metallicFactor);
|
|
9326
|
+
const roughness = unitOrUndefined(pbr?.roughnessFactor);
|
|
9327
|
+
const emissive = numbers(material?.emissiveFactor, 3);
|
|
9328
|
+
return {
|
|
9329
|
+
...metallic !== void 0 ? { metallic } : {},
|
|
9330
|
+
...roughness !== void 0 ? { roughness } : {},
|
|
9331
|
+
...emissive && emissive.some((value) => value > 0) ? { emissive: [emissive[0], emissive[1], emissive[2]] } : {}
|
|
9332
|
+
};
|
|
9333
|
+
}
|
|
9334
|
+
function unitOrUndefined(value) {
|
|
9335
|
+
const parsed = Number(value);
|
|
9336
|
+
if (!Number.isFinite(parsed))
|
|
9337
|
+
return void 0;
|
|
9338
|
+
return Math.min(1, Math.max(0, parsed));
|
|
9339
|
+
}
|
|
9340
|
+
function normalMaterialMap(material, textures, images, baseUrl) {
|
|
9341
|
+
const reference = record(material?.normalTexture);
|
|
9342
|
+
if (!reference)
|
|
9343
|
+
return void 0;
|
|
9344
|
+
const texture = textures[Number(reference.index)];
|
|
9345
|
+
const uri = record(images[Number(record(texture)?.source)])?.uri;
|
|
9346
|
+
if (typeof uri !== "string" || uri.length === 0)
|
|
9347
|
+
return void 0;
|
|
9348
|
+
const strength = Number(reference.scale);
|
|
9349
|
+
return {
|
|
9350
|
+
url: resolveTextureUrl(uri, baseUrl),
|
|
9351
|
+
...Number.isFinite(strength) && strength >= 0 ? { strength } : {}
|
|
9352
|
+
};
|
|
9353
|
+
}
|
|
9354
|
+
function ormMaterialMap(material, textures, images, baseUrl) {
|
|
9355
|
+
const metallicRoughness = textureReference(record(record(material?.pbrMetallicRoughness)?.metallicRoughnessTexture), textures, images, baseUrl);
|
|
9356
|
+
const occlusion = textureReference(record(material?.occlusionTexture), textures, images, baseUrl);
|
|
9357
|
+
if (!metallicRoughness && !occlusion)
|
|
9358
|
+
return void 0;
|
|
9359
|
+
const url = metallicRoughness?.url ?? occlusion.url;
|
|
9360
|
+
const scale = metallicRoughness?.scale ?? occlusion?.scale;
|
|
9361
|
+
return {
|
|
9362
|
+
url,
|
|
9363
|
+
...scale ? { scale } : {},
|
|
9364
|
+
occlusion: occlusion?.url === url,
|
|
9365
|
+
metallicRoughness: metallicRoughness !== void 0
|
|
9366
|
+
};
|
|
9367
|
+
}
|
|
9368
|
+
function textureReference(reference, textures, images, baseUrl) {
|
|
9369
|
+
if (!reference)
|
|
9370
|
+
return void 0;
|
|
9371
|
+
const texture = textures[Number(reference.index)];
|
|
9372
|
+
const uri = record(images[Number(record(texture)?.source)])?.uri;
|
|
9373
|
+
if (typeof uri !== "string" || uri.length === 0)
|
|
9374
|
+
return void 0;
|
|
9375
|
+
const transform = record(record(reference.extensions)?.KHR_texture_transform);
|
|
9376
|
+
const scale = numbers(transform?.scale, 2);
|
|
9377
|
+
const su = scale?.[0];
|
|
9378
|
+
const sv = scale?.[1];
|
|
9379
|
+
return {
|
|
9380
|
+
url: resolveTextureUrl(uri, baseUrl),
|
|
9381
|
+
...typeof su === "number" && typeof sv === "number" && su > 0 && sv > 0 ? { scale: [su, sv] } : {}
|
|
9382
|
+
};
|
|
9383
|
+
}
|
|
9384
|
+
function resolveTextureUrl(uri, baseUrl) {
|
|
9385
|
+
if (!baseUrl)
|
|
9386
|
+
return uri;
|
|
9387
|
+
try {
|
|
9388
|
+
return new URL(uri, baseUrl).href;
|
|
9389
|
+
} catch {
|
|
9390
|
+
return uri;
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
7425
9393
|
function materialColor(material) {
|
|
7426
9394
|
if (!material)
|
|
7427
9395
|
return "#808080";
|
|
7428
9396
|
const color = record(material.pbrMetallicRoughness)?.baseColorFactor;
|
|
7429
9397
|
if (!Array.isArray(color) || color.length < 3)
|
|
7430
9398
|
return "#808080";
|
|
7431
|
-
|
|
9399
|
+
const alpha = color.length >= 4 ? Number(color[3]) : 1;
|
|
9400
|
+
const channels = color.slice(0, alpha < 1 ? 4 : 3).map((value) => Math.round(Math.min(1, Math.max(0, Number(value))) * 255).toString(16).padStart(2, "0"));
|
|
9401
|
+
return `#${channels.join("")}`;
|
|
7432
9402
|
}
|
|
7433
9403
|
function integers(value) {
|
|
7434
9404
|
return Array.isArray(value) ? value.filter((entry) => Number.isInteger(entry)) : [];
|
|
@@ -7443,6 +9413,494 @@ function assetError2(code, message) {
|
|
|
7443
9413
|
return new Error(`${code}: ${message}`);
|
|
7444
9414
|
}
|
|
7445
9415
|
|
|
9416
|
+
// packages/assets/dist/ICEPreviewGeometryPacket.js
|
|
9417
|
+
var MAGIC = "ICE3PGP1";
|
|
9418
|
+
var HEADER_BYTES = 120;
|
|
9419
|
+
var MANIFEST_LENGTH_OFFSET = 40;
|
|
9420
|
+
var BINARY_LENGTH_OFFSET = 48;
|
|
9421
|
+
var MANIFEST_DIGEST_OFFSET = 56;
|
|
9422
|
+
var CONTENT_DIGEST_OFFSET = 88;
|
|
9423
|
+
var MAX_PACKET_BYTES = 256 * 1024 * 1024;
|
|
9424
|
+
var MAX_NODES = 1e6;
|
|
9425
|
+
var MAX_GEOMETRIES = 25e4;
|
|
9426
|
+
var MAX_RANGES = 64;
|
|
9427
|
+
var encoder = new TextEncoder();
|
|
9428
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
9429
|
+
function parsePreviewGeometryPacket(input) {
|
|
9430
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
9431
|
+
if (bytes.byteLength < HEADER_BYTES || bytes.byteLength > MAX_PACKET_BYTES) {
|
|
9432
|
+
throw new RangeError("ICE3 preview packet size is invalid");
|
|
9433
|
+
}
|
|
9434
|
+
if (decoder.decode(bytes.subarray(0, 8)) !== MAGIC) {
|
|
9435
|
+
throw new TypeError("ICE3 preview packet magic is invalid");
|
|
9436
|
+
}
|
|
9437
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
9438
|
+
const schemaVersion = view.getUint16(8, true);
|
|
9439
|
+
const transportVersion = view.getUint16(10, true);
|
|
9440
|
+
const headerBytes = view.getUint32(12, true);
|
|
9441
|
+
if (schemaVersion !== 1 || transportVersion !== 1 || headerBytes !== HEADER_BYTES) {
|
|
9442
|
+
throw new TypeError("ICE3 preview packet version is unsupported");
|
|
9443
|
+
}
|
|
9444
|
+
const manifestLength = readSafeU64(view, MANIFEST_LENGTH_OFFSET);
|
|
9445
|
+
const binaryLength = readSafeU64(view, BINARY_LENGTH_OFFSET);
|
|
9446
|
+
if (manifestLength === 0 || manifestLength % 8 !== 0 || HEADER_BYTES + manifestLength + binaryLength !== bytes.byteLength) {
|
|
9447
|
+
throw new RangeError("ICE3 preview packet lengths are invalid");
|
|
9448
|
+
}
|
|
9449
|
+
const manifestStart = HEADER_BYTES;
|
|
9450
|
+
const manifestEnd = manifestStart + manifestLength;
|
|
9451
|
+
const manifestBytes = bytes.subarray(manifestStart, manifestEnd);
|
|
9452
|
+
const binary = bytes.subarray(manifestEnd);
|
|
9453
|
+
const expectedManifestDigest = bytes.subarray(MANIFEST_DIGEST_OFFSET, MANIFEST_DIGEST_OFFSET + 32);
|
|
9454
|
+
if (!equalBytes(sha256(manifestBytes), expectedManifestDigest)) {
|
|
9455
|
+
throw new TypeError("ICE3 preview packet manifest digest is invalid");
|
|
9456
|
+
}
|
|
9457
|
+
const contentHash = sha256(concatBytes(manifestBytes, binary));
|
|
9458
|
+
const expectedContentDigest = bytes.subarray(CONTENT_DIGEST_OFFSET, CONTENT_DIGEST_OFFSET + 32);
|
|
9459
|
+
if (!equalBytes(contentHash, expectedContentDigest)) {
|
|
9460
|
+
throw new TypeError("ICE3 preview packet content digest is invalid");
|
|
9461
|
+
}
|
|
9462
|
+
let candidate;
|
|
9463
|
+
try {
|
|
9464
|
+
candidate = JSON.parse(decoder.decode(manifestBytes));
|
|
9465
|
+
} catch {
|
|
9466
|
+
throw new TypeError("ICE3 preview packet manifest is invalid");
|
|
9467
|
+
}
|
|
9468
|
+
const manifest = validateManifest(candidate, binary.byteLength);
|
|
9469
|
+
return {
|
|
9470
|
+
...manifest,
|
|
9471
|
+
geometries: manifest.geometries.map((geometry) => decodeGeometry(geometry, binary)),
|
|
9472
|
+
binary
|
|
9473
|
+
};
|
|
9474
|
+
}
|
|
9475
|
+
function applyPreviewGeometryDelta(base, delta) {
|
|
9476
|
+
if (delta.parentRevision !== base.revision) {
|
|
9477
|
+
throw new RangeError(`ICE3 preview packet parent revision ${delta.parentRevision ?? "missing"} does not match ${base.revision}`);
|
|
9478
|
+
}
|
|
9479
|
+
const nodes = mergeById(base.nodes, delta.nodes, delta.removedNodeIds, (node) => node.nodeId);
|
|
9480
|
+
const geometries = mergeGeometries(base.geometries, delta, delta.removedGeometryIds);
|
|
9481
|
+
const materials = mergeById(base.materials, delta.materials, [], (material) => material.materialId);
|
|
9482
|
+
return {
|
|
9483
|
+
schemaVersion: delta.schemaVersion,
|
|
9484
|
+
transportVersion: delta.transportVersion,
|
|
9485
|
+
revision: delta.revision,
|
|
9486
|
+
parentRevision: delta.parentRevision,
|
|
9487
|
+
sourceDigest: delta.sourceDigest,
|
|
9488
|
+
backend: delta.backend,
|
|
9489
|
+
residencyMode: delta.residencyMode,
|
|
9490
|
+
nodes,
|
|
9491
|
+
geometries,
|
|
9492
|
+
materials,
|
|
9493
|
+
removedNodeIds: [],
|
|
9494
|
+
removedGeometryIds: [],
|
|
9495
|
+
binary: delta.binary
|
|
9496
|
+
};
|
|
9497
|
+
}
|
|
9498
|
+
function mergeGeometries(baseGeometries, delta, removedGeometryIds) {
|
|
9499
|
+
const removed = new Set(removedGeometryIds);
|
|
9500
|
+
const updates = new Map(delta.geometries.map((geometry) => [geometry.geometryId, geometry]));
|
|
9501
|
+
const output = [];
|
|
9502
|
+
for (const base of baseGeometries) {
|
|
9503
|
+
if (removed.has(base.geometryId)) {
|
|
9504
|
+
continue;
|
|
9505
|
+
}
|
|
9506
|
+
const update = updates.get(base.geometryId);
|
|
9507
|
+
output.push(update ? mergeGeometry(base, update) : base);
|
|
9508
|
+
updates.delete(base.geometryId);
|
|
9509
|
+
}
|
|
9510
|
+
output.push(...updates.values());
|
|
9511
|
+
return Object.freeze(output);
|
|
9512
|
+
}
|
|
9513
|
+
function mergeGeometry(base, update) {
|
|
9514
|
+
if (base === update) {
|
|
9515
|
+
return base;
|
|
9516
|
+
}
|
|
9517
|
+
return freezeGeometry(update, base.attributeDigest === update.attributeDigest ? base.positions : update.positions, base.topologyDigest === update.topologyDigest ? base.indices : update.indices);
|
|
9518
|
+
}
|
|
9519
|
+
function validateManifest(value, binaryLength) {
|
|
9520
|
+
if (!isRecord(value)) {
|
|
9521
|
+
throw invalidManifest();
|
|
9522
|
+
}
|
|
9523
|
+
const nodes = readArray(value.nodes, MAX_NODES).map(readNode);
|
|
9524
|
+
const geometries = readArray(value.geometries, MAX_GEOMETRIES).map(readGeometryRecord);
|
|
9525
|
+
const materials = readArray(value.materials, MAX_GEOMETRIES).map(readMaterial);
|
|
9526
|
+
const removedNodeIds = readStringArray(value.removedNodeIds, MAX_NODES);
|
|
9527
|
+
const removedGeometryIds = readStringArray(value.removedGeometryIds, MAX_GEOMETRIES);
|
|
9528
|
+
const manifest = {
|
|
9529
|
+
schemaVersion: readVersion(value.schemaVersion, 1),
|
|
9530
|
+
transportVersion: readVersion(value.transportVersion, 1),
|
|
9531
|
+
revision: readSafeInteger(value.revision, 0, Number.MAX_SAFE_INTEGER),
|
|
9532
|
+
parentRevision: value.parentRevision === null ? null : readSafeInteger(value.parentRevision, 0, Number.MAX_SAFE_INTEGER),
|
|
9533
|
+
sourceDigest: readDigest(value.sourceDigest),
|
|
9534
|
+
backend: readEnum(value.backend, ["cpu", "gpu"]),
|
|
9535
|
+
residencyMode: readEnum(value.residencyMode, ["cpu", "cold", "warm", "resident", "snapshot"]),
|
|
9536
|
+
nodes,
|
|
9537
|
+
geometries,
|
|
9538
|
+
materials,
|
|
9539
|
+
removedNodeIds,
|
|
9540
|
+
removedGeometryIds
|
|
9541
|
+
};
|
|
9542
|
+
if (manifest.parentRevision !== null && manifest.parentRevision >= manifest.revision) {
|
|
9543
|
+
throw invalidManifest();
|
|
9544
|
+
}
|
|
9545
|
+
ensureUnique(nodes, (node) => node.nodeId);
|
|
9546
|
+
ensureUnique(geometries, (geometry) => geometry.geometryId);
|
|
9547
|
+
ensureUnique(materials, (material) => material.materialId);
|
|
9548
|
+
for (const geometry of geometries) {
|
|
9549
|
+
for (const range of geometry.ranges) {
|
|
9550
|
+
if (range.byteOffset % 4 !== 0 || range.byteLength === 0 || range.byteOffset + range.byteLength > binaryLength) {
|
|
9551
|
+
throw invalidManifest();
|
|
9552
|
+
}
|
|
9553
|
+
}
|
|
9554
|
+
}
|
|
9555
|
+
return manifest;
|
|
9556
|
+
}
|
|
9557
|
+
function readNode(value, index) {
|
|
9558
|
+
if (!isRecord(value)) {
|
|
9559
|
+
throw new TypeError(`ICE3 preview node ${index} is invalid`);
|
|
9560
|
+
}
|
|
9561
|
+
const matrix = readArray(value.matrix, 16);
|
|
9562
|
+
if (matrix.some((entry) => typeof entry !== "number" || !Number.isFinite(entry))) {
|
|
9563
|
+
throw new TypeError(`ICE3 preview node ${index} matrix is invalid`);
|
|
9564
|
+
}
|
|
9565
|
+
return {
|
|
9566
|
+
nodeId: readNonEmptyString(value.nodeId),
|
|
9567
|
+
parentId: readNullableString(value.parentId),
|
|
9568
|
+
name: readString(value.name),
|
|
9569
|
+
matrix: Object.freeze(matrix),
|
|
9570
|
+
materialId: readNullableString(value.materialId),
|
|
9571
|
+
geometryId: readNullableString(value.geometryId),
|
|
9572
|
+
visible: readBoolean(value.visible)
|
|
9573
|
+
};
|
|
9574
|
+
}
|
|
9575
|
+
function readGeometryRecord(value, index) {
|
|
9576
|
+
if (!isRecord(value)) {
|
|
9577
|
+
throw new TypeError(`ICE3 preview geometry ${index} is invalid`);
|
|
9578
|
+
}
|
|
9579
|
+
const indexWidth = readSafeInteger(value.indexWidth, 1, 8);
|
|
9580
|
+
if (indexWidth !== 2 && indexWidth !== 4) {
|
|
9581
|
+
throw new TypeError(`ICE3 preview geometry ${index} index width is invalid`);
|
|
9582
|
+
}
|
|
9583
|
+
const ranges = readArray(value.ranges, MAX_RANGES).map(readRange);
|
|
9584
|
+
return {
|
|
9585
|
+
geometryId: readNonEmptyString(value.geometryId),
|
|
9586
|
+
residentGeometryId: readNullableString(value.residentGeometryId),
|
|
9587
|
+
topologyDigest: readDigest(value.topologyDigest),
|
|
9588
|
+
attributeDigest: readDigest(value.attributeDigest),
|
|
9589
|
+
vertexCount: readSafeInteger(value.vertexCount, 1, 1e8),
|
|
9590
|
+
indexCount: readSafeInteger(value.indexCount, 0, 3e8),
|
|
9591
|
+
indexWidth,
|
|
9592
|
+
ranges: Object.freeze(ranges)
|
|
9593
|
+
};
|
|
9594
|
+
}
|
|
9595
|
+
function readRange(value, index) {
|
|
9596
|
+
if (!isRecord(value)) {
|
|
9597
|
+
throw new TypeError(`ICE3 preview range ${index} is invalid`);
|
|
9598
|
+
}
|
|
9599
|
+
return {
|
|
9600
|
+
byteOffset: readSafeInteger(value.byteOffset, 0, MAX_PACKET_BYTES),
|
|
9601
|
+
byteLength: readSafeInteger(value.byteLength, 1, MAX_PACKET_BYTES),
|
|
9602
|
+
usage: readEnum(value.usage, ["vertex_attributes", "indices"])
|
|
9603
|
+
};
|
|
9604
|
+
}
|
|
9605
|
+
function readMaterial(value, index) {
|
|
9606
|
+
if (!isRecord(value)) {
|
|
9607
|
+
throw new TypeError(`ICE3 preview material ${index} is invalid`);
|
|
9608
|
+
}
|
|
9609
|
+
const baseColor = readFiniteArray(value.baseColor, 4);
|
|
9610
|
+
return {
|
|
9611
|
+
materialId: readNonEmptyString(value.materialId),
|
|
9612
|
+
baseColor: Object.freeze(baseColor),
|
|
9613
|
+
metallic: readFiniteNumber(value.metallic),
|
|
9614
|
+
roughness: readFiniteNumber(value.roughness),
|
|
9615
|
+
opacity: readFiniteNumber(value.opacity)
|
|
9616
|
+
};
|
|
9617
|
+
}
|
|
9618
|
+
function decodeGeometry(geometry, binary) {
|
|
9619
|
+
const attributes = geometry.ranges.find((range) => range.usage === "vertex_attributes");
|
|
9620
|
+
const topology = geometry.ranges.find((range) => range.usage === "indices");
|
|
9621
|
+
if (!attributes && !topology) {
|
|
9622
|
+
return freezeGeometry(encodeIdentity(geometry), new Float32Array(), new Uint32Array());
|
|
9623
|
+
}
|
|
9624
|
+
const positions = attributes ? decodePositions(attributes, geometry.vertexCount, binary) : new Float32Array(geometry.vertexCount * 3);
|
|
9625
|
+
const indices = topology ? decodeIndices(topology, geometry.indexCount, geometry.indexWidth, binary) : new Uint32Array();
|
|
9626
|
+
return freezeGeometry(encodeIdentity(geometry), positions, indices);
|
|
9627
|
+
}
|
|
9628
|
+
function decodePositions(range, vertexCount, binary) {
|
|
9629
|
+
const bytesPerVertex = range.byteLength / vertexCount;
|
|
9630
|
+
const components = bytesPerVertex / 4;
|
|
9631
|
+
if (!Number.isInteger(components) || components < 3) {
|
|
9632
|
+
throw new TypeError("ICE3 preview position range has an invalid stride");
|
|
9633
|
+
}
|
|
9634
|
+
const source = new Float32Array(binary.buffer, binary.byteOffset + range.byteOffset, range.byteLength / 4);
|
|
9635
|
+
if (components === 3) {
|
|
9636
|
+
return source;
|
|
9637
|
+
}
|
|
9638
|
+
const positions = new Float32Array(vertexCount * 3);
|
|
9639
|
+
for (let vertex = 0; vertex < vertexCount; vertex += 1) {
|
|
9640
|
+
const sourceOffset = vertex * components;
|
|
9641
|
+
positions[vertex * 3] = source[sourceOffset];
|
|
9642
|
+
positions[vertex * 3 + 1] = source[sourceOffset + 1];
|
|
9643
|
+
positions[vertex * 3 + 2] = source[sourceOffset + 2];
|
|
9644
|
+
}
|
|
9645
|
+
return positions;
|
|
9646
|
+
}
|
|
9647
|
+
function decodeIndices(range, indexCount, indexWidth, binary) {
|
|
9648
|
+
if (range.byteLength !== indexCount * indexWidth) {
|
|
9649
|
+
throw new TypeError("ICE3 preview index range has an invalid length");
|
|
9650
|
+
}
|
|
9651
|
+
const offset = binary.byteOffset + range.byteOffset;
|
|
9652
|
+
return indexWidth === 2 ? new Uint16Array(binary.buffer, offset, indexCount) : new Uint32Array(binary.buffer, offset, indexCount);
|
|
9653
|
+
}
|
|
9654
|
+
function freezeGeometry(identity, positions, indices) {
|
|
9655
|
+
return Object.freeze({
|
|
9656
|
+
...identity,
|
|
9657
|
+
positions,
|
|
9658
|
+
indices
|
|
9659
|
+
});
|
|
9660
|
+
}
|
|
9661
|
+
function encodeIdentity(geometry) {
|
|
9662
|
+
return geometry;
|
|
9663
|
+
}
|
|
9664
|
+
function mergeById(base, updates, removed, id) {
|
|
9665
|
+
const removedIds = new Set(removed);
|
|
9666
|
+
const updateMap = new Map(updates.map((value) => [id(value), value]));
|
|
9667
|
+
const output = base.filter((value) => !removedIds.has(id(value))).map((value) => updateMap.get(id(value)) ?? value);
|
|
9668
|
+
const baseIds = new Set(base.map(id));
|
|
9669
|
+
for (const value of updates) {
|
|
9670
|
+
if (!baseIds.has(id(value))) {
|
|
9671
|
+
output.push(value);
|
|
9672
|
+
}
|
|
9673
|
+
}
|
|
9674
|
+
return Object.freeze(output);
|
|
9675
|
+
}
|
|
9676
|
+
function readArray(value, maximum) {
|
|
9677
|
+
if (!Array.isArray(value) || value.length > maximum) {
|
|
9678
|
+
throw invalidManifest();
|
|
9679
|
+
}
|
|
9680
|
+
return value;
|
|
9681
|
+
}
|
|
9682
|
+
function readStringArray(value, maximum) {
|
|
9683
|
+
return Object.freeze(readArray(value, maximum).map(readString));
|
|
9684
|
+
}
|
|
9685
|
+
function readFiniteArray(value, length) {
|
|
9686
|
+
const values = readArray(value, length);
|
|
9687
|
+
if (values.length !== length || values.some((entry) => typeof entry !== "number" || !Number.isFinite(entry))) {
|
|
9688
|
+
throw invalidManifest();
|
|
9689
|
+
}
|
|
9690
|
+
return values;
|
|
9691
|
+
}
|
|
9692
|
+
function readVersion(value, expected) {
|
|
9693
|
+
if (value !== expected) {
|
|
9694
|
+
throw new TypeError("ICE3 preview packet version is unsupported");
|
|
9695
|
+
}
|
|
9696
|
+
return expected;
|
|
9697
|
+
}
|
|
9698
|
+
function readSafeInteger(value, minimum, maximum) {
|
|
9699
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
9700
|
+
throw invalidManifest();
|
|
9701
|
+
}
|
|
9702
|
+
return value;
|
|
9703
|
+
}
|
|
9704
|
+
function readFiniteNumber(value) {
|
|
9705
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
9706
|
+
throw invalidManifest();
|
|
9707
|
+
}
|
|
9708
|
+
return value;
|
|
9709
|
+
}
|
|
9710
|
+
function readBoolean(value) {
|
|
9711
|
+
if (typeof value !== "boolean") {
|
|
9712
|
+
throw invalidManifest();
|
|
9713
|
+
}
|
|
9714
|
+
return value;
|
|
9715
|
+
}
|
|
9716
|
+
function readString(value) {
|
|
9717
|
+
if (typeof value !== "string") {
|
|
9718
|
+
throw invalidManifest();
|
|
9719
|
+
}
|
|
9720
|
+
return value;
|
|
9721
|
+
}
|
|
9722
|
+
function readNonEmptyString(value) {
|
|
9723
|
+
const result = readString(value);
|
|
9724
|
+
if (!result.trim()) {
|
|
9725
|
+
throw invalidManifest();
|
|
9726
|
+
}
|
|
9727
|
+
return result;
|
|
9728
|
+
}
|
|
9729
|
+
function readNullableString(value) {
|
|
9730
|
+
return value === null ? null : readString(value);
|
|
9731
|
+
}
|
|
9732
|
+
function readDigest(value) {
|
|
9733
|
+
const digest = readString(value);
|
|
9734
|
+
if (!/^[0-9a-fA-F]{64}$/.test(digest)) {
|
|
9735
|
+
throw invalidManifest();
|
|
9736
|
+
}
|
|
9737
|
+
return digest.toLowerCase();
|
|
9738
|
+
}
|
|
9739
|
+
function readEnum(value, allowed) {
|
|
9740
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
9741
|
+
throw invalidManifest();
|
|
9742
|
+
}
|
|
9743
|
+
return value;
|
|
9744
|
+
}
|
|
9745
|
+
function ensureUnique(values, id) {
|
|
9746
|
+
if (new Set(values.map(id)).size !== values.length) {
|
|
9747
|
+
throw invalidManifest();
|
|
9748
|
+
}
|
|
9749
|
+
}
|
|
9750
|
+
function invalidManifest() {
|
|
9751
|
+
return new TypeError("ICE3 preview packet manifest is invalid");
|
|
9752
|
+
}
|
|
9753
|
+
function isRecord(value) {
|
|
9754
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9755
|
+
}
|
|
9756
|
+
function readSafeU64(view, offset) {
|
|
9757
|
+
const value = view.getBigUint64(offset, true);
|
|
9758
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
9759
|
+
throw new RangeError("ICE3 preview packet length exceeds safe integer range");
|
|
9760
|
+
}
|
|
9761
|
+
return Number(value);
|
|
9762
|
+
}
|
|
9763
|
+
function concatBytes(left, right) {
|
|
9764
|
+
const output = new Uint8Array(left.length + right.length);
|
|
9765
|
+
output.set(left);
|
|
9766
|
+
output.set(right, left.length);
|
|
9767
|
+
return output;
|
|
9768
|
+
}
|
|
9769
|
+
function equalBytes(left, right) {
|
|
9770
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
9771
|
+
}
|
|
9772
|
+
var SHA256_CONSTANTS = new Uint32Array([
|
|
9773
|
+
1116352408,
|
|
9774
|
+
1899447441,
|
|
9775
|
+
3049323471,
|
|
9776
|
+
3921009573,
|
|
9777
|
+
961987163,
|
|
9778
|
+
1508970993,
|
|
9779
|
+
2453635748,
|
|
9780
|
+
2870763221,
|
|
9781
|
+
3624381080,
|
|
9782
|
+
310598401,
|
|
9783
|
+
607225278,
|
|
9784
|
+
1426881987,
|
|
9785
|
+
1925078388,
|
|
9786
|
+
2162078206,
|
|
9787
|
+
2614888103,
|
|
9788
|
+
3248222580,
|
|
9789
|
+
3835390401,
|
|
9790
|
+
4022224774,
|
|
9791
|
+
264347078,
|
|
9792
|
+
604807628,
|
|
9793
|
+
770255983,
|
|
9794
|
+
1249150122,
|
|
9795
|
+
1555081692,
|
|
9796
|
+
1996064986,
|
|
9797
|
+
2554220882,
|
|
9798
|
+
2821834349,
|
|
9799
|
+
2952996808,
|
|
9800
|
+
3210313671,
|
|
9801
|
+
3336571891,
|
|
9802
|
+
3584528711,
|
|
9803
|
+
113926993,
|
|
9804
|
+
338241895,
|
|
9805
|
+
666307205,
|
|
9806
|
+
773529912,
|
|
9807
|
+
1294757372,
|
|
9808
|
+
1396182291,
|
|
9809
|
+
1695183700,
|
|
9810
|
+
1986661051,
|
|
9811
|
+
2177026350,
|
|
9812
|
+
2456956037,
|
|
9813
|
+
2730485921,
|
|
9814
|
+
2820302411,
|
|
9815
|
+
3259730800,
|
|
9816
|
+
3345764771,
|
|
9817
|
+
3516065817,
|
|
9818
|
+
3600352804,
|
|
9819
|
+
4094571909,
|
|
9820
|
+
275423344,
|
|
9821
|
+
430227734,
|
|
9822
|
+
506948616,
|
|
9823
|
+
659060556,
|
|
9824
|
+
883997877,
|
|
9825
|
+
958139571,
|
|
9826
|
+
1322822218,
|
|
9827
|
+
1537002063,
|
|
9828
|
+
1747873779,
|
|
9829
|
+
1955562222,
|
|
9830
|
+
2024104815,
|
|
9831
|
+
2227730452,
|
|
9832
|
+
2361852424,
|
|
9833
|
+
2428436474,
|
|
9834
|
+
2756734187,
|
|
9835
|
+
3204031479,
|
|
9836
|
+
3329325298
|
|
9837
|
+
]);
|
|
9838
|
+
function sha256(input) {
|
|
9839
|
+
const bitLength = input.length * 8;
|
|
9840
|
+
const paddedLength = Math.ceil((input.length + 9) / 64) * 64;
|
|
9841
|
+
const data = new Uint8Array(paddedLength);
|
|
9842
|
+
data.set(input);
|
|
9843
|
+
data[input.length] = 128;
|
|
9844
|
+
const view = new DataView(data.buffer);
|
|
9845
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
|
|
9846
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0);
|
|
9847
|
+
const hash = new Uint32Array([
|
|
9848
|
+
1779033703,
|
|
9849
|
+
3144134277,
|
|
9850
|
+
1013904242,
|
|
9851
|
+
2773480762,
|
|
9852
|
+
1359893119,
|
|
9853
|
+
2600822924,
|
|
9854
|
+
528734635,
|
|
9855
|
+
1541459225
|
|
9856
|
+
]);
|
|
9857
|
+
const words = new Uint32Array(64);
|
|
9858
|
+
for (let offset = 0; offset < data.length; offset += 64) {
|
|
9859
|
+
for (let index = 0; index < 16; index += 1) {
|
|
9860
|
+
words[index] = view.getUint32(offset + index * 4);
|
|
9861
|
+
}
|
|
9862
|
+
for (let index = 16; index < 64; index += 1) {
|
|
9863
|
+
const s0 = rotateRight(words[index - 15], 7) ^ rotateRight(words[index - 15], 18) ^ words[index - 15] >>> 3;
|
|
9864
|
+
const s1 = rotateRight(words[index - 2], 17) ^ rotateRight(words[index - 2], 19) ^ words[index - 2] >>> 10;
|
|
9865
|
+
words[index] = words[index - 16] + s0 + words[index - 7] + s1 >>> 0;
|
|
9866
|
+
}
|
|
9867
|
+
let [a, b, c, d, e, f, g, h] = hash;
|
|
9868
|
+
for (let index = 0; index < 64; index += 1) {
|
|
9869
|
+
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
9870
|
+
const choice = e & f ^ ~e & g;
|
|
9871
|
+
const temp1 = h + sum1 + choice + SHA256_CONSTANTS[index] + words[index] >>> 0;
|
|
9872
|
+
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
9873
|
+
const majority = a & b ^ a & c ^ b & c;
|
|
9874
|
+
const temp2 = sum0 + majority >>> 0;
|
|
9875
|
+
h = g;
|
|
9876
|
+
g = f;
|
|
9877
|
+
f = e;
|
|
9878
|
+
e = d + temp1 >>> 0;
|
|
9879
|
+
d = c;
|
|
9880
|
+
c = b;
|
|
9881
|
+
b = a;
|
|
9882
|
+
a = temp1 + temp2 >>> 0;
|
|
9883
|
+
}
|
|
9884
|
+
hash[0] = hash[0] + a >>> 0;
|
|
9885
|
+
hash[1] = hash[1] + b >>> 0;
|
|
9886
|
+
hash[2] = hash[2] + c >>> 0;
|
|
9887
|
+
hash[3] = hash[3] + d >>> 0;
|
|
9888
|
+
hash[4] = hash[4] + e >>> 0;
|
|
9889
|
+
hash[5] = hash[5] + f >>> 0;
|
|
9890
|
+
hash[6] = hash[6] + g >>> 0;
|
|
9891
|
+
hash[7] = hash[7] + h >>> 0;
|
|
9892
|
+
}
|
|
9893
|
+
const output = new Uint8Array(32);
|
|
9894
|
+
const outputView = new DataView(output.buffer);
|
|
9895
|
+
for (let index = 0; index < hash.length; index += 1) {
|
|
9896
|
+
outputView.setUint32(index * 4, hash[index], false);
|
|
9897
|
+
}
|
|
9898
|
+
return output;
|
|
9899
|
+
}
|
|
9900
|
+
function rotateRight(value, bits) {
|
|
9901
|
+
return value >>> bits | value << 32 - bits;
|
|
9902
|
+
}
|
|
9903
|
+
|
|
7446
9904
|
// packages/assets/dist/ICETilesetReader.js
|
|
7447
9905
|
var IDENTITY = [
|
|
7448
9906
|
1,
|
|
@@ -7715,7 +10173,11 @@ function parseTransform(value) {
|
|
|
7715
10173
|
return matrix;
|
|
7716
10174
|
}
|
|
7717
10175
|
function assertContains(parent, child, id) {
|
|
7718
|
-
if ([0, 1, 2].some((axis) =>
|
|
10176
|
+
if ([0, 1, 2].some((axis) => {
|
|
10177
|
+
const scale = Math.max(1, Math.abs(parent.min[axis]), Math.abs(parent.max[axis]), Math.abs(child.min[axis]), Math.abs(child.max[axis]));
|
|
10178
|
+
const tolerance = EPSILON * scale;
|
|
10179
|
+
return child.min[axis] < parent.min[axis] - tolerance || child.max[axis] > parent.max[axis] + tolerance;
|
|
10180
|
+
})) {
|
|
7719
10181
|
throw invalid(`child bounds escape parent tile ${id}`);
|
|
7720
10182
|
}
|
|
7721
10183
|
}
|
|
@@ -7828,7 +10290,7 @@ var ICEAssetLoader = class {
|
|
|
7828
10290
|
throw new Error(`ICE3_ASSET_FETCH_FAILED: HTTP ${response.status}`);
|
|
7829
10291
|
}
|
|
7830
10292
|
const bytes = await response.arrayBuffer();
|
|
7831
|
-
const snapshot = readPreparedGLB(bytes);
|
|
10293
|
+
const snapshot = readPreparedGLB(bytes, { baseUrl: url });
|
|
7832
10294
|
return { bytes, snapshot };
|
|
7833
10295
|
}
|
|
7834
10296
|
};
|
|
@@ -8618,7 +11080,7 @@ var ICETilesetHandle = class extends ICEEventDispatcher {
|
|
|
8618
11080
|
adaptivePlanningSignature = "";
|
|
8619
11081
|
renderBatchingMode;
|
|
8620
11082
|
visibleCoverageIds = /* @__PURE__ */ new Set();
|
|
8621
|
-
constructor(engine, snapshot, camera, scheduler,
|
|
11083
|
+
constructor(engine, snapshot, camera, scheduler, fetch2, maxConcurrentTiles, maxCommitsPerFrame, maxScreenSpaceErrorPixels, lodSettleDelayMs, renderBatching, callerSignal) {
|
|
8622
11084
|
super();
|
|
8623
11085
|
this.engine = engine;
|
|
8624
11086
|
this.snapshot = snapshot;
|
|
@@ -8649,7 +11111,7 @@ var ICETilesetHandle = class extends ICEEventDispatcher {
|
|
|
8649
11111
|
});
|
|
8650
11112
|
}
|
|
8651
11113
|
this.assetLoader = new ICEAssetLoader(engine, {
|
|
8652
|
-
fetch: (input, init) =>
|
|
11114
|
+
fetch: (input, init) => fetch2(input, {
|
|
8653
11115
|
...init ?? {},
|
|
8654
11116
|
signal: this.abortController.signal
|
|
8655
11117
|
})
|
|
@@ -9212,12 +11674,6 @@ var ICETilesetHandle = class extends ICEEventDispatcher {
|
|
|
9212
11674
|
this.assetLoader.clear(uri);
|
|
9213
11675
|
this.states.set(tileId, "evicted");
|
|
9214
11676
|
this.emit("TILESET_TILE_UNLOADED", tileId);
|
|
9215
|
-
const remainingIds = /* @__PURE__ */ new Set();
|
|
9216
|
-
collectNodeAndSelectionIDs(this.scene, remainingIds);
|
|
9217
|
-
const selection = this.engine.getSelection();
|
|
9218
|
-
if (selection.some((id) => !remainingIds.has(id))) {
|
|
9219
|
-
this.engine.setSelection(selection.filter((id) => remainingIds.has(id)));
|
|
9220
|
-
}
|
|
9221
11677
|
this.engine.requestRender("tileset-tile-unloaded");
|
|
9222
11678
|
}
|
|
9223
11679
|
hasPendingVisibleTargets() {
|
|
@@ -9271,7 +11727,6 @@ var ICETilesetHandle = class extends ICEEventDispatcher {
|
|
|
9271
11727
|
this.failedLodFallbacks.delete(fallbackId);
|
|
9272
11728
|
}
|
|
9273
11729
|
this.visibleCoverageIds = targetIds;
|
|
9274
|
-
this.reconcileSelectionVisibility();
|
|
9275
11730
|
this.engine.requestRender("tileset-visible-target-committed");
|
|
9276
11731
|
if (!this.readySettled) {
|
|
9277
11732
|
this.readySettled = true;
|
|
@@ -9309,17 +11764,6 @@ var ICETilesetHandle = class extends ICEEventDispatcher {
|
|
|
9309
11764
|
}
|
|
9310
11765
|
return visible;
|
|
9311
11766
|
}
|
|
9312
|
-
reconcileSelectionVisibility() {
|
|
9313
|
-
const visibleIds = /* @__PURE__ */ new Set();
|
|
9314
|
-
for (const child of this.scene.children) {
|
|
9315
|
-
collectVisibleNodeAndSelectionIDs(child, visibleIds);
|
|
9316
|
-
}
|
|
9317
|
-
const selection = this.engine.getSelection();
|
|
9318
|
-
const visibleSelection = selection.filter((id) => visibleIds.has(id));
|
|
9319
|
-
if (visibleSelection.length !== selection.length) {
|
|
9320
|
-
this.engine.setSelection(visibleSelection);
|
|
9321
|
-
}
|
|
9322
|
-
}
|
|
9323
11767
|
rejectReadyIfVisibleSetFailed() {
|
|
9324
11768
|
if (this.readySettled || !this.workSet)
|
|
9325
11769
|
return;
|
|
@@ -9728,34 +12172,6 @@ function collectNodeIDs(node, output) {
|
|
|
9728
12172
|
for (const child of node.children)
|
|
9729
12173
|
collectNodeIDs(child, output);
|
|
9730
12174
|
}
|
|
9731
|
-
function collectNodeAndSelectionIDs(node, output) {
|
|
9732
|
-
output.add(node.id);
|
|
9733
|
-
if (node instanceof ICEMesh)
|
|
9734
|
-
output.add(node.selectionId);
|
|
9735
|
-
if (node instanceof ICERenderBatch) {
|
|
9736
|
-
for (const selectionId of node.semanticSelectionIds) {
|
|
9737
|
-
output.add(selectionId);
|
|
9738
|
-
}
|
|
9739
|
-
}
|
|
9740
|
-
for (const child of node.children) {
|
|
9741
|
-
collectNodeAndSelectionIDs(child, output);
|
|
9742
|
-
}
|
|
9743
|
-
}
|
|
9744
|
-
function collectVisibleNodeAndSelectionIDs(node, output) {
|
|
9745
|
-
if (!node.visible)
|
|
9746
|
-
return;
|
|
9747
|
-
output.add(node.id);
|
|
9748
|
-
if (node instanceof ICEMesh)
|
|
9749
|
-
output.add(node.selectionId);
|
|
9750
|
-
if (node instanceof ICERenderBatch) {
|
|
9751
|
-
for (const selectionId of node.semanticSelectionIds) {
|
|
9752
|
-
output.add(selectionId);
|
|
9753
|
-
}
|
|
9754
|
-
}
|
|
9755
|
-
for (const child of node.children) {
|
|
9756
|
-
collectVisibleNodeAndSelectionIDs(child, output);
|
|
9757
|
-
}
|
|
9758
|
-
}
|
|
9759
12175
|
function throwIfAborted(signal) {
|
|
9760
12176
|
if (signal?.aborted)
|
|
9761
12177
|
throw abortedTilesetError();
|
|
@@ -9766,6 +12182,72 @@ function isAbortError(error) {
|
|
|
9766
12182
|
function abortedTilesetError() {
|
|
9767
12183
|
return new Error("ICE3_TILESET_ABORTED: Tileset loading was aborted");
|
|
9768
12184
|
}
|
|
12185
|
+
|
|
12186
|
+
// packages/runtime/dist/asset/ICEPreviewPacketLoader.js
|
|
12187
|
+
var ICEPreviewPacketLoader = class {
|
|
12188
|
+
fetcher;
|
|
12189
|
+
pinGeometry;
|
|
12190
|
+
geometries = /* @__PURE__ */ new Map();
|
|
12191
|
+
publishedScene = null;
|
|
12192
|
+
constructor(options = {}) {
|
|
12193
|
+
const normalized = typeof options === "function" ? { fetch: options } : options;
|
|
12194
|
+
this.fetcher = normalized.fetch ?? globalThis.fetch.bind(globalThis);
|
|
12195
|
+
this.pinGeometry = normalized.pinGeometry;
|
|
12196
|
+
}
|
|
12197
|
+
get scene() {
|
|
12198
|
+
return this.publishedScene;
|
|
12199
|
+
}
|
|
12200
|
+
get currentGeometries() {
|
|
12201
|
+
return this.geometries;
|
|
12202
|
+
}
|
|
12203
|
+
async load(url, current) {
|
|
12204
|
+
let bytes;
|
|
12205
|
+
try {
|
|
12206
|
+
const response = await this.fetcher(url, {
|
|
12207
|
+
headers: {
|
|
12208
|
+
accept: "application/vnd.ice3.preview-geometry; version=1"
|
|
12209
|
+
}
|
|
12210
|
+
});
|
|
12211
|
+
if (!response.ok) {
|
|
12212
|
+
return { kind: "fallback", reason: "packet-unavailable" };
|
|
12213
|
+
}
|
|
12214
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
12215
|
+
} catch {
|
|
12216
|
+
return { kind: "fallback", reason: "packet-unavailable" };
|
|
12217
|
+
}
|
|
12218
|
+
let incoming;
|
|
12219
|
+
try {
|
|
12220
|
+
incoming = parsePreviewGeometryPacket(bytes);
|
|
12221
|
+
} catch {
|
|
12222
|
+
return { kind: "fallback", reason: "packet-invalid" };
|
|
12223
|
+
}
|
|
12224
|
+
let packet = incoming;
|
|
12225
|
+
if (incoming.parentRevision !== null) {
|
|
12226
|
+
if (!current || incoming.parentRevision !== current.revision) {
|
|
12227
|
+
return { kind: "fallback", reason: "packet-revision-mismatch" };
|
|
12228
|
+
}
|
|
12229
|
+
packet = applyPreviewGeometryDelta(current, incoming);
|
|
12230
|
+
}
|
|
12231
|
+
let built;
|
|
12232
|
+
try {
|
|
12233
|
+
built = buildPreviewPacketScene(packet, this.geometries);
|
|
12234
|
+
if (this.pinGeometry) {
|
|
12235
|
+
for (const [geometryId, update] of built.geometryUpdates) {
|
|
12236
|
+
const geometry = built.geometries.get(geometryId);
|
|
12237
|
+
if (!geometry) {
|
|
12238
|
+
throw new Error("ICE3_PREVIEW_PACKET_INVALID: geometry is missing");
|
|
12239
|
+
}
|
|
12240
|
+
this.pinGeometry(geometry, update);
|
|
12241
|
+
}
|
|
12242
|
+
}
|
|
12243
|
+
} catch {
|
|
12244
|
+
return { kind: "fallback", reason: "packet-invalid" };
|
|
12245
|
+
}
|
|
12246
|
+
this.geometries = built.geometries;
|
|
12247
|
+
this.publishedScene = built.scene;
|
|
12248
|
+
return { kind: "packet", packet };
|
|
12249
|
+
}
|
|
12250
|
+
};
|
|
9769
12251
|
export {
|
|
9770
12252
|
ICE3,
|
|
9771
12253
|
ICEAssetLoader,
|
|
@@ -9778,13 +12260,31 @@ export {
|
|
|
9778
12260
|
ICENode,
|
|
9779
12261
|
ICEOrbitController,
|
|
9780
12262
|
ICEPerspectiveCamera,
|
|
12263
|
+
ICEPreviewPacketLoader,
|
|
9781
12264
|
ICEScene,
|
|
9782
12265
|
ICEScreenLayer,
|
|
9783
12266
|
ICETilesetHandle,
|
|
9784
12267
|
ICETilesetLoader,
|
|
9785
12268
|
ICETypeRegistry,
|
|
9786
12269
|
ICEWorldLayer,
|
|
12270
|
+
collectSceneItems,
|
|
9787
12271
|
deserializeScene,
|
|
9788
12272
|
serializeScene
|
|
9789
12273
|
};
|
|
12274
|
+
//! 阴影视锥:按场景包围盒拟合方向光的正交投影。
|
|
12275
|
+
//!
|
|
12276
|
+
//! 早期实现把光照视锥写死成 860 米见方、960 米深。两个后果都很贵:
|
|
12277
|
+
//! 2048 的深度图摊到 860 米是每纹素 0.42 米(薄墙的影子直接消失),
|
|
12278
|
+
//! 而深度偏差 0.0015 在 960 米的跨度上等于 1.4 米,短影子被整段吃掉。
|
|
12279
|
+
//! 视锥贴合场景后,偏差回到"厘米"量级,贴图分辨率也才有意义。
|
|
12280
|
+
//! 方向光阴影贴图。
|
|
12281
|
+
//! 一次深度 pass 把场景从光源方向渲染进深度纹理,主 pass 再按世界坐标比较深度。
|
|
12282
|
+
//! 只处理单方向光、单张贴图;视锥按场景包围盒拟合(见 ShadowFrustum)。
|
|
12283
|
+
//! 场景包围盒:给阴影视锥用。
|
|
12284
|
+
//! 引擎里没有现成的场景 bounds,而阴影视锥必须贴合实际内容——写死范围会让
|
|
12285
|
+
//! 深度精度和贴图分辨率一起浪费(见 ShadowFrustum)。这里按"几何局部包围盒
|
|
12286
|
+
//! 缓存 + 世界矩阵变换"累加,避免每帧扫描几十万个顶点。
|
|
12287
|
+
//! 相机深度通道:屏幕空间遮蔽的来源。
|
|
12288
|
+
//! 从**相机**方向渲染一遍深度。必须覆盖全部几何路径——瓦片走渲染批次,其余走实例化
|
|
12289
|
+
//! 批次;只画一条会让深度图大半是空的(SPEC-007)。
|
|
9790
12290
|
//# sourceMappingURL=index.js.map
|