@navaramap/three 0.0.5 → 0.0.6

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 (58) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/fontWorker-JfTCpd38.js +2 -0
  3. package/dist/assets/fontWorker-JfTCpd38.js.map +1 -0
  4. package/dist/assets/{index-TKhRxurv.js → index-B3U_DvcP.js} +2 -2
  5. package/dist/assets/index-B3U_DvcP.js.map +1 -0
  6. package/dist/assets/{navara_wasm_font_worker_bg-Kk06ArRB.wasm → navara_wasm_font_worker_bg-DympKYVX.wasm} +0 -0
  7. package/dist/assets/{navara_wasm_worker_bg-BZjLKf4i.wasm → navara_wasm_worker_bg-Bx_l45cW.wasm} +0 -0
  8. package/dist/index.d.ts +510 -374
  9. package/dist/index.js +6150 -5810
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.umd.cjs +1188 -793
  12. package/dist/index.umd.cjs.map +1 -1
  13. package/package.json +12 -12
  14. package/src/core/DescRegistry.ts +3 -2
  15. package/src/core/EffectDesc.ts +10 -0
  16. package/src/core/EffectDescRegistry.ts +14 -0
  17. package/src/core/MeshDesc.ts +39 -2
  18. package/src/core/MeshDescWithSelectiveEffect.ts +14 -2
  19. package/src/core/ViewContext.ts +116 -7
  20. package/src/device.ts +4 -2
  21. package/src/index.ts +123 -11
  22. package/src/layer.ts +7 -5
  23. package/src/layers/effect/MRTPassEffectDesc.ts +34 -3
  24. package/src/layers/effect/SelectiveEffectDesc.ts +10 -2
  25. package/src/layers/effect/TransparentPassEffectDesc.ts +5 -4
  26. package/src/layersManager.ts +32 -1
  27. package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +3 -2
  28. package/src/material/enhancer/polygon/polygonBaseEnhancer/index.ts +0 -6
  29. package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +0 -6
  30. package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +4 -10
  31. package/src/material/enhancer/polygon/polygonBaseEnhancer/state.ts +1 -4
  32. package/src/material/enhancer/polygon/polygonBaseEnhancer/types.ts +0 -9
  33. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/coverage.ts +31 -0
  34. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.test.ts +75 -0
  35. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +26 -0
  36. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +1 -1
  37. package/src/material/gbufferLayout.test.ts +171 -0
  38. package/src/material/gbufferLayout.ts +298 -0
  39. package/src/material/index.ts +1 -0
  40. package/src/material/macro/tileShader.test.ts +35 -33
  41. package/src/material/macro/tileShader.ts +33 -10
  42. package/src/material/overrideMaterialsForMRT.ts +113 -71
  43. package/src/mesh/DrapedMesh.test.ts +57 -0
  44. package/src/mesh/DrapedMesh.ts +82 -0
  45. package/src/mesh/model.ts +3 -0
  46. package/src/mesh/polygon.ts +3 -2
  47. package/src/mesh/polyline.ts +3 -0
  48. package/src/mesh/sdfText/layout.ts +1 -3
  49. package/src/mesh/tile/index.ts +8 -4
  50. package/src/passes/CustomRenderPass.test.ts +143 -0
  51. package/src/passes/CustomRenderPass.ts +273 -30
  52. package/src/passes/RenderTargetCopyPass.ts +6 -0
  53. package/src/plugins/AttributionPlugin/index.ts +3 -2
  54. package/src/shaders/index.ts +16 -0
  55. package/src/type/index.ts +9 -51
  56. package/dist/assets/fontWorker-OwvKiQ8o.js +0 -2
  57. package/dist/assets/fontWorker-OwvKiQ8o.js.map +0 -1
  58. package/dist/assets/index-TKhRxurv.js.map +0 -1
@@ -0,0 +1,143 @@
1
+ import type { Globe } from "@navaramap/core";
2
+ import {
3
+ Group,
4
+ Mesh,
5
+ MeshStandardMaterial,
6
+ PerspectiveCamera,
7
+ Scene,
8
+ WebGLRenderTarget,
9
+ } from "three";
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import { NVR_UNLIT_SCENE_DEFINE } from "../material";
13
+ import {
14
+ USE_GBUFFER_SHADOW_DEFINE,
15
+ resolveGBufferOptions,
16
+ } from "../material/gbufferLayout";
17
+ import type { Scenes } from "../scene";
18
+
19
+ import { CustomRenderPass } from "./CustomRenderPass";
20
+
21
+ const createScenes = (): Scenes => ({
22
+ light: new Group(),
23
+ mrt: new Scene(),
24
+ globe: new Scene(),
25
+ draped: new Scene(),
26
+ opaque: new Scene(),
27
+ transparent: new Scene(),
28
+ skyEnvMap: new Scene(),
29
+ });
30
+
31
+ const addMesh = (scene: Scene): MeshStandardMaterial => {
32
+ const material = new MeshStandardMaterial();
33
+ scene.add(new Mesh(undefined, material));
34
+ return material;
35
+ };
36
+
37
+ const createPass = (scenes: Scenes, lit: boolean) =>
38
+ new CustomRenderPass(
39
+ scenes,
40
+ new PerspectiveCamera(),
41
+ new WebGLRenderTarget(1, 1),
42
+ {} as Globe,
43
+ { buffers: resolveGBufferOptions({ shadow: true }), lit },
44
+ );
45
+
46
+ // Regression: `view.lit` was stamped on the G-buffer scenes only, so meshes
47
+ // in the forward-only scenes – the default routing - stayed lit.
48
+ describe("stampGBufferDefines", () => {
49
+ it("stamps the scene lit default on both G-buffer and forward-only scenes", () => {
50
+ const scenes = createScenes();
51
+ const mrtMaterial = addMesh(scenes.mrt);
52
+ const opaqueMaterial = addMesh(scenes.opaque);
53
+ const transparentMaterial = addMesh(scenes.transparent);
54
+
55
+ const pass = createPass(scenes, false);
56
+ pass["stampGBufferDefines"]();
57
+
58
+ for (const material of [mrtMaterial, opaqueMaterial, transparentMaterial]) {
59
+ expect(material.defines?.[NVR_UNLIT_SCENE_DEFINE]).toBe(1);
60
+ }
61
+ });
62
+
63
+ it("keeps the G-buffer defines out of the forward-only scenes", () => {
64
+ const scenes = createScenes();
65
+ const mrtMaterial = addMesh(scenes.mrt);
66
+ const opaqueMaterial = addMesh(scenes.opaque);
67
+
68
+ const pass = createPass(scenes, true);
69
+ pass["stampGBufferDefines"]();
70
+
71
+ expect(mrtMaterial.defines?.[USE_GBUFFER_SHADOW_DEFINE]).toBe(1);
72
+ // Single-attachment target – the extra outputs would be undeclared.
73
+ expect(opaqueMaterial.defines?.[USE_GBUFFER_SHADOW_DEFINE] ?? false).toBe(
74
+ false,
75
+ );
76
+ });
77
+
78
+ it("stamps the G-buffer defines when a material moves from opaque to mrt", () => {
79
+ const scenes = createScenes();
80
+ const material = new MeshStandardMaterial();
81
+ const mesh = new Mesh(undefined, material);
82
+ scenes.opaque.add(mesh);
83
+
84
+ const pass = createPass(scenes, true);
85
+ pass["stampGBufferDefines"]();
86
+
87
+ // A shared "already stamped" set would leave it without the outputs.
88
+ scenes.opaque.remove(mesh);
89
+ scenes.mrt.add(mesh);
90
+ pass["stampGBufferDefines"]();
91
+
92
+ expect(material.defines?.[USE_GBUFFER_SHADOW_DEFINE]).toBe(1);
93
+ });
94
+
95
+ it("restamps every material after setLit", () => {
96
+ const scenes = createScenes();
97
+ const opaqueMaterial = addMesh(scenes.opaque);
98
+
99
+ const pass = createPass(scenes, true);
100
+ pass["stampGBufferDefines"]();
101
+ expect(opaqueMaterial.defines?.[NVR_UNLIT_SCENE_DEFINE] ?? false).toBe(
102
+ false,
103
+ );
104
+
105
+ pass.setLit(false);
106
+ pass["stampGBufferDefines"]();
107
+ expect(opaqueMaterial.defines?.[NVR_UNLIT_SCENE_DEFINE]).toBe(1);
108
+ });
109
+ });
110
+
111
+ describe("globe-normal copy target", () => {
112
+ const size = (pass: CustomRenderPass) =>
113
+ pass.globeNormalCopyPass.texture.image as { width: number; height: number };
114
+
115
+ it("stays 1x1 while no draped mesh exists, even across a resize", () => {
116
+ const pass = createPass(createScenes(), true);
117
+ pass.setSize(800, 600);
118
+
119
+ expect(size(pass).width).toBe(1);
120
+ expect(size(pass).height).toBe(1);
121
+ });
122
+
123
+ it("applies the size stored while inactive once a draped mesh appears", () => {
124
+ const pass = createPass(createScenes(), true);
125
+ pass.setSize(800, 600);
126
+
127
+ pass["setGlobeNormalActive"](true);
128
+
129
+ expect(size(pass).width).toBe(800);
130
+ expect(size(pass).height).toBe(600);
131
+ });
132
+
133
+ it("shrinks back when the last draped mesh goes away", () => {
134
+ const pass = createPass(createScenes(), true);
135
+ pass.setSize(800, 600);
136
+ pass["setGlobeNormalActive"](true);
137
+
138
+ pass["setGlobeNormalActive"](false);
139
+
140
+ expect(size(pass).width).toBe(1);
141
+ expect(size(pass).height).toBe(1);
142
+ });
143
+ });
@@ -4,18 +4,31 @@ import {
4
4
  Color,
5
5
  DepthStencilFormat,
6
6
  DepthTexture,
7
- HalfFloatType,
8
- NearestFilter,
9
7
  RGBADepthPacking,
10
8
  Scene,
11
9
  UnsignedInt248Type,
12
10
  WebGLRenderTarget,
11
+ type Material,
12
+ type Mesh,
13
13
  type PerspectiveCamera,
14
+ type Texture,
14
15
  type WebGLRenderer,
15
16
  } from "three";
16
17
 
17
18
  import { RenderPass } from "../effects";
18
- import { DrapedMesh } from "../mesh/DrapedMesh";
19
+ import { NVR_BLENDED_DEFINE, NVR_UNLIT_SCENE_DEFINE } from "../material";
20
+ import {
21
+ GBUFFER_ATTACHMENT_NAMES,
22
+ GBUFFER_DEFINE_NAMES,
23
+ appendOptionalGBufferAttachments,
24
+ computeGBufferDefines,
25
+ computeGBufferTextureIndex,
26
+ createGBufferAttachments,
27
+ resolveGBufferOptions,
28
+ type GBufferTextureIndex,
29
+ type ResolvedGBufferOptions,
30
+ } from "../material/gbufferLayout";
31
+ import { DrapedMesh, setupMaterialForDrape } from "../mesh/DrapedMesh";
19
32
  import type { Scenes } from "../scene";
20
33
 
21
34
  import { AllDepthCopyPass, NormalCopyPass, RenderTargetCopyPass } from ".";
@@ -30,6 +43,14 @@ export type CustomRenderPassOptions = {
30
43
  debugNormal?: boolean;
31
44
  disableShadow?: boolean;
32
45
  allowTransparent?: boolean;
46
+ /** G-buffer attachment configuration. Defaults to all optional buffers disabled. */
47
+ buffers?: ResolvedGBufferOptions;
48
+ /**
49
+ * Scene-level default of the `lit` material option (`view.lit`). When
50
+ * `false`, materials without an explicit `lit` output plain albedo.
51
+ * @defaultValue true
52
+ */
53
+ lit?: boolean;
33
54
  };
34
55
 
35
56
  export class CustomRenderPass extends RenderPass {
@@ -53,7 +74,39 @@ export class CustomRenderPass extends RenderPass {
53
74
  });
54
75
 
55
76
  private debugNormalCopyPass?: NormalCopyPass;
77
+ private readonly gbufferScenes: readonly Scene[];
78
+ // Rendered outside the G-buffer pass, so these must never declare G-buffer
79
+ // outputs the render target has no attachments for.
80
+ private readonly forwardScenes: readonly Scene[];
81
+ private readonly stampScenes: readonly Scene[];
56
82
  private allowTransparent: boolean;
83
+ private buffers: ResolvedGBufferOptions;
84
+ // Scene-level default of the `lit` material option (`view.lit`).
85
+ private sceneLit: boolean;
86
+ // Template for rebuilding the G-buffer render target in setBuffers.
87
+ private inputBufferTemplate: WebGLRenderTarget;
88
+ /**
89
+ * Attachment indices of `gbufferRenderTarget.textures` for this view.
90
+ * Recomputed by {@link setBuffers}.
91
+ */
92
+ textureIndex: GBufferTextureIndex;
93
+ // Defines carrying the enabled optional outputs and their locations,
94
+ // stamped onto every material rendered into the G-buffer.
95
+ private gbufferDefines: Readonly<Record<string, number>>;
96
+ private gbufferDefinesStamped = new WeakSet<Material>();
97
+ // Separate from the G-buffer set: the two cover different scene sets, and a
98
+ // shared one would skip the G-buffer defines for a material first seen in
99
+ // the opaque scene that later moves to the MRT pass.
100
+ private litStamped = new WeakSet<Material>();
101
+ private stampDirty = true;
102
+ // Drives the globe-normal copy target, which stays 1x1 until a draped mesh
103
+ // exists. Tracked here because setSize can arrive while it is inactive.
104
+ private globeNormalActive = false;
105
+ private readonly globeNormalUniform: { value: Texture | null };
106
+ private width = 1;
107
+ private height = 1;
108
+ private lastProgramCount = -1;
109
+ private readonly lastSceneChildCounts = [-1, -1, -1, -1, -1];
57
110
 
58
111
  constructor(
59
112
  scenes: Scenes,
@@ -72,25 +125,20 @@ export class CustomRenderPass extends RenderPass {
72
125
  this._camera = camera;
73
126
  this.globe = globe;
74
127
 
128
+ this.gbufferScenes = [scenes.globe, scenes.mrt, scenes.draped];
129
+ this.forwardScenes = [scenes.opaque, scenes.transparent];
130
+ this.stampScenes = [...this.gbufferScenes, ...this.forwardScenes];
131
+
132
+ this.buffers = options?.buffers ?? resolveGBufferOptions();
133
+ this.sceneLit = options?.lit ?? true;
134
+ this.textureIndex = computeGBufferTextureIndex(this.buffers);
135
+ this.gbufferDefines = computeGBufferDefines(this.buffers);
136
+ this.inputBufferTemplate = inputBuffer;
137
+
75
138
  this.gbufferRenderTarget = inputBuffer.clone();
76
- // attachment 1: normal buffer
77
- this.gbufferRenderTarget.textures.push(
78
- this.gbufferRenderTarget.texture.clone(),
79
- );
80
- // attachment 2: effectIds buffer
81
- // HalfFloatType preserves integer masks up to 11 bits (0..2047) exactly.
82
- // NearestFilter is required because this stores discrete bitmask data.
83
- const effectIdsTexture = this.gbufferRenderTarget.texture.clone();
84
- effectIdsTexture.type = HalfFloatType;
85
- effectIdsTexture.minFilter = NearestFilter;
86
- effectIdsTexture.magFilter = NearestFilter;
87
- effectIdsTexture.generateMipmaps = false;
88
- this.gbufferRenderTarget.textures.push(effectIdsTexture);
89
- // attachment 3: emissive buffer (RGB=diffuseColor×intensity+emissive additive, A=unused)
90
- // HalfFloatType preserves HDR emissive values for bloom extraction.
91
- const emissiveTexture = this.gbufferRenderTarget.texture.clone();
92
- emissiveTexture.type = HalfFloatType;
93
- this.gbufferRenderTarget.textures.push(emissiveTexture);
139
+ // Extra G-buffer attachments (normal/effectIds/emissive) – see
140
+ // material/gbufferLayout.ts for the layout definition.
141
+ createGBufferAttachments(this.gbufferRenderTarget, this.buffers);
94
142
 
95
143
  // The gbuffer owns its depth texture instead of sharing the composer's one
96
144
  // (postprocessing >= 6.39 no longer exposes a writable shared depth
@@ -117,16 +165,27 @@ export class CustomRenderPass extends RenderPass {
117
165
  this.allowTransparent = options?.allowTransparent ?? true;
118
166
 
119
167
  this.globeNormalCopyPass = new NormalCopyPass();
120
- this.globeNormalCopyPass.setNormalTexture(
121
- this.gbufferRenderTarget.textures[1],
122
- );
168
+ // The copy target keeps its Texture identity across setSize, so draped
169
+ // materials can bind this ref once.
170
+ this.globeNormalUniform = { value: this.globeNormalCopyPass.texture };
123
171
  if (options?.debugNormal) {
124
172
  this.debugNormalCopyPass = new NormalCopyPass();
125
173
  this.debugNormalCopyPass.unpackNormal = true;
126
- this.debugNormalCopyPass.setNormalTexture(
127
- this.gbufferRenderTarget.textures[1],
128
- );
129
174
  }
175
+ this.bindNormalTexture();
176
+ }
177
+
178
+ /**
179
+ * Re-points the copy passes at the normal attachment. The attachment is
180
+ * freshly allocated on every {@link setBuffers}, so the reference cannot be
181
+ * captured once.
182
+ */
183
+ private bindNormalTexture(): void {
184
+ const index = this.textureIndex.normal;
185
+ if (index === undefined) return;
186
+ const texture = this.gbufferRenderTarget.textures[index];
187
+ this.globeNormalCopyPass.setNormalTexture(texture);
188
+ this.debugNormalCopyPass?.setNormalTexture(texture);
130
189
  }
131
190
 
132
191
  // Render the scene with world scene that includes user setting object like a light.
@@ -140,11 +199,170 @@ export class CustomRenderPass extends RenderPass {
140
199
  scene.remove(this._scenes.light);
141
200
  }
142
201
 
202
+ /**
203
+ * Applies a new derived buffer configuration. Attachment locations may have
204
+ * shifted, so every material is re-stamped and its shader recompiled.
205
+ */
206
+ setBuffers(buffers: ResolvedGBufferOptions): void {
207
+ // Only the attachment-backed names matter here — `globeNormal` is picked
208
+ // up by render() and must not trigger a rebuild on its own.
209
+ const unchanged = GBUFFER_ATTACHMENT_NAMES.every(
210
+ (name) => buffers[name] === this.buffers[name],
211
+ );
212
+ this.buffers = buffers;
213
+ if (unchanged) {
214
+ return;
215
+ }
216
+ this.textureIndex = computeGBufferTextureIndex(buffers);
217
+ this.gbufferDefines = computeGBufferDefines(buffers);
218
+ this.gbufferDefinesStamped = new WeakSet();
219
+ this.stampDirty = true;
220
+
221
+ // Must be a fresh target: splicing textures in place leaves the renderer's
222
+ // cached GL state on a stale attachment, so writes land in the framebuffer
223
+ // while samplers read empty storage. Only color and depth keep their
224
+ // Texture identity; every optional attachment is reallocated, which is why
225
+ // consumers must re-fetch each frame rather than capture at creation.
226
+ const old = this.gbufferRenderTarget;
227
+ const rt = this.inputBufferTemplate.clone();
228
+ rt.setSize(old.width, old.height);
229
+ rt.texture = old.texture;
230
+ appendOptionalGBufferAttachments(rt, buffers);
231
+ rt.depthTexture = old.depthTexture;
232
+ // three disposes rt.depthTexture with the target; detach the reused one.
233
+ old.depthTexture = null;
234
+ this.gbufferRenderTarget = rt;
235
+ old.dispose();
236
+
237
+ this.copyPass.setRenderTarget(rt);
238
+ this.bindNormalTexture();
239
+ }
240
+
241
+ /** Applies a new scene-level `lit` default (`view.lit` changed). */
242
+ setLit(lit: boolean): void {
243
+ if (this.sceneLit === lit) return;
244
+ this.sceneLit = lit;
245
+ this.litStamped = new WeakSet();
246
+ this.stampDirty = true;
247
+ }
248
+
249
+ /**
250
+ * O(1) gate keeping the stamping traversal off the per-frame path. The
251
+ * program count catches deeply-nested async additions (a glTF populating a
252
+ * scene-resident group): a material must compile before it can render, so
253
+ * the traversal converges one frame later.
254
+ */
255
+ private shouldStampGBufferDefines(renderer: WebGLRenderer): boolean {
256
+ let dirty = this.stampDirty;
257
+ this.stampDirty = false;
258
+
259
+ const programCount = renderer.info.programs?.length ?? 0;
260
+ if (programCount !== this.lastProgramCount) {
261
+ this.lastProgramCount = programCount;
262
+ dirty = true;
263
+ }
264
+
265
+ for (let i = 0; i < this.stampScenes.length; i++) {
266
+ const count = this.stampScenes[i].children.length;
267
+ if (count !== this.lastSceneChildCounts[i]) {
268
+ this.lastSceneChildCounts[i] = count;
269
+ dirty = true;
270
+ }
271
+ }
272
+
273
+ return dirty;
274
+ }
275
+
276
+ // Materials reach the G-buffer from anywhere (built-ins, enhancers, user
277
+ // ShaderMaterials), so they are stamped here rather than per-desc. `lit` is
278
+ // a lighting define, not a G-buffer one, hence the forward scenes too.
279
+ private stampGBufferDefines(): void {
280
+ for (const scene of this.gbufferScenes) {
281
+ scene.traverse((object) => {
282
+ this.forEachMaterial(object, (m) => this.stampGBufferDefine(m));
283
+ });
284
+ }
285
+ for (const scene of this.forwardScenes) {
286
+ scene.traverse((object) => {
287
+ this.forEachMaterial(object, (m) => this.stampLitDefine(m));
288
+ });
289
+ }
290
+ // Drape shading is opted into here rather than per-desc, so a mesh moved
291
+ // into the draped scene picks it up wherever it came from.
292
+ this._scenes.draped.traverse((object) => {
293
+ this.forEachMaterial(object, (m) =>
294
+ setupMaterialForDrape(m, this.globeNormalUniform),
295
+ );
296
+ });
297
+ }
298
+
299
+ private forEachMaterial(
300
+ object: unknown,
301
+ visit: (material: Material) => void,
302
+ ): void {
303
+ const material = (object as Partial<Mesh>).material;
304
+ if (!material) return;
305
+ if (Array.isArray(material)) {
306
+ for (const m of material) visit(m);
307
+ } else {
308
+ visit(material);
309
+ }
310
+ }
311
+
312
+ private stampGBufferDefine(material: Material): void {
313
+ material.defines ??= {};
314
+ let changed = false;
315
+
316
+ // `material.transparent` toggles at runtime (e.g. `globe.transparent`),
317
+ // so this one is synced on every visit instead of being gated by the set.
318
+ const blended = material.transparent ? 1 : false;
319
+ if ((material.defines[NVR_BLENDED_DEFINE] ?? false) !== blended) {
320
+ material.defines[NVR_BLENDED_DEFINE] = blended;
321
+ changed = true;
322
+ }
323
+
324
+ if (!this.gbufferDefinesStamped.has(material)) {
325
+ this.gbufferDefinesStamped.add(material);
326
+ for (const name of GBUFFER_DEFINE_NAMES) {
327
+ // `false` clears a stale define – three's generateDefines skips it.
328
+ const value = this.gbufferDefines[name] ?? false;
329
+ const current = material.defines[name] ?? false;
330
+ if (current !== value) {
331
+ material.defines[name] = value;
332
+ changed = true;
333
+ }
334
+ }
335
+ }
336
+
337
+ if (changed) {
338
+ material.needsUpdate = true;
339
+ }
340
+
341
+ this.stampLitDefine(material);
342
+ }
343
+
344
+ /** Per-material `lit` (NVR_LIT / NVR_UNLIT) wins over this in the shader. */
345
+ private stampLitDefine(material: Material): void {
346
+ if (this.litStamped.has(material)) return;
347
+ this.litStamped.add(material);
348
+
349
+ material.defines ??= {};
350
+ const sceneUnlit = this.sceneLit ? false : 1;
351
+ if ((material.defines[NVR_UNLIT_SCENE_DEFINE] ?? false) !== sceneUnlit) {
352
+ material.defines[NVR_UNLIT_SCENE_DEFINE] = sceneUnlit;
353
+ material.needsUpdate = true;
354
+ }
355
+ }
356
+
143
357
  render(
144
358
  renderer: WebGLRenderer,
145
359
  inputBuffer: WebGLRenderTarget | null,
146
360
  _outputBuffer: WebGLRenderTarget | null,
147
361
  ) {
362
+ if (this.shouldStampGBufferDefines(renderer)) {
363
+ this.stampGBufferDefines();
364
+ }
365
+
148
366
  const shouldDrapeByStencilTest = this._scenes.draped.children.length !== 0;
149
367
 
150
368
  const renderTarget = this.gbufferRenderTarget;
@@ -179,8 +397,15 @@ export class CustomRenderPass extends RenderPass {
179
397
 
180
398
  this._renderWithLight(renderer, this._scenes.globe);
181
399
 
182
- // Set normal texture for copy pass
183
- this.globeNormalCopyPass.render(renderer, null, null);
400
+ // Draped meshes read it and draw further down, so the copy still happens
401
+ // before its consumer; effects opt in through `requiredBuffers`.
402
+ const needsGlobeNormal =
403
+ (shouldDrapeByStencilTest || this.buffers.globeNormal) &&
404
+ this.textureIndex.normal !== undefined;
405
+ this.setGlobeNormalActive(needsGlobeNormal);
406
+ if (needsGlobeNormal) {
407
+ this.globeNormalCopyPass.render(renderer, null, null);
408
+ }
184
409
 
185
410
  this.globeDepthCopyPass.setDepthTexture(
186
411
  renderTarget.depthTexture as DepthTexture,
@@ -263,13 +488,31 @@ export class CustomRenderPass extends RenderPass {
263
488
  setDepthTexture(): void {}
264
489
 
265
490
  setSize(width: number, height: number) {
491
+ this.width = width;
492
+ this.height = height;
266
493
  this.gbufferRenderTarget.setSize(width, height);
267
494
  this.globeDepthCopyPass.setSize(width, height);
268
495
  this.allDepthCopyPass.setSize(width, height);
269
- this.globeNormalCopyPass.setSize(width, height);
496
+ if (this.globeNormalActive) {
497
+ this.globeNormalCopyPass.setSize(width, height);
498
+ }
270
499
  this.debugNormalCopyPass?.setSize(width, height);
271
500
  }
272
501
 
502
+ /**
503
+ * Grows the globe-normal copy target only while something consumes it, and
504
+ * shrinks it back to 1x1 otherwise — at full resolution it is a screen-sized
505
+ * RGBA16F target, the same cost again as the G-buffer's normal attachment.
506
+ */
507
+ private setGlobeNormalActive(active: boolean): void {
508
+ if (this.globeNormalActive === active) return;
509
+ this.globeNormalActive = active;
510
+ this.globeNormalCopyPass.setSize(
511
+ active ? this.width : 1,
512
+ active ? this.height : 1,
513
+ );
514
+ }
515
+
273
516
  // Drape a feature on the terrain by stencil test.
274
517
  // Refs
275
518
  // - https://www.isprs.org/proceedings/XXXVII/congress/2_pdf/5_WG-II-5/06.pdf
@@ -77,6 +77,12 @@ export class RenderTargetCopyPass extends Pass {
77
77
  this._material.uniforms.color.value = texture;
78
78
  }
79
79
 
80
+ /** Points the pass at a rebuilt render target (same-purpose replacement). */
81
+ setRenderTarget(renderTarget: WebGLRenderTarget): void {
82
+ this._renderTarget = renderTarget;
83
+ this.setTexture(renderTarget.texture);
84
+ }
85
+
80
86
  /**
81
87
  * When a depth texture is set, the pass copies its depth into the target's
82
88
  * depth buffer via gl_FragDepth. Depth test must be enabled (with AlwaysDepth)
@@ -18,7 +18,8 @@
18
18
  * await view.init();
19
19
  *
20
20
  * // A 3D-tiles layer whose tiles embed their own copyright (tracked dynamically).
21
- * const photoreal = view.addLayer({ type: "cesium3dtiles", data: { url } });
21
+ * const photorealSource = view.addSource({ type: "3d-tiles", url });
22
+ * const photoreal = view.addLayer({ type: "3d-tiles", source: photorealSource });
22
23
  *
23
24
  * // `add` / `remove` manage the set of displayed attributions.
24
25
  * view.attribution?.add([
@@ -124,7 +125,7 @@ let styleRefCount = 0;
124
125
  */
125
126
  const NAVARA_CREDIT: AttributionItem = {
126
127
  attribution: "Navara",
127
- attributionUrl: "https://navara-docs.netlify.app/",
128
+ attributionUrl: "https://navara-docs.reearth.workers.dev//",
128
129
  };
129
130
 
130
131
  /**
@@ -1 +1,17 @@
1
+ import { packing } from "@takram/three-geospatial/shaders";
2
+ import { ShaderChunk } from "three";
3
+
1
4
  export * from "@takram/three-geospatial/shaders";
5
+
6
+ /**
7
+ * Codec for the normal buffer's RG channels, which are **octahedral**, not a
8
+ * plain `xy * 2 - 1`: decode with `unpackVec2ToNormal`. Prepend to shaders
9
+ * that cannot use `#include` resolution (postprocessing effect fragments).
10
+ */
11
+ export const NORMAL_PACKING_SHADER: string = packing;
12
+
13
+ /**
14
+ * Three's `packing` chunk, for the MRT pass's packed depth buffers – check
15
+ * `depthBufferPacking` / `globeDepthBufferPacking` for the packing in use.
16
+ */
17
+ export const DEPTH_PACKING_SHADER: string = ShaderChunk.packing;
package/src/type/index.ts CHANGED
@@ -1,12 +1,5 @@
1
1
  import type { NormalizeWASMClass, TileHandle } from "@navaramap/core";
2
2
  import type {
3
- B3dmLayerDescription,
4
- PntsLayerDescription,
5
- Cesium3dTilesLayerDescription,
6
- GeoJsonLayerDescription,
7
- TerrainLayerDescription,
8
- TileLayerDescription,
9
- MvtLayerDescription,
10
3
  VectorLayerDescription,
11
4
  RasterLayerDescription,
12
5
  TerrainSourceLayerDescription,
@@ -56,16 +49,6 @@ export type BuiltInEffectDescription =
56
49
  | SkyEnvMapPassConfig
57
50
  | TransparentPassConfig;
58
51
 
59
- // export type MVTLayer = {
60
- // type: "mvt";
61
- // zoom: number;
62
- // layers?: string[];
63
- // height?: number;
64
- // extent?: Extent;
65
- // url: string;
66
- // color?: number;
67
- // };
68
-
69
52
  type Layer<LD> = NormalizeWASMClass<LD>;
70
53
 
71
54
  /**
@@ -87,31 +70,11 @@ type ConvertColorFields<T> = {
87
70
 
88
71
  /**
89
72
  * Helper type to enable Navara Color objects in all color-related fields.
90
- * This applies to model, point, billboard, text, polyline, polygon, rasterTile, etc.
73
+ * This applies to model, point, billboard, text, polyline, polygon, raster, etc.
91
74
  * Both number and Navara Color objects are accepted for backward compatibility.
92
75
  */
93
76
  type WithColorSupport<T> = ConvertColorFields<T>;
94
77
 
95
- export type TilesLayer = WithColorSupport<
96
- Layer<TileLayerDescription & { type: "tiles" }>
97
- >;
98
- export type TerrainLayer = Layer<TerrainLayerDescription & { type: "terrain" }>;
99
- export type GeoJsonLayer = WithColorSupport<
100
- Layer<GeoJsonLayerDescription & { type: "geojson" }>
101
- >;
102
- export type B3dmLayer = WithColorSupport<
103
- Layer<B3dmLayerDescription & { type: "b3dm" }>
104
- >;
105
- export type PntsLayer = WithColorSupport<
106
- Layer<PntsLayerDescription & { type: "pnts" }>
107
- >;
108
- export type Cesium3dTilesLayer = WithColorSupport<
109
- Layer<Cesium3dTilesLayerDescription & { type: "cesium3dtiles" }>
110
- >;
111
- export type MvtLayer = WithColorSupport<
112
- Layer<MvtLayerDescription & { type: "mvt" }>
113
- >;
114
-
115
78
  /**
116
79
  * A reference to a {@link Source}: either the source handle returned by
117
80
  * `addSource`, or its `id` string.
@@ -145,9 +108,14 @@ type TerrainSourceLayerBase = WithColorSupport<
145
108
  >;
146
109
 
147
110
  /**
148
- * A `terrain` layer renders a `raster-dem` or `quantized-mesh` source as the globe surface.
111
+ * A `terrain` layer renders a `raster-dem` or `quantized-mesh` source as the
112
+ * globe surface. Alternatively, omit `source` and pass `ellipsoid` to render a
113
+ * flat ellipsoid surface (e.g. to drape clamp-to-ground data on).
149
114
  */
150
- export type TerrainSourceLayer = SourceLayerBase<TerrainSourceLayerBase>;
115
+ export type TerrainSourceLayer = SourceLayerBase<TerrainSourceLayerBase> & {
116
+ /** Render a source-less flat ellipsoid surface. Mutually exclusive with `source`. */
117
+ ellipsoid?: TerrainSourceLayerBase["terrain"];
118
+ };
151
119
 
152
120
  type Tiles3dLayerBase = WithColorSupport<
153
121
  Layer<Tiles3dLayerDescription & { type: "3d-tiles" }>
@@ -157,17 +125,7 @@ type Tiles3dLayerBase = WithColorSupport<
157
125
  export type Tiles3dLayer = SourceLayerBase<Tiles3dLayerBase>;
158
126
 
159
127
  export type LayerDescription =
160
- | TilesLayer
161
- | TerrainLayer
162
- | GeoJsonLayer
163
- | B3dmLayer
164
- | PntsLayer
165
- | Cesium3dTilesLayer
166
- | MvtLayer
167
- | VectorLayer
168
- | RasterLayer
169
- | TerrainSourceLayer
170
- | Tiles3dLayer;
128
+ VectorLayer | RasterLayer | TerrainSourceLayer | Tiles3dLayer;
171
129
 
172
130
  /**
173
131
  * Fields common to every source, independent of its `type`.