@navaramap/three 0.0.8 → 0.1.1

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.
Files changed (39) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +20 -8
  3. package/dist/assets/fontWorker-C5cHhkWQ.js +2 -0
  4. package/dist/assets/fontWorker-C5cHhkWQ.js.map +1 -0
  5. package/dist/assets/{index-DaejBdUz.js → index-VfUncKuS.js} +2 -2
  6. package/dist/assets/index-VfUncKuS.js.map +1 -0
  7. package/dist/assets/{navara_wasm_font_worker_bg-CSmoDs_Q.wasm → navara_wasm_font_worker_bg-CKqHBoCR.wasm} +0 -0
  8. package/dist/assets/{navara_wasm_worker_bg-C1dUd_Nk.wasm → navara_wasm_worker_bg-CT26EH41.wasm} +0 -0
  9. package/dist/index.d.ts +298 -54
  10. package/dist/index.js +1569 -1396
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.umd.cjs +90 -69
  13. package/dist/index.umd.cjs.map +1 -1
  14. package/package.json +12 -12
  15. package/src/core/BaseDesc.ts +25 -1
  16. package/src/core/MeshDesc.test.ts +6 -8
  17. package/src/core/MeshDesc.ts +4 -6
  18. package/src/evaluations/FeatureEvaluator.ts +17 -10
  19. package/src/event/feature.ts +15 -2
  20. package/src/event/features/billboard.test.ts +62 -0
  21. package/src/event/features/billboard.ts +4 -2
  22. package/src/index.ts +266 -98
  23. package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +2 -2
  24. package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +2 -2
  25. package/src/material/macro/tileShader.ts +12 -2
  26. package/src/mesh/polyline.ts +32 -4
  27. package/src/mesh/sdfText/batchedSdfText.test.ts +65 -0
  28. package/src/mesh/sdfText/batchedSdfText.ts +121 -43
  29. package/src/mesh/sprite/instancedSprite.ts +173 -65
  30. package/src/pick/pickHelper.test.ts +389 -4
  31. package/src/pick/pickHelper.ts +212 -37
  32. package/src/plugins/AttributionPlugin/index.ts +1 -1
  33. package/src/renderOrder.ts +10 -0
  34. package/src/terrain/sampleTerrainMostDetailed.test.ts +6 -6
  35. package/src/terrain/sampleTerrainMostDetailed.ts +7 -5
  36. package/src/type/index.ts +22 -1
  37. package/dist/assets/fontWorker-CkLGtrXf.js +0 -2
  38. package/dist/assets/fontWorker-CkLGtrXf.js.map +0 -1
  39. package/dist/assets/index-DaejBdUz.js.map +0 -1
@@ -66,7 +66,15 @@ export class InstancedSpriteMesh
66
66
  implements PickableMesh, DeclutterParticipant
67
67
  {
68
68
  private _geometryType: SpriteGeometryType = GEOMETRY_TYPES.Point;
69
- private _batchIdToInstance = new Map<number, number>();
69
+ /**
70
+ * Feature (batch) index → this feature's instance ids. A feature owns
71
+ * multiple instances for MultiPoint geometry and for points derived from
72
+ * line/polygon vertices via `geometryTypes`, so per-feature styling must
73
+ * fan out to all of them. `null` means instances and features are 1:1.
74
+ */
75
+ private _batchIndexToInstances: Map<number, number[]> | null = null;
76
+ /** Instance count of the current geometry; bounds the identity fallback. */
77
+ private _instanceCount = 0;
70
78
  private _initialColor: Color = new Color(0xffffff);
71
79
  private _initialHeight = 0.0;
72
80
  private _initialSize = -1.0; // Negative value indicates "use uScale" in shader
@@ -161,7 +169,7 @@ export class InstancedSpriteMesh
161
169
  const state = enhancer.states();
162
170
  const cx = Math.min(Math.max(state.center[0], -0.5), 0.5);
163
171
  const cy = Math.min(Math.max(state.center[1], -0.5), 0.5);
164
- // Mirror of instancedSprite.vert.glsl:100-104 — aspect is per-instance
172
+ // Mirror of instancedSprite.vert.glsl:115-118 — aspect is per-instance
165
173
  // (from the atlas rect), not a material-level uniform; there is no
166
174
  // material-wide "aspect" state to read.
167
175
  const uvRect = state.billboard
@@ -181,9 +189,14 @@ export class InstancedSpriteMesh
181
189
 
182
190
  const override = overrides ? overrides[i] : Number.NaN;
183
191
  const rectH = uvRect ? uvRect.getW(i) : 0;
184
- const aspect = uvRect && rectH > 0.0 ? uvRect.getZ(i) / rectH : 1.0;
185
-
186
- // Mirror of instancedSprite.vert.glsl:95-97 — the quad spans
192
+ const rectW = uvRect ? uvRect.getZ(i) : 0;
193
+ // Mirror of the shader's empty-rect cull: an instance with no image
194
+ // packed yet is not drawn, so it must not reserve declutter space and
195
+ // hide the labels around it.
196
+ if (uvRect && (rectW <= 0.0 || rectH <= 0.0)) continue;
197
+ const aspect = uvRect ? rectW / rectH : 1.0;
198
+
199
+ // Mirror of instancedSprite.vert.glsl:122-125 — the quad spans
187
200
  // (position.xy - center) * vec2(aspect, 1) * size around the anchor.
188
201
  out.push({
189
202
  anchorX: anchors[i * 3],
@@ -481,17 +494,15 @@ export class InstancedSpriteMesh
481
494
  colorBuffer[i * 3 + 0] = this._initialColor.r;
482
495
  colorBuffer[i * 3 + 1] = this._initialColor.g;
483
496
  colorBuffer[i * 3 + 2] = this._initialColor.b;
484
-
485
- // Map batch ID to instance id
486
- // assuming batch IDs are 32bit floats
487
- const batchId = positionsInfo.batchIDs[i];
488
- this._batchIdToInstance.set(batchId, i);
489
497
  }
490
498
 
499
+ this._instanceCount = instanceCount;
500
+ this._rebuildBatchIndexMap(m);
501
+
491
502
  if (m instanceof NavaraBillboardMesh) {
492
503
  // instanceUvRect: vec4(x, y, w, h) — this instance's atlas sub-rect in
493
- // pixels. Zeroed until an image is packed; a zero-height rect samples a
494
- // transparent texel, so instances stay invisible rather than garbled.
504
+ // pixels. Zeroed until an image is packed; the vertex shader culls empty
505
+ // rects, so instances stay invisible rather than garbled.
495
506
  instancedGeometry.setAttribute(
496
507
  "instanceUvRect",
497
508
  new InstancedBufferAttribute(new Float32Array(instanceCount * 4), 4),
@@ -755,6 +766,20 @@ export class InstancedSpriteMesh
755
766
  rectAttr.setXYZW(i, rect.x, rect.y, rect.w, rect.h);
756
767
  }
757
768
  rectAttr.needsUpdate = true;
769
+ this._onAtlasRectsChanged();
770
+ }
771
+
772
+ /**
773
+ * An instance's atlas rect changed. The rect drives the quad's aspect and
774
+ * whether the instance draws at all, so the declutter bounds computed before
775
+ * the image landed are stale — and because packs resolve long after the
776
+ * frame that requested them, the view has usually gone idle by now. Without
777
+ * the render request the page keeps showing the pre-image state until the
778
+ * next camera move.
779
+ */
780
+ private _onAtlasRectsChanged(): void {
781
+ this.ctx.declutter?.markDirty();
782
+ this.ctx.renderFlag.forceUpdate = true;
758
783
  }
759
784
 
760
785
  onBeforePicking(): void {
@@ -781,32 +806,85 @@ export class InstancedSpriteMesh
781
806
  return this._enhancedMaterial;
782
807
  }
783
808
 
784
- setFeatureColorByBatchId(batchId: number, color: Color) {
785
- const instanceId = this._batchIdToInstance.get(batchId);
786
- if (instanceId === undefined) return;
809
+ /**
810
+ * Group instances by their feature's batch index from the geometry's
811
+ * per-instance `batch_index` buffer. The u32 view is consumed synchronously,
812
+ * so other wasm calls may detach views, so it must not be stored. The handle
813
+ * stays owned by the ECS geometry, whose destroy path frees it
814
+ * (`remove_from_buf`). An identity mapping (every feature owns exactly one
815
+ * instance, the common case) skips the map entirely.
816
+ */
817
+ private _rebuildBatchIndexMap(
818
+ m: NavaraPointMesh | NavaraBillboardMesh,
819
+ ): void {
820
+ const batchIndexData = m.geometry.batch_index?.data;
821
+ const batchIndices =
822
+ batchIndexData !== undefined ? this.ctx.buf.u32(batchIndexData) : null;
823
+ this._batchIndexToInstances = null;
824
+ if (!batchIndices) return;
825
+
826
+ let identity = true;
827
+ for (let i = 0; i < batchIndices.length; i++) {
828
+ if (batchIndices[i] !== i) {
829
+ identity = false;
830
+ break;
831
+ }
832
+ }
833
+ if (identity) return;
834
+
835
+ const map = new Map<number, number[]>();
836
+ for (let i = 0; i < batchIndices.length; i++) {
837
+ const batchIndex = batchIndices[i];
838
+ let instances = map.get(batchIndex);
839
+ if (!instances) {
840
+ instances = [];
841
+ map.set(batchIndex, instances);
842
+ }
843
+ instances.push(i);
844
+ }
845
+ this._batchIndexToInstances = map;
846
+ }
847
+
848
+ /** All instances owned by the feature at `batchIndex`; empty when unknown. */
849
+ private instancesOfBatchIndex(batchIndex: number): number[] {
850
+ if (this._batchIndexToInstances) {
851
+ return this._batchIndexToInstances.get(batchIndex) ?? [];
852
+ }
853
+ return batchIndex >= 0 && batchIndex < this._instanceCount
854
+ ? [batchIndex]
855
+ : [];
856
+ }
857
+
858
+ setFeatureColorByBatchIndex(batchIndex: number, color: Color) {
859
+ const instances = this.instancesOfBatchIndex(batchIndex);
860
+ if (instances.length === 0) return;
787
861
 
788
862
  const colorAttr = this.geometry.getAttribute(
789
863
  "instanceColor",
790
864
  ) as InstancedBufferAttribute;
791
- colorAttr.setXYZ(instanceId, color.r, color.g, color.b);
865
+ for (const instanceId of instances) {
866
+ colorAttr.setXYZ(instanceId, color.r, color.g, color.b);
867
+ }
792
868
  colorAttr.needsUpdate = true;
793
869
  }
794
870
 
795
- setFeatureShowByBatchId(batchId: number, rawVisible: boolean) {
796
- const instanceId = this._batchIdToInstance.get(batchId);
797
- if (instanceId === undefined) return;
871
+ setFeatureShowByBatchIndex(batchIndex: number, rawVisible: boolean) {
872
+ const instances = this.instancesOfBatchIndex(batchIndex);
873
+ if (instances.length === 0) return;
798
874
 
799
875
  const paramsAttr = this.geometry.getAttribute(
800
876
  "instanceParams",
801
877
  ) as InstancedBufferAttribute;
802
- paramsAttr.setZ(instanceId, rawVisible ? 1.0 : 0.0);
878
+ for (const instanceId of instances) {
879
+ paramsAttr.setZ(instanceId, rawVisible ? 1.0 : 0.0);
880
+ }
803
881
  paramsAttr.needsUpdate = true;
804
882
  this.ctx.declutter?.markDirty();
805
883
  }
806
884
 
807
- setFeatureOpacityByBatchId(batchId: number, opacity: number) {
808
- const instanceId = this._batchIdToInstance.get(batchId);
809
- if (instanceId === undefined) return;
885
+ setFeatureOpacityByBatchIndex(batchIndex: number, opacity: number) {
886
+ const instances = this.instancesOfBatchIndex(batchIndex);
887
+ if (instances.length === 0) return;
810
888
 
811
889
  const paramsAttr = this.geometry.getAttribute(
812
890
  "instanceParams",
@@ -814,20 +892,24 @@ export class InstancedSpriteMesh
814
892
  const clampedOpacity = Number.isFinite(opacity)
815
893
  ? Math.max(0.0, Math.min(1.0, opacity))
816
894
  : 1.0;
817
- paramsAttr.setW(instanceId, clampedOpacity);
895
+ for (const instanceId of instances) {
896
+ paramsAttr.setW(instanceId, clampedOpacity);
897
+ }
818
898
  paramsAttr.needsUpdate = true;
819
899
  }
820
900
 
821
- setFeatureHeightByBatchId(batchId: number, height: number) {
822
- const instanceId = this._batchIdToInstance.get(batchId);
823
- if (instanceId === undefined) return;
901
+ setFeatureHeightByBatchIndex(batchIndex: number, height: number) {
902
+ const instances = this.instancesOfBatchIndex(batchIndex);
903
+ if (instances.length === 0) return;
824
904
 
825
905
  const paramsAttr = this.geometry.getAttribute(
826
906
  "instanceParams",
827
907
  ) as InstancedBufferAttribute;
828
908
 
829
909
  const sanitizedHeight = Number.isFinite(height) ? height : 0.0;
830
- paramsAttr.setX(instanceId, sanitizedHeight);
910
+ for (const instanceId of instances) {
911
+ paramsAttr.setX(instanceId, sanitizedHeight);
912
+ }
831
913
  paramsAttr.needsUpdate = true;
832
914
  this.ctx.declutter?.markDirty();
833
915
  }
@@ -854,9 +936,12 @@ export class InstancedSpriteMesh
854
936
  * layer-level `declutterPriority` for this instance. Driven by the feature
855
937
  * evaluator.
856
938
  */
857
- setFeatureDeclutterPriorityByBatchId(batchId: number, priority: number) {
858
- const instanceId = this._batchIdToInstance.get(batchId);
859
- if (instanceId === undefined) return;
939
+ setFeatureDeclutterPriorityByBatchIndex(
940
+ batchIndex: number,
941
+ priority: number,
942
+ ) {
943
+ const instances = this.instancesOfBatchIndex(batchIndex);
944
+ if (instances.length === 0) return;
860
945
 
861
946
  if (!this._declutterPriorityOverrides) {
862
947
  const count = this._anchors ? this._anchors.length / 3 : 0;
@@ -865,15 +950,21 @@ export class InstancedSpriteMesh
865
950
  Number.NaN,
866
951
  );
867
952
  }
868
- if (instanceId >= this._declutterPriorityOverrides.length) return;
869
- if (this._declutterPriorityOverrides[instanceId] === priority) return;
870
- this._declutterPriorityOverrides[instanceId] = priority;
871
- this.ctx.declutter?.markDirty();
953
+ let changed = false;
954
+ for (const instanceId of instances) {
955
+ if (instanceId >= this._declutterPriorityOverrides.length) continue;
956
+ if (this._declutterPriorityOverrides[instanceId] === priority) continue;
957
+ this._declutterPriorityOverrides[instanceId] = priority;
958
+ changed = true;
959
+ }
960
+ if (changed) {
961
+ this.ctx.declutter?.markDirty();
962
+ }
872
963
  }
873
964
 
874
- setFeatureSizeByBatchId(batchId: number, size: number) {
875
- const instanceId = this._batchIdToInstance.get(batchId);
876
- if (instanceId === undefined) return;
965
+ setFeatureSizeByBatchIndex(batchIndex: number, size: number) {
966
+ const instances = this.instancesOfBatchIndex(batchIndex);
967
+ if (instances.length === 0) return;
877
968
 
878
969
  const paramsAttr = this.geometry.getAttribute(
879
970
  "instanceParams",
@@ -884,7 +975,9 @@ export class InstancedSpriteMesh
884
975
  ? -1.0
885
976
  : size
886
977
  : -1.0;
887
- paramsAttr.setY(instanceId, sanitizedSize);
978
+ for (const instanceId of instances) {
979
+ paramsAttr.setY(instanceId, sanitizedSize);
980
+ }
888
981
  paramsAttr.needsUpdate = true;
889
982
  this.ctx.declutter?.markDirty();
890
983
  }
@@ -897,45 +990,60 @@ export class InstancedSpriteMesh
897
990
  * reverts the feature to the material's default image. No-op for
898
991
  * non-billboard (point) meshes.
899
992
  */
900
- async setFeatureImageByBatchId(
901
- batchId: number,
993
+ async setFeatureImageByBatchIndex(
994
+ batchIndex: number,
902
995
  url: string | null | undefined,
903
996
  ): Promise<void> {
904
- const instanceId = this._batchIdToInstance.get(batchId);
905
- if (instanceId === undefined) return;
997
+ const instances = this.instancesOfBatchIndex(batchIndex);
998
+ if (instances.length === 0) return;
906
999
 
907
1000
  const rectAttr = this.geometry.getAttribute("instanceUvRect") as
908
1001
  InstancedBufferAttribute | undefined;
909
1002
  if (!rectAttr) return;
910
1003
 
911
1004
  if (url == null) {
912
- this._requestedImageUrls.delete(instanceId);
913
- if (!this._imageOverrides.delete(instanceId)) return;
914
- // Zero rect (invisible) until the default image finishes loading, same
915
- // as instances that never had an override.
916
- const rect = this._defaultRect;
917
- rectAttr.setXYZW(
918
- instanceId,
919
- rect?.x ?? 0,
920
- rect?.y ?? 0,
921
- rect?.w ?? 0,
922
- rect?.h ?? 0,
923
- );
924
- rectAttr.needsUpdate = true;
1005
+ let cleared = false;
1006
+ for (const instanceId of instances) {
1007
+ this._requestedImageUrls.delete(instanceId);
1008
+ if (!this._imageOverrides.delete(instanceId)) continue;
1009
+ // Zero rect (invisible) until the default image finishes loading,
1010
+ // same as instances that never had an override.
1011
+ const rect = this._defaultRect;
1012
+ rectAttr.setXYZW(
1013
+ instanceId,
1014
+ rect?.x ?? 0,
1015
+ rect?.y ?? 0,
1016
+ rect?.w ?? 0,
1017
+ rect?.h ?? 0,
1018
+ );
1019
+ cleared = true;
1020
+ }
1021
+ if (cleared) {
1022
+ rectAttr.needsUpdate = true;
1023
+ this._onAtlasRectsChanged();
1024
+ }
925
1025
  return;
926
1026
  }
927
1027
 
928
- this._requestedImageUrls.set(instanceId, url);
1028
+ for (const instanceId of instances) {
1029
+ this._requestedImageUrls.set(instanceId, url);
1030
+ }
929
1031
  const rect = await this._ensureAtlas().pack(url);
930
1032
  this._reportAtlasBytes();
931
1033
  if (!rect) return;
932
- // A newer override or a clear superseded this load while it was in flight.
933
- if (this._requestedImageUrls.get(instanceId) !== url) return;
934
-
935
- this._imageOverrides.add(instanceId);
936
- this._syncAtlasUniforms();
937
- rectAttr.setXYZW(instanceId, rect.x, rect.y, rect.w, rect.h);
938
- rectAttr.needsUpdate = true;
1034
+ let applied = false;
1035
+ for (const instanceId of instances) {
1036
+ // A newer override or a clear superseded this load while in flight.
1037
+ if (this._requestedImageUrls.get(instanceId) !== url) continue;
1038
+ this._imageOverrides.add(instanceId);
1039
+ rectAttr.setXYZW(instanceId, rect.x, rect.y, rect.w, rect.h);
1040
+ applied = true;
1041
+ }
1042
+ if (applied) {
1043
+ this._syncAtlasUniforms();
1044
+ rectAttr.needsUpdate = true;
1045
+ this._onAtlasRectsChanged();
1046
+ }
939
1047
  }
940
1048
 
941
1049
  dispose(): void {
@@ -958,7 +1066,7 @@ export class InstancedSpriteMesh
958
1066
  (this.material as ShaderMaterial).dispose();
959
1067
 
960
1068
  // Clear internal collections to release references
961
- this._batchIdToInstance.clear();
1069
+ this._batchIndexToInstances = null;
962
1070
  this._anchors = null;
963
1071
  this._declutterTargets = null;
964
1072
  this._declutterPriorityOverrides = null;