@woosh/meep-engine 2.165.5 → 2.165.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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts.map +1 -1
  3. package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +65 -12
  4. package/src/engine/ecs/gui/hud/testHudEdgeStick.d.ts +2 -0
  5. package/src/engine/ecs/gui/hud/testHudEdgeStick.d.ts.map +1 -0
  6. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +134 -0
  7. package/src/engine/ecs/gui/position/ViewportPositionSystem.d.ts.map +1 -1
  8. package/src/engine/ecs/gui/position/ViewportPositionSystem.js +44 -23
  9. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +24 -1
  10. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
  11. package/src/engine/graphics/ecs/trail3d/Trail3D.js +43 -0
  12. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.d.ts +1 -0
  13. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +9 -1
  14. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
  15. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +4 -0
  16. package/src/engine/graphics/ecs/trail3d/prototypeTrail3D.js +22 -1
  17. package/src/engine/graphics/render/forward_plus/LightManager.d.ts +5 -0
  18. package/src/engine/graphics/render/forward_plus/LightManager.d.ts.map +1 -1
  19. package/src/engine/graphics/render/forward_plus/LightManager.js +11 -0
  20. package/src/engine/graphics/render/forward_plus/model/AbstractLight.d.ts +16 -1
  21. package/src/engine/graphics/render/forward_plus/model/AbstractLight.d.ts.map +1 -1
  22. package/src/engine/graphics/render/forward_plus/model/AbstractLight.js +22 -1
  23. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts +7 -0
  24. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts.map +1 -1
  25. package/src/engine/graphics/render/forward_plus/model/Decal.js +11 -0
  26. package/src/engine/graphics/render/forward_plus/model/PointLight.d.ts +2 -0
  27. package/src/engine/graphics/render/forward_plus/model/PointLight.d.ts.map +1 -1
  28. package/src/engine/graphics/render/forward_plus/model/PointLight.js +10 -0
  29. package/src/engine/graphics/render/forward_plus/model/SpotLight.d.ts +2 -0
  30. package/src/engine/graphics/render/forward_plus/model/SpotLight.d.ts.map +1 -1
  31. package/src/engine/graphics/render/forward_plus/model/SpotLight.js +12 -0
  32. package/src/engine/graphics/trail/tube/TubeMaterialX.d.ts +8 -1
  33. package/src/engine/graphics/trail/tube/TubeMaterialX.d.ts.map +1 -1
  34. package/src/engine/graphics/trail/tube/TubeMaterialX.js +111 -15
  35. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.d.ts +9 -2
  36. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.d.ts.map +1 -1
  37. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +15 -4
  38. package/src/engine/graphics/trail/tube/TubeXPlugin.d.ts.map +1 -1
  39. package/src/engine/graphics/trail/tube/TubeXPlugin.js +7 -1
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Pure JavaScript game engine. Fully featured and production ready.",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "2.165.5",
9
+ "version": "2.165.6",
10
10
  "main": "build/meep.module.js",
11
11
  "module": "build/meep.module.js",
12
12
  "exports": {
@@ -1 +1 @@
1
- {"version":3,"file":"HeadsUpDisplaySystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js"],"names":[],"mappings":";AAeA;;;;;GAKG;AACH;IASI;;;OAGG;IACH,4BAFW,cAAc,EAqBxB;IA/BD,wCAAgC;IAGhC,2HAGE;IAaE,yBAA8B;IAM9B;;;;OAIG;IACH,+BAAiD;IAGrD,iCAGC;IAED,gCAIC;IAED;;;;;;;OAOG;IACH,sCAwGC;IAED,6BAsBC;CACJ;uBAtMsB,iBAAiB;2BAIb,qBAAqB;uBAFzB,kBAAkB;4CALG,uDAAuD;6BAMtE,iCAAiC;+BAL/B,qCAAqC"}
1
+ {"version":3,"file":"HeadsUpDisplaySystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js"],"names":[],"mappings":";AAeA;;;;;GAKG;AACH;IASI;;;OAGG;IACH,4BAFW,cAAc,EAqBxB;IA/BD,wCAAgC;IAGhC,2HAGE;IAaE,yBAA8B;IAM9B;;;;OAIG;IACH,+BAAiD;IAGrD,iCAGC;IAED,gCAIC;IAED;;;;;;;OAOG;IACH,sCA6JC;IAED,6BAsBC;CACJ;uBA3PsB,iBAAiB;2BAIb,qBAAqB;uBAFzB,kBAAkB;4CALG,uDAAuD;6BAMtE,iCAAiC;+BAL/B,qCAAqC"}
@@ -118,26 +118,79 @@ class HeadsUpDisplaySystem extends System {
118
118
  const worldPositionY = y;
119
119
  const worldPositionZ = z;
120
120
 
121
- // Convert the [-1, 1] screen coordinate into a world coordinate on the near plane
122
-
123
- // apply projection matrix
121
+ // project world position into clip space
124
122
  const _x = projection[0] * x + projection[4] * y + projection[8] * z + projection[12];
125
123
  const _y = projection[1] * x + projection[5] * y + projection[9] * z + projection[13];
126
124
  const _z = projection[2] * x + projection[6] * y + projection[10] * z + projection[14];
127
125
  const _w = projection[3] * x + projection[7] * y + projection[11] * z + projection[15];
128
126
 
129
- // apply perspective transform
130
- const d = 1 / Math.abs(_w);
131
- x = _x * d;
132
- y = _y * d;
133
- z = _z * d;
127
+ let ndcX;
128
+ let ndcY;
129
+ let trackedPositionOutOfBounds;
134
130
 
135
- const ndcX = (x + 1) / 2;
136
- const ndcY = (1 - y) / 2;
131
+ if (_w > 0) {
132
+ // point is in front of the camera plane, apply perspective transform
133
+ const d = 1 / _w;
137
134
 
138
- vp.position.set(ndcX, ndcY);
135
+ x = _x * d;
136
+ y = _y * d;
137
+ z = _z * d;
138
+
139
+ trackedPositionOutOfBounds = z > 1 || z < -1;
140
+
141
+ let dx = x * 0.5;
142
+ let dy = y * -0.5;
143
+
144
+ /*
145
+ Near the camera plane the projected position grows without bound while carrying no more
146
+ information than its direction from the viewport center. Cap it to the boundary of a doubled
147
+ viewport rect: edge-stick placement depends only on that direction, and unbounded coordinates
148
+ wreck float precision downstream (element extents cancel to zero next to values around 1e15).
149
+ */
150
+ const m = Math.max(Math.abs(dx), Math.abs(dy));
151
+
152
+ if (m > 1) {
153
+ const s = 1 / m;
154
+
155
+ dx *= s;
156
+ dy *= s;
157
+ }
158
+
159
+ ndcX = 0.5 + dx;
160
+ ndcY = 0.5 + dy;
161
+ } else {
162
+ /*
163
+ Point is on or behind the camera plane. Perspective division inverts through the projection
164
+ center, which would place the point on the opposite side of the screen from where it actually is.
165
+ Only the direction from the viewport center remains meaningful: clip-space (_x, -_y), the limit of
166
+ the in-front projection as a point approaches the camera plane.
139
167
 
140
- const trackedPositionOutOfBounds = z > 1 || z < -1;
168
+ Push the position out along that direction to the boundary of a doubled viewport rect. That is
169
+ guaranteed to be off-screen, letting stick-to-edge placement resolve it to the correct point on
170
+ the screen edge; without stick-to-edge the element is hidden anyway.
171
+ */
172
+ trackedPositionOutOfBounds = true;
173
+
174
+ let dx = _x;
175
+ let dy = -_y;
176
+
177
+ const m = Math.max(Math.abs(dx), Math.abs(dy));
178
+
179
+ if (m > 0) {
180
+ const s = 1 / m;
181
+
182
+ dx *= s;
183
+ dy *= s;
184
+ } else {
185
+ // point lies exactly on the camera's view axis, direction is undefined; push down
186
+ dy = 1;
187
+ }
188
+
189
+ ndcX = 0.5 + dx;
190
+ ndcY = 0.5 + dy;
191
+ }
192
+
193
+ vp.position.set(ndcX, ndcY);
141
194
 
142
195
  const visible = (!trackedPositionOutOfBounds || vp.stickToScreenEdge) && this.__visibility_predicate.test(worldPositionX, worldPositionY, worldPositionZ);
143
196
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=testHudEdgeStick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testHudEdgeStick.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/gui/hud/testHudEdgeStick.js"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ import { EngineHarness } from "../../../EngineHarness.js";
2
+ import Entity from "../../Entity.js";
3
+ import { Transform } from "../../transform/Transform.js";
4
+ import EmptyView from "../../../../view/elements/EmptyView.js";
5
+ import GUIElement from "../GUIElement.js";
6
+ import GUIElementSystem from "../GUIElementSystem.js";
7
+ import ViewportPosition from "../position/ViewportPosition.js";
8
+ import ViewportPositionSystem from "../position/ViewportPositionSystem.js";
9
+ import HeadsUpDisplay from "./HeadsUpDisplay.js";
10
+ import HeadsUpDisplaySystem from "./HeadsUpDisplaySystem.js";
11
+ import { EngineConfiguration } from "../../../EngineConfiguration.js";
12
+ import { AttachmentSystem } from "../../attachment/AttachmentSystem.js";
13
+ import { CameraSystem } from "../../../graphics/ecs/camera/CameraSystem.js";
14
+ import TopDownCameraController from "../../../graphics/ecs/camera/topdown/TopDownCameraController.js";
15
+
16
+ const eh = new EngineHarness();
17
+
18
+ /**
19
+ *
20
+ * @param {Engine} engine
21
+ * @return {EngineConfiguration}
22
+ */
23
+ function makeConfig(engine) {
24
+ const r = new EngineConfiguration();
25
+
26
+ r.addSystem(new AttachmentSystem());
27
+ r.addSystem(new HeadsUpDisplaySystem(engine.graphics));
28
+ r.addSystem(new ViewportPositionSystem(engine.gameView.size));
29
+ r.addSystem(new GUIElementSystem(engine.gui.view, engine));
30
+
31
+ return r;
32
+ }
33
+
34
+ const RING_RADIUS = 25;
35
+ const BADGE_COUNT = 12;
36
+
37
+ /**
38
+ *
39
+ * @param {Engine} engine
40
+ */
41
+ async function main(engine) {
42
+
43
+ await EngineHarness.buildBasics({
44
+ engine,
45
+ enableWater: false,
46
+ enableShadows: false,
47
+ pitch: 0.7,
48
+ distance: 10
49
+ });
50
+
51
+ // highlight stuck badges
52
+ const style = document.createElement('style');
53
+ style.textContent = `
54
+ .hud-edge-stick-badge {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ font: bold 12px sans-serif;
59
+ color: white;
60
+ border-radius: 50%;
61
+ border: 2px solid white;
62
+ box-sizing: border-box;
63
+ }
64
+ .hud-edge-stick-badge.hud-system-sticky-flag {
65
+ border-color: black;
66
+ }
67
+ `;
68
+ document.head.appendChild(style);
69
+
70
+ const ecd = engine.entityManager.dataset;
71
+
72
+ const tracked = [];
73
+
74
+ for (let i = 0; i < BADGE_COUNT; i++) {
75
+ const angle = (i / BADGE_COUNT) * Math.PI * 2;
76
+
77
+ const x = 5 + Math.cos(angle) * RING_RADIUS;
78
+ const z = 5 + Math.sin(angle) * RING_RADIUS;
79
+
80
+ const view = new EmptyView({
81
+ classList: ['hud-edge-stick-badge'],
82
+ css: {
83
+ background: `hsl(${(i / BADGE_COUNT) * 360}, 80%, 45%)`
84
+ }
85
+ });
86
+
87
+ view.size.set(28, 28);
88
+ view.el.textContent = String(i);
89
+
90
+ const transform = new Transform();
91
+ transform.position.set(x, 0.5, z);
92
+
93
+ const vp = new ViewportPosition();
94
+ vp.anchor.set(0.5, 0.5);
95
+ vp.stickToScreenEdge = true;
96
+ vp.screenEdgeWidth = 8;
97
+
98
+ const entity = new Entity()
99
+ .add(transform)
100
+ .add(new HeadsUpDisplay())
101
+ .add(vp)
102
+ .add(GUIElement.fromView(view))
103
+ .build(ecd);
104
+
105
+ tracked.push({ entity, transform, vp, view, index: i });
106
+ }
107
+
108
+ const active_camera = CameraSystem.getFirstActiveCamera(ecd);
109
+ const controller = ecd.getComponent(active_camera.entity, TopDownCameraController);
110
+
111
+ window.__hud_edge_stick_test = {
112
+ engine,
113
+ ecd,
114
+ tracked,
115
+ camera: active_camera.component,
116
+ controller
117
+ };
118
+ }
119
+
120
+ /**
121
+ *
122
+ * @param {EngineHarness} harness
123
+ */
124
+ async function init(harness) {
125
+ const engine = eh.engine;
126
+
127
+ await makeConfig(engine).apply(engine);
128
+
129
+ await eh.initialize();
130
+
131
+ main(engine);
132
+ }
133
+
134
+ init(eh);
@@ -1 +1 @@
1
- {"version":3,"file":"ViewportPositionSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/gui/position/ViewportPositionSystem.js"],"names":[],"mappings":";AAuBA;IACI,+BAoBC;IAjBG,8DAAkD;IAElD,kEAEC;IAED,kBAAgC;IAEhC,yCAME;IAEF,SAAc;IAGlB,2CAEC;IAED,4CAEC;IAED;;;OAGG;IACH,sBAHW,UAAU,MACV,gBAAgB,QAkE1B;IAED;;;;;OAKG;IACH,SAJW,gBAAgB,MAChB,UAAU,qBA8BpB;IAED;;;;;OAKG;IACH,WAJW,gBAAgB,MAChB,UAAU,qBAmBpB;IA4EL;;;OAGG;IACH,mCAFU,OAAO,CAEwC;CA7ExD;uBAlLsB,iBAAiB;uBACjB,kBAAkB;6BAEZ,uBAAuB;4CAJR,uDAAuD;8BAJrE,iDAAiD"}
1
+ {"version":3,"file":"ViewportPositionSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/gui/position/ViewportPositionSystem.js"],"names":[],"mappings":";AAwBA;IACI,+BAoBC;IAjBG,8DAAkD;IAElD,kEAEC;IAED,kBAAgC;IAEhC,yCAME;IAEF,SAAc;IAGlB,2CAEC;IAED,4CAEC;IAED;;;OAGG;IACH,sBAHW,UAAU,MACV,gBAAgB,QAkE1B;IAED;;;;;OAKG;IACH,SAJW,gBAAgB,MAChB,UAAU,qBA8BpB;IAED;;;;;OAKG;IACH,WAJW,gBAAgB,MAChB,UAAU,qBAmBpB;IAgGL;;;OAGG;IACH,mCAFU,OAAO,CAEwC;CAjGxD;uBAlLsB,iBAAiB;uBACjB,kBAAkB;6BAEZ,uBAAuB;4CAJR,uDAAuD;8BALrE,iDAAiD"}
@@ -1,6 +1,7 @@
1
1
  import { assert } from "../../../../core/assert.js";
2
2
  import { SignalBinding } from "../../../../core/events/signal/SignalBinding.js";
3
3
  import AABB2 from "../../../../core/geom/2d/aabb/AABB2.js";
4
+ import { clamp } from "../../../../core/math/clamp.js";
4
5
  import { EPSILON } from "../../../../core/math/EPSILON.js";
5
6
  import { ResourceAccessKind } from "../../../../core/model/ResourceAccessKind.js";
6
7
  import { ResourceAccessSpecification } from "../../../../core/model/ResourceAccessSpecification.js";
@@ -186,9 +187,6 @@ class ViewportPositionSystem extends System {
186
187
 
187
188
  const STICKY_FLAG_CLASS_NAME = 'hud-system-sticky-flag';
188
189
 
189
-
190
- const stickyBounds = new AABB2();
191
-
192
190
  /**
193
191
  *
194
192
  * @param {View} view
@@ -202,37 +200,60 @@ function setElementPosition(view, bounds, viewportSize, vp) {
202
200
 
203
201
  if (vp.stickToScreenEdge) {
204
202
 
205
-
206
203
  const screenEdgeWidth = vp.screenEdgeWidth;
207
204
  const clipEdgeX = screenEdgeWidth / viewportSize.x;
208
205
  const clipEdgeY = screenEdgeWidth / viewportSize.y;
209
206
 
207
+ const width = bounds.width;
208
+ const height = bounds.height;
210
209
 
211
- stickyBounds.set(clipEdgeX, clipEdgeY, 1 - clipEdgeX, 1 - clipEdgeY);
210
+ const anchorX = vp.anchor.x;
211
+ const anchorY = vp.anchor.y;
212
212
 
213
- let stickFlag = false;
213
+ // tracked point that the element hangs off, in normalized viewport coordinates
214
+ const px = bounds.x0 + width * anchorX;
215
+ const py = bounds.y0 + height * anchorY;
216
+
217
+ // rect of anchor positions for which the element stays fully on screen, inset by the edge margin
218
+ const ax0 = clipEdgeX + width * anchorX;
219
+ const ay0 = clipEdgeY + height * anchorY;
220
+ const ax1 = 1 - clipEdgeX - width * (1 - anchorX);
221
+ const ay1 = 1 - clipEdgeY - height * (1 - anchorY);
222
+
223
+ const stickFlag = px < ax0 || px > ax1 || py < ay0 || py > ay1;
224
+
225
+ if (stickFlag) {
226
+ /*
227
+ Tracked point is off-screen. Place the element on the line from the viewport center towards the
228
+ tracked point, at the point where that line leaves the valid rect. This keeps the element on the
229
+ invisible line leading to the tracked position, instead of clamping each axis independently,
230
+ which piles far-away directions into the corners.
231
+ */
232
+ const dx = px - 0.5;
233
+ const dy = py - 0.5;
234
+
235
+ let t = 1;
236
+
237
+ if (dx > 0) {
238
+ t = Math.min(t, (ax1 - 0.5) / dx);
239
+ } else if (dx < 0) {
240
+ t = Math.min(t, (ax0 - 0.5) / dx);
241
+ }
214
242
 
215
- if (bounds.x0 < stickyBounds.x0) {
216
- x = stickyBounds.x0;
243
+ if (dy > 0) {
244
+ t = Math.min(t, (ay1 - 0.5) / dy);
245
+ } else if (dy < 0) {
246
+ t = Math.min(t, (ay0 - 0.5) / dy);
247
+ }
217
248
 
218
- stickFlag = true;
219
- } else if (bounds.x1 > stickyBounds.x1) {
220
- x = stickyBounds.x1 - bounds.width;
249
+ // clamp covers rects that do not contain the viewport center, such as for oversized elements
250
+ const sx = clamp(0.5 + dx * t, ax0, ax1);
251
+ const sy = clamp(0.5 + dy * t, ay0, ay1);
221
252
 
222
- stickFlag = true;
253
+ x = sx - width * anchorX;
254
+ y = sy - height * anchorY;
223
255
  } else {
224
256
  x = bounds.x0;
225
- }
226
-
227
- if (bounds.y0 < stickyBounds.y0) {
228
- y = stickyBounds.y0;
229
-
230
- stickFlag = true;
231
- } else if (bounds.y1 > stickyBounds.y1) {
232
- y = stickyBounds.y1 - bounds.height;
233
-
234
- stickFlag = true;
235
- } else {
236
257
  y = bounds.y0;
237
258
  }
238
259
 
@@ -96,6 +96,27 @@ declare class Trail3D {
96
96
  get textureURL(): string;
97
97
  set depthWrite(arg: boolean);
98
98
  get depthWrite(): boolean;
99
+ /**
100
+ * @param {boolean} v
101
+ */
102
+ set lightingEnabled(arg: boolean);
103
+ /**
104
+ * Whether the trail's tube processes standard scene lighting. When enabled the
105
+ * tube shades as a matte (Lambert) surface under the scene's ambient /
106
+ * hemisphere / light-probe / directional / point / spot lights and receives
107
+ * shadows; it never casts them. Under the Forward+ pipeline the material is
108
+ * additionally rewritten by the renderer's material manager, so clustered
109
+ * point lights and decals apply as well. When disabled (default) the tube
110
+ * renders unlit.
111
+ *
112
+ * Stored in {@link Trail3DFlags.Lit}. This is a build-time option — it selects
113
+ * the compiled material and the mesh's shadow settings — so it must be set
114
+ * before the trail is built / registered with the system, and cannot change
115
+ * afterwards.
116
+ *
117
+ * @returns {boolean}
118
+ */
119
+ get lightingEnabled(): boolean;
99
120
  /**
100
121
  * @param {number|Trail3DFlags} flag
101
122
  * @returns {void}
@@ -124,7 +145,7 @@ declare class Trail3D {
124
145
  * Clear the trail's geometry and reset its state.
125
146
  */
126
147
  clear(): void;
127
- fromJSON({ maxAge, width, radialSegments, spawnMode, spawnDistance, textureURL, depthWrite, offset, color }: {
148
+ fromJSON({ maxAge, width, radialSegments, spawnMode, spawnDistance, textureURL, depthWrite, lightingEnabled, offset, color }: {
128
149
  maxAge?: number;
129
150
  width?: number;
130
151
  radialSegments?: number;
@@ -132,6 +153,7 @@ declare class Trail3D {
132
153
  spawnDistance?: number;
133
154
  textureURL: any;
134
155
  depthWrite?: boolean;
156
+ lightingEnabled?: boolean;
135
157
  offset?: Vector3;
136
158
  color?: {
137
159
  r: number;
@@ -154,6 +176,7 @@ declare class Trail3D {
154
176
  };
155
177
  textureURL: string;
156
178
  depthWrite: boolean;
179
+ lightingEnabled: boolean;
157
180
  offset: {
158
181
  x: number;
159
182
  y: number;
@@ -1 +1 @@
1
- {"version":3,"file":"Trail3D.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3D.js"],"names":[],"mappings":";AAgCA;;;;;GAKG;AACH;IA8MI,oCAMC;IAlND;;;OAGG;IACH,QAFU,MAAM,CAES;IAEzB;;;OAGG;IACH,OAFU,MAAM,CAEM;IAEtB;;;;OAIG;IACH,gBAFU,MAAM,CAEyB;IAEzC;;;OAGG;IACH,WAFU,cAAc,GAAC,MAAM,CAEC;IAEhC;;;;OAIG;IACH,eAFU,MAAM,CAEuB;IAEvC;;;OAGG;IACH,MAFU,MAAM,CAEP;IAET;;OAEG;IACH,eAFU,MAAM,CAEE;IAElB;;;OAGG;IACH,qBAFU,MAAM,CAEQ;IAExB;;;OAGG;IACH,yBAFU,MAAM,CAEY;IAE5B;;;;;OAKG;IACH,gBAFU,KAAK,CAEe;IAE9B;;;;OAIG;IACH,iBAFU,OAAO,CAEM;IAEvB;;;OAGG;IACH,MAFU,KAAK,GAAC,IAAI,CAER;IAEZ;;;OAGG;IACH,MAFU,OAAK,IAAI,CAEP;IAEZ;;;OAGG;IACH,UAFU,iBAAiB,CAEQ;IAEnC;;;OAGG;IACH,cAFU,SAAS,CAEG;IAEtB;;;OAGG;IACH,cAAsB;IAEtB,gBAQC;IAMD,4BAEC;IAND,yBAEC;IAUD,6BAEC;IAND,0BAEC;IAMD;;;OAGG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GAAC,YAAY,SACnB,OAAO,QAQjB;IAED;;;OAGG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,OAAO,CAInB;IAED;;OAEG;IACH,qBAFW,MAAM,QAehB;IAED;;OAEG;IACH,cAmBC;IAUD;;;;;;;;;;;;;;;aAsBC;IAED;;;;;;;;;;;;;;;;;;;MAYC;IAED;;OAEG;IACH,cAFW,OAAO,WAajB;IAkDD;;;;;;OAMG;IACH,cANW,MAAM,KACN,MAAM,KACN,MAAM,YACN,MAAM,GACJ,OAAO,CAiEnB;;CAEJ;;;;;+BAja8B,+BAA+B;sBAPxC,iCAAiC;oBACnC,kCAAkC;sBAShC,2BAA2B;kCACf,uCAAuC;0BAZ/C,yCAAyC;6BAatC,mBAAmB"}
1
+ {"version":3,"file":"Trail3D.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3D.js"],"names":[],"mappings":";AAiCA;;;;;GAKG;AACH;IAoPI,oCAMC;IAxPD;;;OAGG;IACH,QAFU,MAAM,CAES;IAEzB;;;OAGG;IACH,OAFU,MAAM,CAEM;IAEtB;;;;OAIG;IACH,gBAFU,MAAM,CAEyB;IAEzC;;;OAGG;IACH,WAFU,cAAc,GAAC,MAAM,CAEC;IAEhC;;;;OAIG;IACH,eAFU,MAAM,CAEuB;IAEvC;;;OAGG;IACH,MAFU,MAAM,CAEP;IAET;;OAEG;IACH,eAFU,MAAM,CAEE;IAElB;;;OAGG;IACH,qBAFU,MAAM,CAEQ;IAExB;;;OAGG;IACH,yBAFU,MAAM,CAEY;IAE5B;;;;;OAKG;IACH,gBAFU,KAAK,CAEe;IAE9B;;;;OAIG;IACH,iBAFU,OAAO,CAEM;IAEvB;;;OAGG;IACH,MAFU,KAAK,GAAC,IAAI,CAER;IAEZ;;;OAGG;IACH,MAFU,OAAK,IAAI,CAEP;IAEZ;;;OAGG;IACH,UAFU,iBAAiB,CAEQ;IAEnC;;;OAGG;IACH,cAFU,SAAS,CAEG;IAEtB;;;OAGG;IACH,cAAsB;IAEtB,gBAQC;IAMD,4BAEC;IAND,yBAEC;IAUD,6BAEC;IAND,0BAEC;IA0BD;;OAEG;IACH,kCAQC;IA/BD;;;;;;;;;;;;;;;OAeG;IACH,+BAEC;IAeD;;;OAGG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GAAC,YAAY,SACnB,OAAO,QAQjB;IAED;;;OAGG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,OAAO,CAInB;IAED;;OAEG;IACH,qBAFW,MAAM,QAoBhB;IAED;;OAEG;IACH,cAmBC;IAUD;;;;;;;;;;;;;;;;aAwBC;IAED;;;;;;;;;;;;;;;;;;;;MAaC;IAED;;OAEG;IACH,cAFW,OAAO,WAcjB;IAkDD;;;;;;OAMG;IACH,cANW,MAAM,KACN,MAAM,KACN,MAAM,YACN,MAAM,GACJ,OAAO,CAiEnB;;CAEJ;;;;;+BA3c8B,+BAA+B;sBAPxC,iCAAiC;oBACnC,kCAAkC;sBAShC,2BAA2B;kCACf,uCAAuC;0BAZ/C,yCAAyC;6BAatC,mBAAmB"}
@@ -1,3 +1,4 @@
1
+ import { assert } from "../../../../core/assert.js";
1
2
  import { FLOAT32_MAX } from "../../../../core/binary/FLOAT32_MAX.js";
2
3
  import { BvhClient } from "../../../../core/bvh2/bvh3/BvhClient.js";
3
4
  import { Color } from "../../../../core/color/Color.js";
@@ -164,6 +165,39 @@ class Trail3D {
164
165
  this.material.depthWrite = v;
165
166
  }
166
167
 
168
+ /**
169
+ * Whether the trail's tube processes standard scene lighting. When enabled the
170
+ * tube shades as a matte (Lambert) surface under the scene's ambient /
171
+ * hemisphere / light-probe / directional / point / spot lights and receives
172
+ * shadows; it never casts them. Under the Forward+ pipeline the material is
173
+ * additionally rewritten by the renderer's material manager, so clustered
174
+ * point lights and decals apply as well. When disabled (default) the tube
175
+ * renders unlit.
176
+ *
177
+ * Stored in {@link Trail3DFlags.Lit}. This is a build-time option — it selects
178
+ * the compiled material and the mesh's shadow settings — so it must be set
179
+ * before the trail is built / registered with the system, and cannot change
180
+ * afterwards.
181
+ *
182
+ * @returns {boolean}
183
+ */
184
+ get lightingEnabled() {
185
+ return this.getFlag(Trail3DFlags.Lit);
186
+ }
187
+
188
+ /**
189
+ * @param {boolean} v
190
+ */
191
+ set lightingEnabled(v) {
192
+ if (v === this.lightingEnabled) {
193
+ return;
194
+ }
195
+
196
+ assert.notOk(this.getFlag(Trail3DFlags.Built), "lightingEnabled cannot change after the trail is built");
197
+
198
+ this.writeFlag(Trail3DFlags.Lit, v);
199
+ }
200
+
167
201
  /**
168
202
  * @param {number|Trail3DFlags} flag
169
203
  * @returns {void}
@@ -214,6 +248,11 @@ class Trail3D {
214
248
 
215
249
  const mesh = ThreeFactory.createMesh(geometry, null);
216
250
 
251
+ // receive-only lighting: castShadow stays false. The geometry's `position`
252
+ // attribute holds ring CENTRES (the surface is extruded in the material's
253
+ // vertex stage), so a stock depth pass would render a degenerate line anyway.
254
+ mesh.receiveShadow = this.getFlag(Trail3DFlags.Lit);
255
+
217
256
  this.tube = tube;
218
257
  this.mesh = mesh;
219
258
  }
@@ -258,6 +297,7 @@ class Trail3D {
258
297
  spawnDistance = DEFAULT_SPAWN_DISTANCE,
259
298
  textureURL,
260
299
  depthWrite = false,
300
+ lightingEnabled = false,
261
301
  offset = Vector3.zero,
262
302
  color = { r: 1, g: 1, b: 1, a: 1 }
263
303
  }) {
@@ -269,6 +309,7 @@ class Trail3D {
269
309
  this.spawnDistance = spawnDistance;
270
310
  this.textureURL = textureURL;
271
311
  this.depthWrite = depthWrite;
312
+ this.lightingEnabled = lightingEnabled;
272
313
 
273
314
  this.offset.fromJSON(offset);
274
315
  this.color.fromJSON(color);
@@ -284,6 +325,7 @@ class Trail3D {
284
325
  color: this.color.toJSON(),
285
326
  textureURL: this.textureURL,
286
327
  depthWrite: this.depthWrite,
328
+ lightingEnabled: this.lightingEnabled,
287
329
  offset: this.offset.toJSON()
288
330
  };
289
331
  }
@@ -294,6 +336,7 @@ class Trail3D {
294
336
  equals(other) {
295
337
  return this.textureURL === other.textureURL
296
338
  && this.depthWrite === other.depthWrite
339
+ && this.lightingEnabled === other.lightingEnabled
297
340
  && this.width === other.width
298
341
  && this.radialSegments === other.radialSegments
299
342
  && this.maxAge === other.maxAge
@@ -5,5 +5,6 @@ export namespace Trail3DFlags {
5
5
  let Built: number;
6
6
  let BoundsNeedUpdate: number;
7
7
  let Reseed: number;
8
+ let Lit: number;
8
9
  }
9
10
  //# sourceMappingURL=Trail3DFlags.d.ts.map
@@ -14,5 +14,13 @@ export const Trail3DFlags = {
14
14
  * segment can bridge post-clear geometry to pre-clear history (e.g. a long
15
15
  * streak after teleporting the entity).
16
16
  */
17
- Reseed: 16
17
+ Reseed: 16,
18
+
19
+ /**
20
+ * The tube processes standard scene lighting (receive-only — it never casts
21
+ * shadows). Build-time flag: it selects the compiled material and the mesh's
22
+ * shadow settings, so it must be set before the trail is built / registered
23
+ * with the system; changing it afterwards has no effect.
24
+ */
25
+ Lit: 32
18
26
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Trail3DSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3DSystem.js"],"names":[],"mappings":";AAyBA;IAiCI;;;OAGG;IACH,4BAkBC;IAtDD,oDAAoC;IAEpC,+DAEE;IAEF;;OAEG;IACH,WAFU,0BAA0B,CAES;IAiB7C;;;OAGG;IACH,YAAgB;IAeZ;;OAEG;IACH,UAFU,cAAc,CAEO;IAE/B;;OAEG;IACH,aAFU,cAAY,IAAI,CAEH;IAG3B,2CAmBC;IAED,4CAIC;IA2DD;;;;OAIG;IACH,YAJW,OAAO,aACP,SAAS,YACT,MAAM,QAYhB;IAED;;;;OAIG;IACH,kBAJW,OAAO,aACP,SAAS,UACT,MAAM,QAQhB;IAED;;;OAGG;IACH,yBAHW,OAAO,aACP,SAAS,QAkCnB;IAED,6BAWC;;CACJ;uBA9OsB,wBAAwB;0BACrB,qCAAqC;oBAW3C,cAAc;4CAbU,uDAAuD;2CAUxD,0DAA0D;+BANtE,yBAAyB"}
1
+ {"version":3,"file":"Trail3DSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3DSystem.js"],"names":[],"mappings":";AAyBA;IAiCI;;;OAGG;IACH,4BAkBC;IAtDD,oDAAoC;IAEpC,+DAEE;IAEF;;OAEG;IACH,WAFU,0BAA0B,CAES;IAiB7C;;;OAGG;IACH,YAAgB;IAeZ;;OAEG;IACH,UAFU,cAAc,CAEO;IAE/B;;OAEG;IACH,aAFU,cAAY,IAAI,CAEH;IAG3B,2CAmBC;IAED,4CAIC;IA2DD;;;;OAIG;IACH,YAJW,OAAO,aACP,SAAS,YACT,MAAM,QAgBhB;IAED;;;;OAIG;IACH,kBAJW,OAAO,aACP,SAAS,UACT,MAAM,QAQhB;IAED;;;OAGG;IACH,yBAHW,OAAO,aACP,SAAS,QAkCnB;IAED,6BAWC;;CACJ;uBAlPsB,wBAAwB;0BACrB,qCAAqC;oBAW3C,cAAc;4CAbU,uDAAuD;2CAUxD,0DAA0D;+BANtE,yBAAyB"}
@@ -170,6 +170,10 @@ class Trail3DSystem extends System {
170
170
  * @param {number} entityId
171
171
  */
172
172
  link(trail, transform, entityId) {
173
+ // Trail3DFlags.Lit is final by link time — bake it into the material spec
174
+ // (the material cache key) so lit and unlit trails obtain distinct materials
175
+ trail.material.lit = trail.getFlag(Trail3DFlags.Lit);
176
+
173
177
  if (!trail.getFlag(Trail3DFlags.Built)) {
174
178
  this.#build_trail(trail, transform);
175
179
  } else {
@@ -6,6 +6,8 @@ import { EngineHarness } from "../../../EngineHarness.js";
6
6
  import { BehaviorComponent } from "../../../intelligence/behavior/ecs/BehaviorComponent.js";
7
7
  import { BehaviorSystem } from "../../../intelligence/behavior/ecs/BehaviorSystem.js";
8
8
  import { OrbitingBehavior } from "../../../intelligence/behavior/ecs/OrbitingBehavior.js";
9
+ import { Light } from "../light/Light.js";
10
+ import { LightType } from "../light/LightType.js";
9
11
  import { TrailSpawnMode } from "../../trail/TrailSpawnMode.js";
10
12
  import Trail3D from "./Trail3D.js";
11
13
  import Trail3DSystem from "./Trail3DSystem.js";
@@ -44,6 +46,9 @@ async function main(engine) {
44
46
  // exercise the distance-based spawning path too
45
47
  spawnMode: TrailSpawnMode.Distance,
46
48
  spawnDistance: 0.15,
49
+ // alternate unlit / lit around the ring: lit tubes shade under the
50
+ // harness sun + ambient fill and receive (never cast) shadows
51
+ lightingEnabled: i % 2 === 1,
47
52
  color: { r: 1, g: 1, b: 1, a: 1 }
48
53
  }));
49
54
 
@@ -59,7 +64,23 @@ async function main(engine) {
59
64
  entity.build(ecd);
60
65
  }
61
66
 
62
- console.log("Trail3D prototype: spawned", emitter_count, "orbiting tube emitters");
67
+ // a strong green point light in the middle of the ring. Non-shadow-casting
68
+ // POINT lights route through the Forward+ clustered path, so it only shows
69
+ // on materials that went through the FP material transformer — including
70
+ // the lit tubes.
71
+ new Entity()
72
+ .add(Light.fromJSON({
73
+ type: LightType.POINT,
74
+ color: { r: 0.1, g: 1, b: 0.1 },
75
+ intensity: 4,
76
+ distance: 8
77
+ }))
78
+ .add(Transform.fromJSON({
79
+ position: { x: 9, y: 2, z: 9 }
80
+ }))
81
+ .build(ecd);
82
+
83
+ console.log("Trail3D prototype: spawned", emitter_count, "orbiting tube emitters (odd indices lit) + clustered point light");
63
84
  }
64
85
 
65
86
  harness.initialize({
@@ -191,6 +191,11 @@ export class LightManager {
191
191
  * @private
192
192
  */
193
193
  private __handle_light_dimensions_change;
194
+ /**
195
+ *
196
+ * @private
197
+ */
198
+ private __handle_light_appearance_change;
194
199
  /**
195
200
  *
196
201
  * @param {LightRenderMetadata} data
@@ -1 +1 @@
1
- {"version":3,"file":"LightManager.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/render/forward_plus/LightManager.js"],"names":[],"mappings":"AAoIA;IAiBQ;;;;OAIG;IACH,2BAAgD;IAEhD,oCAAoC;IACpC,yCAA4C;IAE5C;;;;;;;;OAQG;IACH,0BAKC;IAaD;;;;;OAKG;IACH,+BAAoD;IAMpD;;;;;OAKG;IACH,8BAAmD;IAMnD;;;;OAIG;IACH,uCAAiF;IAWjF;;;;;OAKG;IACH,+BAEE;IAEF;;;;OAIG;IACH,iCAA8E;IAE9E;;;;OAIG;IACH,2BAAmC;IAEnC;;;;;OAKG;IACH,kCAAoE;IACpE;;;;;OAKG;IACH,kCAAoE;IAEpE;;;;OAIG;IACH,gCAAiC;IAEjC;;;;OAIG;IACH,6BAAiD;IACjD;;;;OAIG;IACH,6BAAiD;IAEjD;;;;OAIG;IACH,uBAAmC;IAEnC;;;;OAIG;IACH,8BAAiD;IAEjD;;;;OAIG;IACH,4BAA+C;IAG/C;;;;OAIG;IACH,yBAA2C;IAE3C;;;;OAIG;IACH,iCAAmD;IAUnD;;;;;OAKG;IACH,kCAAqC;IAErC;;;;OAIG;IACH,mCAAsC;IAK1C,0BAEC;IAED;;;;;;OAMG;IACH,0CAQC;IAED;;;OAGG;IACH,uCAEC;IAED,qCAQC;IAED;;;;;;OAMG;IACH,4CAgBC;IAED,iCAIC;IAED,gCA6BC;IAED;;;;;OAKG;IACH,6BAEC;IAED;;;OAGG;IACH,oBAFa,WAAW,CAIvB;IAED;;;OAGG;IACH,kBAFa,WAAW,CAIvB;IAED;;OAEG;IACH,sBAFa,aAAa,CAIzB;IAED;;OAEG;IACH,wBAFa,WAAW,CAIvB;IAED;;;OAGG;IACH,iBAFa,SAAS,OAAO,CAAC,CAI7B;IAED;;;OAGG;IACH,yCAKC;IAED;;;;OAIG;IACH,qCAyBC;IAED;;;;OAIG;IACH,uCA8BC;IAED;;;;OAIG;IACH,qCAMC;IAED;;;;OAIG;IACH,uCAKC;IAED;;;;;OAKG;IACH,4CAJW,mBAAmB,GACjB,MAAM,CAalB;IAED;;;;;;OAMG;IACH,+BAgCC;IAED;;;OAGG;IACH,6BAoBC;IAED,6BAYC;IAGD;;;;OAIG;IACH,6BAMC;IAED,mCA0EC;IAED,mCAgFC;IAED;;;OAGG;IACH,oCA+IC;IAED;;;OAGG;IACH,uCAUC;IAED;;;OAGG;IACH,oCAOC;IAED;;;OAGG;IACH,uCAWC;IAED;;;OAGG;IACH,mBAFW,SAAO,MAAM,iBAAiB,QAmDxC;IAED,gBAKC;IAED;;;;;OAKG;IACH,wBAJW,MAAM,KACN,MAAM,KACN,MAAM,QA2ChB;IAED;;;;OAIG;IACH,gCAFa,OAAO,CAInB;IAED;;;OAGG;IACH,qCAMC;IAED;;;;OAIG;IACH,mCAFa,OAAO,CAiBnB;;CACJ;4BAvrCM,OAAO;8BAAP,OAAO;oBAkBM,kCAAkC;oCAiBlB,0BAA0B"}
1
+ {"version":3,"file":"LightManager.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/render/forward_plus/LightManager.js"],"names":[],"mappings":"AAoIA;IAiBQ;;;;OAIG;IACH,2BAAgD;IAEhD,oCAAoC;IACpC,yCAA4C;IAE5C;;;;;;;;OAQG;IACH,0BAKC;IAaD;;;;;OAKG;IACH,+BAAoD;IAMpD;;;;;OAKG;IACH,8BAAmD;IAMnD;;;;OAIG;IACH,uCAAiF;IAWjF;;;;;OAKG;IACH,+BAEE;IAEF;;;;OAIG;IACH,iCAA8E;IAE9E;;;;OAIG;IACH,2BAAmC;IAEnC;;;;;OAKG;IACH,kCAAoE;IACpE;;;;;OAKG;IACH,kCAAoE;IAEpE;;;;OAIG;IACH,gCAAiC;IAEjC;;;;OAIG;IACH,6BAAiD;IACjD;;;;OAIG;IACH,6BAAiD;IAEjD;;;;OAIG;IACH,uBAAmC;IAEnC;;;;OAIG;IACH,8BAAiD;IAEjD;;;;OAIG;IACH,4BAA+C;IAG/C;;;;OAIG;IACH,yBAA2C;IAE3C;;;;OAIG;IACH,iCAAmD;IAUnD;;;;;OAKG;IACH,kCAAqC;IAErC;;;;OAIG;IACH,mCAAsC;IAK1C,0BAEC;IAED;;;;;;OAMG;IACH,0CAQC;IAED;;;OAGG;IACH,uCAEC;IAED,qCAQC;IAED;;;;;;OAMG;IACH,4CAgBC;IAED,iCAIC;IAED,gCA6BC;IAED;;;;;OAKG;IACH,6BAEC;IAED;;;OAGG;IACH,oBAFa,WAAW,CAIvB;IAED;;;OAGG;IACH,kBAFa,WAAW,CAIvB;IAED;;OAEG;IACH,sBAFa,aAAa,CAIzB;IAED;;OAEG;IACH,wBAFa,WAAW,CAIvB;IAED;;;OAGG;IACH,iBAFa,SAAS,OAAO,CAAC,CAI7B;IAED;;;OAGG;IACH,yCAKC;IAED;;;OAGG;IACH,yCAGC;IAED;;;;OAIG;IACH,qCAyBC;IAED;;;;OAIG;IACH,uCA8BC;IAED;;;;OAIG;IACH,qCAOC;IAED;;;;OAIG;IACH,uCAMC;IAED;;;;;OAKG;IACH,4CAJW,mBAAmB,GACjB,MAAM,CAalB;IAED;;;;;;OAMG;IACH,+BAgCC;IAED;;;OAGG;IACH,6BAoBC;IAED,6BAYC;IAGD;;;;OAIG;IACH,6BAMC;IAED,mCA0EC;IAED,mCAgFC;IAED;;;OAGG;IACH,oCA+IC;IAED;;;OAGG;IACH,uCAUC;IAED;;;OAGG;IACH,oCAOC;IAED;;;OAGG;IACH,uCAWC;IAED;;;OAGG;IACH,mBAFW,SAAO,MAAM,iBAAiB,QAmDxC;IAED,gBAKC;IAED;;;;;OAKG;IACH,wBAJW,MAAM,KACN,MAAM,KACN,MAAM,QA2ChB;IAED;;;;OAIG;IACH,gCAFa,OAAO,CAInB;IAED;;;OAGG;IACH,qCAMC;IAED;;;;OAIG;IACH,mCAFa,OAAO,CAiBnB;;CACJ;4BAlsCM,OAAO;8BAAP,OAAO;oBAkBM,kCAAkC;oCAiBlB,0BAA0B"}
@@ -505,6 +505,15 @@ export class LightManager {
505
505
  this.__light_data_needs_update = true;
506
506
  }
507
507
 
508
+ /**
509
+ *
510
+ * @private
511
+ */
512
+ __handle_light_appearance_change() {
513
+ // appearance changes (such as color or intensity) don't affect bounds, only the encoded data
514
+ this.__light_data_needs_update = true;
515
+ }
516
+
508
517
  /**
509
518
  *
510
519
  * @param {LightRenderMetadata} data
@@ -585,6 +594,7 @@ export class LightManager {
585
594
  const light = data.light;
586
595
 
587
596
  light.onDimensionChanged(this.__handle_light_dimensions_change, this);
597
+ light.onAppearanceChanged(this.__handle_light_appearance_change, this);
588
598
  }
589
599
 
590
600
  /**
@@ -597,6 +607,7 @@ export class LightManager {
597
607
 
598
608
  const light = data.light;
599
609
  light.offDimensionChanged(this.__handle_light_dimensions_change, this);
610
+ light.offAppearanceChanged(this.__handle_light_appearance_change, this);
600
611
  }
601
612
 
602
613
  /**
@@ -2,7 +2,7 @@ export class AbstractLight {
2
2
  id: number;
3
3
  toString(): string;
4
4
  /**
5
- *
5
+ * Dimension changes affect the light's bounds ({@link AbstractLight#getAABB}) as well as its encoded record
6
6
  * @param {function} f
7
7
  * @param {*} [thisArg]
8
8
  * @returns {void}
@@ -15,6 +15,21 @@ export class AbstractLight {
15
15
  * @returns {void}
16
16
  */
17
17
  offDimensionChanged(f: Function, thisArg?: any): void;
18
+ /**
19
+ * Appearance changes affect the light's encoded record ({@link AbstractLight#toArray}) but not its bounds,
20
+ * such as color or intensity
21
+ * @param {function} f
22
+ * @param {*} [thisArg]
23
+ * @returns {void}
24
+ */
25
+ onAppearanceChanged(f: Function, thisArg?: any): void;
26
+ /**
27
+ *
28
+ * @param {function} f
29
+ * @param {*} [thisArg]
30
+ * @returns {void}
31
+ */
32
+ offAppearanceChanged(f: Function, thisArg?: any): void;
18
33
  /**
19
34
  *
20
35
  * @param {number[]} result
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractLight.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/AbstractLight.js"],"names":[],"mappings":"AAEA;IAEI,WAAkB;IAGlB,mBAEC;IAED;;;;;OAKG;IACH,gDAFa,IAAI,CAIhB;IAED;;;;;OAKG;IACH,iDAFa,IAAI,CAIhB;IAED;;;;OAIG;IACH,kBAHW,MAAM,EAAE,GACN,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,EAAE,GAAC,UAAU,MAAM,CAAC,GAAC,YAAY,GACrC,IAAI,CAIhB;IAED;;;;;OAKG;IACH,qBAJW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,WAClC,MAAM,GACJ,MAAM,CAIlB;IAED;;;;OAIG;IACH,eAHW,aAAa,GACX,MAAM,CAIlB;IAGL,aAA4B;CAF3B"}
1
+ {"version":3,"file":"AbstractLight.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/AbstractLight.js"],"names":[],"mappings":"AAEA;IAEI,WAAkB;IAGlB,mBAEC;IAED;;;;;OAKG;IACH,gDAFa,IAAI,CAIhB;IAED;;;;;OAKG;IACH,iDAFa,IAAI,CAIhB;IAED;;;;;;OAMG;IACH,iDAFa,IAAI,CAIhB;IAED;;;;;OAKG;IACH,kDAFa,IAAI,CAIhB;IAED;;;;OAIG;IACH,kBAHW,MAAM,EAAE,GACN,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,EAAE,GAAC,UAAU,MAAM,CAAC,GAAC,YAAY,GACrC,IAAI,CAIhB;IAED;;;;;OAKG;IACH,qBAJW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,WAClC,MAAM,GACJ,MAAM,CAIlB;IAED;;;;OAIG;IACH,eAHW,aAAa,GACX,MAAM,CAIlB;IAGL,aAA4B;CAF3B"}
@@ -10,7 +10,7 @@ export class AbstractLight {
10
10
  }
11
11
 
12
12
  /**
13
- *
13
+ * Dimension changes affect the light's bounds ({@link AbstractLight#getAABB}) as well as its encoded record
14
14
  * @param {function} f
15
15
  * @param {*} [thisArg]
16
16
  * @returns {void}
@@ -29,6 +29,27 @@ export class AbstractLight {
29
29
  throw new Error('Not Implemented');
30
30
  }
31
31
 
32
+ /**
33
+ * Appearance changes affect the light's encoded record ({@link AbstractLight#toArray}) but not its bounds,
34
+ * such as color or intensity
35
+ * @param {function} f
36
+ * @param {*} [thisArg]
37
+ * @returns {void}
38
+ */
39
+ onAppearanceChanged(f, thisArg) {
40
+ throw new Error('Not Implemented');
41
+ }
42
+
43
+ /**
44
+ *
45
+ * @param {function} f
46
+ * @param {*} [thisArg]
47
+ * @returns {void}
48
+ */
49
+ offAppearanceChanged(f, thisArg) {
50
+ throw new Error('Not Implemented');
51
+ }
52
+
32
53
  /**
33
54
  *
34
55
  * @param {number[]} result
@@ -29,6 +29,13 @@ export class Decal extends AbstractLight {
29
29
  compare(other: any): number;
30
30
  onDimensionChanged(f: any, thisArg: any): void;
31
31
  offDimensionChanged(f: any, thisArg: any): void;
32
+ /**
33
+ * Appearance data (color, uv) is stored in raw arrays without change signals; uv only shifts when the
34
+ * texture atlas re-packs, which happens alongside decal addition/removal where the encoded data is
35
+ * invalidated anyway
36
+ */
37
+ onAppearanceChanged(f: any, thisArg: any): void;
38
+ offAppearanceChanged(f: any, thisArg: any): void;
32
39
  getCenter(result: any): void;
33
40
  getAABB(result: any): void;
34
41
  toArray(destination: any, address: any): number;
@@ -1 +1 @@
1
- {"version":3,"file":"Decal.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/Decal.js"],"names":[],"mappings":"AASA;IAIQ;;;OAGG;IACH,WAFU,MAAM,EAAE,GAAC,YAAY,GAAC,MAAM,CAED;IACrC,gCAA6C;IAC7C,iBAA6B;IAE7B,oBAAgC;IAKhC;;;OAGG;IACH,2BAA2B;IAE3B;;;;OAIG;IACH,eAFU,MAAM,CAEM;IAEtB,0EAA6C;IAGjD;;;OAGG;IACH,gBAFW,MAAM,EAAE,GAAC,YAAY,GAAC,UAAU,MAAM,CAAC,QAOjD;IAED,6CAGC;IAED,yCAGC;IAED,4BAUC;IAED,+CAGC;IAED,gDAGC;IAED,6BAKC;IAED,2BAIC;IAED,gDAcC;IAGL;;;OAGG;IACH,kBAFU,OAAO,CAEM;IAEvB,2BAAkC;CARjC;8BAjH6B,oBAAoB;mBAK/B,6CAA6C"}
1
+ {"version":3,"file":"Decal.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/Decal.js"],"names":[],"mappings":"AASA;IAIQ;;;OAGG;IACH,WAFU,MAAM,EAAE,GAAC,YAAY,GAAC,MAAM,CAED;IACrC,gCAA6C;IAC7C,iBAA6B;IAE7B,oBAAgC;IAKhC;;;OAGG;IACH,2BAA2B;IAE3B;;;;OAIG;IACH,eAFU,MAAM,CAEM;IAEtB,0EAA6C;IAGjD;;;OAGG;IACH,gBAFW,MAAM,EAAE,GAAC,YAAY,GAAC,UAAU,MAAM,CAAC,QAOjD;IAED,6CAGC;IAED,yCAGC;IAED,4BAUC;IAED,+CAGC;IAED,gDAGC;IAED;;;;OAIG;IACH,gDACC;IAED,iDACC;IAED,6BAKC;IAED,2BAIC;IAED,gDAcC;IAGL;;;OAGG;IACH,kBAFU,OAAO,CAEM;IAEvB,2BAAkC;CARjC;8BA5H6B,oBAAoB;mBAK/B,6CAA6C"}
@@ -83,6 +83,17 @@ export class Decal extends AbstractLight {
83
83
  this.__signal_transfom_changed.remove(f, thisArg);
84
84
  }
85
85
 
86
+ /**
87
+ * Appearance data (color, uv) is stored in raw arrays without change signals; uv only shifts when the
88
+ * texture atlas re-packs, which happens alongside decal addition/removal where the encoded data is
89
+ * invalidated anyway
90
+ */
91
+ onAppearanceChanged(f, thisArg) {
92
+ }
93
+
94
+ offAppearanceChanged(f, thisArg) {
95
+ }
96
+
86
97
  getCenter(result) {
87
98
  // extract translation portion of the matrix
88
99
  result[0] = this.transform[12];
@@ -23,6 +23,8 @@ export class PointLight extends AbstractLight {
23
23
  getAABB(result: any): void;
24
24
  onDimensionChanged(listener: any, context: any): void;
25
25
  offDimensionChanged(listener: any, context: any): void;
26
+ onAppearanceChanged(listener: any, context: any): void;
27
+ offAppearanceChanged(listener: any, context: any): void;
26
28
  /**
27
29
  * @readonly
28
30
  * @type {boolean}
@@ -1 +1 @@
1
- {"version":3,"file":"PointLight.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/PointLight.js"],"names":[],"mappings":"AAKA;IAEI;;;OAGG;IACH,mBAFU,OAAO,CAEQ;IAEzB;;;OAGG;IACH,iBAFU,OAAO,CAEO;IAExB;;;OAGG;IACH,gBAFU,KAAK,CAEY;IAE3B;;;OAGG;IACH,oBAFU,OAAO,CAEU;IAG3B,6BAEC;IAED,2BAeC;IAED,sDAGC;IAED,uDAGC;IA6BL;;;OAGG;IACH,uBAFU,OAAO,CAEgB;IAEjC,2BAAuC;CARtC;8BArF6B,oBAAoB;oBAD9B,qCAAqC;oBADrC,qCAAqC;sBADnC,oCAAoC"}
1
+ {"version":3,"file":"PointLight.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/PointLight.js"],"names":[],"mappings":"AAKA;IAEI;;;OAGG;IACH,mBAFU,OAAO,CAEQ;IAEzB;;;OAGG;IACH,iBAFU,OAAO,CAEO;IAExB;;;OAGG;IACH,gBAFU,KAAK,CAEY;IAE3B;;;OAGG;IACH,oBAFU,OAAO,CAEU;IAG3B,6BAEC;IAED,2BAeC;IAED,sDAGC;IAED,uDAGC;IAED,uDAGC;IAED,wDAGC;IA6BL;;;OAGG;IACH,uBAFU,OAAO,CAEgB;IAEjC,2BAAuC;CARtC;8BA/F6B,oBAAoB;oBAD9B,qCAAqC;oBADrC,qCAAqC;sBADnC,oCAAoC"}
@@ -61,6 +61,16 @@ export class PointLight extends AbstractLight {
61
61
  this.radius.onChanged.remove(listener, context);
62
62
  }
63
63
 
64
+ onAppearanceChanged(listener, context) {
65
+ this.color.onChanged.add(listener, context);
66
+ this.intensity.onChanged.add(listener, context);
67
+ }
68
+
69
+ offAppearanceChanged(listener, context) {
70
+ this.color.onChanged.remove(listener, context);
71
+ this.intensity.onChanged.remove(listener, context);
72
+ }
73
+
64
74
  /**
65
75
  *
66
76
  * @param {number[]|Float32Array|Float64Array} destination
@@ -38,6 +38,8 @@ export class SpotLight extends AbstractLight {
38
38
  getAABB(result: any): void;
39
39
  onDimensionChanged(listener: any, context: any): void;
40
40
  offDimensionChanged(listener: any, context: any): void;
41
+ onAppearanceChanged(listener: any, context: any): void;
42
+ offAppearanceChanged(listener: any, context: any): void;
41
43
  /**
42
44
  * @readonly
43
45
  * @type {boolean}
@@ -1 +1 @@
1
- {"version":3,"file":"SpotLight.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/SpotLight.js"],"names":[],"mappings":"AAMA;IAGI;;;OAGG;IACH,mBAFU,OAAO,CAEQ;IAEzB;;;OAGG;IACH,oBAFU,OAAO,CAES;IAE1B;;;OAGG;IACH,gBAFU,OAAO,CAEgB;IAEjC;;;OAGG;IACH,mBAFU,OAAO,CAES;IAE1B;;;OAGG;IACH,gBAFU,KAAK,CAEY;IAE3B;;;OAGG;IACH,oBAFU,OAAO,CAEU;IAE3B;;;OAGG;IACH,mBAFU,OAAO,CAEW;IAG5B,6BAYC;IAED,2BAcC;IAED,sDAKC;IAED,uDAKC;IAuCL;;;OAGG;IACH,sBAFU,OAAO,CAEc;CAN9B;8BA/H6B,oBAAoB;oBAD9B,qCAAqC;oBADrC,qCAAqC;sBAFnC,oCAAoC"}
1
+ {"version":3,"file":"SpotLight.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/render/forward_plus/model/SpotLight.js"],"names":[],"mappings":"AAMA;IAGI;;;OAGG;IACH,mBAFU,OAAO,CAEQ;IAEzB;;;OAGG;IACH,oBAFU,OAAO,CAES;IAE1B;;;OAGG;IACH,gBAFU,OAAO,CAEgB;IAEjC;;;OAGG;IACH,mBAFU,OAAO,CAES;IAE1B;;;OAGG;IACH,gBAFU,KAAK,CAEY;IAE3B;;;OAGG;IACH,oBAFU,OAAO,CAEU;IAE3B;;;OAGG;IACH,mBAFU,OAAO,CAEW;IAG5B,6BAYC;IAED,2BAcC;IAED,sDAKC;IAED,uDAKC;IAED,uDAIC;IAED,wDAIC;IAuCL;;;OAGG;IACH,sBAFU,OAAO,CAEc;CAN9B;8BA3I6B,oBAAoB;oBAD9B,qCAAqC;oBADrC,qCAAqC;sBAFnC,oCAAoC"}
@@ -94,6 +94,18 @@ export class SpotLight extends AbstractLight {
94
94
  this.direction.onChanged.remove(listener, context);
95
95
  }
96
96
 
97
+ onAppearanceChanged(listener, context) {
98
+ this.color.onChanged.add(listener, context);
99
+ this.intensity.onChanged.add(listener, context);
100
+ this.penumbra.onChanged.add(listener, context);
101
+ }
102
+
103
+ offAppearanceChanged(listener, context) {
104
+ this.color.onChanged.remove(listener, context);
105
+ this.intensity.onChanged.remove(listener, context);
106
+ this.penumbra.onChanged.remove(listener, context);
107
+ }
108
+
97
109
  /**
98
110
  *
99
111
  * @param {number[]|Float32Array|Float64Array} destination
@@ -1,5 +1,12 @@
1
1
  export class TubeMaterialX extends ShaderMaterial {
2
- constructor(params: any);
2
+ /**
3
+ * @param {Object} params three.js material parameters, plus:
4
+ * @param {boolean} [params.lit=false] compile the lit variant — standard light
5
+ * chain, receive-only shadows — instead of the unlit one
6
+ */
7
+ constructor({ lit, ...params }?: {
8
+ lit?: boolean;
9
+ });
3
10
  }
4
11
  import { ShaderMaterial } from "three";
5
12
  //# sourceMappingURL=TubeMaterialX.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TubeMaterialX.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/trail/tube/TubeMaterialX.js"],"names":[],"mappings":"AAsDA;IACI,yBAuBC;CACJ;+BA/E0D,OAAO"}
1
+ {"version":3,"file":"TubeMaterialX.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/trail/tube/TubeMaterialX.js"],"names":[],"mappings":"AAuIA;IACI;;;;OAIG;IACH;QAH4B,GAAG,GAApB,OAAO;OAoCjB;CACJ;+BA/KsF,OAAO"}
@@ -1,12 +1,25 @@
1
- import { DoubleSide, NormalBlending, ShaderMaterial } from "three";
1
+ import { DoubleSide, NormalBlending, ShaderMaterial, UniformsLib, UniformsUtils } from "three";
2
2
 
3
3
  const TWO_PI = "6.283185307179586";
4
4
 
5
5
  /**
6
- * Unlit tube material. Vertices arrive as ring CENTRES; the actual surface vertex
6
+ * Tube material. Vertices arrive as ring CENTRES; the actual surface vertex
7
7
  * is extruded here in WORLD space from the per-knot tangent frame (tn, tb) and the
8
8
  * vertex's angular position `tubeUv.x`. The mesh transform is identity (centres are
9
9
  * already world space, written CPU-side), so modelViewMatrix == viewMatrix.
10
+ *
11
+ * Unlit by default. The LIT define switches the fragment stage onto the standard
12
+ * three.js light chain instead — specular is forced to zero, so the tube shades as
13
+ * a matte (Lambert) surface under ambient / hemisphere / light-probe / directional /
14
+ * point / spot lights, and receives shadows (it never casts them). The surface
15
+ * normal is the ring's radial direction; the per-knot thickness taper's tilt is
16
+ * ignored.
17
+ *
18
+ * The lit variant deliberately follows the three.js chunk conventions —
19
+ * `void main() {`, `transformed`, `#include <worldpos_vertex>`,
20
+ * `#include <lights_pars_begin>`, `#include <lights_fragment_begin>` — because the
21
+ * Forward+ {@link MaterialTransformer} keys its textual injections on exactly these
22
+ * markers to add clustered point-light accumulation and decals.
10
23
  */
11
24
  const vertexShader = `
12
25
  varying vec2 vUv;
@@ -19,18 +32,44 @@ const vertexShader = `
19
32
  attribute float thickness;
20
33
  attribute vec3 color;
21
34
 
22
- void main(){
35
+ #ifdef LIT
36
+
37
+ varying vec3 vNormal;
38
+ varying vec3 vViewPosition;
39
+
40
+ #include <common>
41
+ #include <shadowmap_pars_vertex>
42
+
43
+ #endif
44
+
45
+ void main() {
23
46
  float angle = tubeUv.x * ${TWO_PI};
24
47
 
25
48
  // radial direction in the knot's frame; unit, perpendicular to the tangent
26
49
  vec3 radial = cos(angle) * tn + sin(angle) * tb;
27
50
 
28
- vec3 surface = position + radial * (thickness * 0.5);
51
+ // extruded surface vertex, named per three.js chunk convention
52
+ vec3 transformed = position + radial * (thickness * 0.5);
29
53
 
30
54
  vUv = tubeUv;
31
55
  vColor = vec4(color / 255.0, alpha);
32
56
 
33
- gl_Position = projectionMatrix * modelViewMatrix * vec4(surface, 1.0);
57
+ vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0);
58
+
59
+ #ifdef LIT
60
+
61
+ // the tube's surface normal is the radial direction itself
62
+ vec3 transformedNormal = normalMatrix * radial;
63
+
64
+ vNormal = transformedNormal;
65
+ vViewPosition = -mvPosition.xyz;
66
+
67
+ #include <worldpos_vertex>
68
+ #include <shadowmap_vertex>
69
+
70
+ #endif
71
+
72
+ gl_Position = projectionMatrix * mvPosition;
34
73
  }
35
74
  `;
36
75
 
@@ -40,7 +79,21 @@ const fragmentShader = `
40
79
 
41
80
  uniform sampler2D uDiffuse;
42
81
 
43
- void main(){
82
+ #ifdef LIT
83
+
84
+ // vViewPosition is declared by <lights_phong_pars_fragment>
85
+ varying vec3 vNormal;
86
+
87
+ #include <common>
88
+ #include <packing>
89
+ #include <bsdfs>
90
+ #include <lights_pars_begin>
91
+ #include <lights_phong_pars_fragment>
92
+ #include <shadowmap_pars_fragment>
93
+
94
+ #endif
95
+
96
+ void main() {
44
97
  vec4 diffuseColor = vColor;
45
98
 
46
99
  #ifdef USE_TEXTURE
@@ -48,24 +101,67 @@ const fragmentShader = `
48
101
  diffuseColor *= texel;
49
102
  #endif
50
103
 
51
- gl_FragColor = diffuseColor;
104
+ #ifdef LIT
105
+
106
+ ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
107
+
108
+ #include <normal_fragment_begin>
109
+
110
+ // Blinn-Phong with specular forced to zero = pure Lambert; a trail is a
111
+ // matte surface, highlights on a fading tube read as artifacts
112
+ BlinnPhongMaterial material;
113
+ material.diffuseColor = diffuseColor.rgb;
114
+ material.specularColor = vec3(0.0);
115
+ material.specularShininess = 1.0;
116
+ material.specularStrength = 0.0;
117
+
118
+ #include <lights_fragment_begin>
119
+ #include <lights_fragment_end>
120
+
121
+ vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;
122
+
123
+ gl_FragColor = vec4(outgoingLight, diffuseColor.a);
124
+
125
+ #include <tonemapping_fragment>
126
+ #include <encodings_fragment>
127
+
128
+ #else
129
+
130
+ gl_FragColor = diffuseColor;
131
+
132
+ #endif
52
133
  }
53
134
  `;
54
135
 
55
136
  export class TubeMaterialX extends ShaderMaterial {
56
- constructor(params) {
137
+ /**
138
+ * @param {Object} params three.js material parameters, plus:
139
+ * @param {boolean} [params.lit=false] compile the lit variant — standard light
140
+ * chain, receive-only shadows — instead of the unlit one
141
+ */
142
+ constructor({ lit = false, ...params } = {}) {
57
143
  super({
58
144
  fragmentShader,
59
145
  vertexShader,
60
- uniforms: {
61
- uDiffuse: {
62
- type: 't',
63
- value: null
64
- }
65
- },
146
+ uniforms: lit
147
+ ? UniformsUtils.merge([
148
+ UniformsLib.lights,
149
+ { uDiffuse: { value: null } }
150
+ ])
151
+ : {
152
+ uDiffuse: {
153
+ type: 't',
154
+ value: null
155
+ }
156
+ },
66
157
  defines: {
67
- USE_TEXTURE: false
158
+ USE_TEXTURE: false,
159
+ // a `false` define is omitted from the compiled shader entirely,
160
+ // not emitted as "#define LIT false"
161
+ LIT: lit
68
162
  },
163
+ // wires the renderer's light state into the merged uniforms
164
+ lights: lit,
69
165
  blending: NormalBlending,
70
166
  transparent: true,
71
167
  // The primary use is transparent trails (depthWrite: false). Callers that
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Material configuration for a {@link TubeX}. Used as a cache key in
3
3
  * {@link TubeXPlugin}, so any field that changes the compiled material (texture,
4
- * depth-write mode) participates in {@link equals}/{@link hash}.
4
+ * depth-write mode, lighting) participates in {@link equals}/{@link hash}.
5
5
  */
6
6
  export class TubeXMaterialSpec {
7
7
  /**
@@ -16,11 +16,18 @@ export class TubeXMaterialSpec {
16
16
  * @type {boolean}
17
17
  */
18
18
  depthWrite: boolean;
19
+ /**
20
+ * When true the tube shades as a matte surface under the standard scene
21
+ * lights and receives shadows; when false (default) it renders unlit.
22
+ * @type {boolean}
23
+ */
24
+ lit: boolean;
19
25
  equals(other: any): boolean;
20
26
  hash(): number;
21
- fromJSON({ diffuse, depthWrite }: {
27
+ fromJSON({ diffuse, depthWrite, lit }: {
22
28
  diffuse: any;
23
29
  depthWrite?: boolean;
30
+ lit?: boolean;
24
31
  }): void;
25
32
  }
26
33
  //# sourceMappingURL=TubeXMaterialSpec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TubeXMaterialSpec.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/trail/tube/TubeXMaterialSpec.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IAEI;;;OAGG;IACH,SAFU,SAAO,IAAI,CAEN;IAEf;;;;;OAKG;IACH,YAFU,OAAO,CAEE;IAEnB,4BAGC;IAED,eAEC;IAED;;;aAYC;CACJ"}
1
+ {"version":3,"file":"TubeXMaterialSpec.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/trail/tube/TubeXMaterialSpec.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IAEI;;;OAGG;IACH,SAFU,SAAO,IAAI,CAEN;IAEf;;;;;OAKG;IACH,YAFU,OAAO,CAEE;IAEnB;;;;OAIG;IACH,KAFU,OAAO,CAEL;IAEZ,4BAIC;IAED,eAEC;IAED;;;;aAeC;CACJ"}
@@ -4,7 +4,7 @@ import { computeStringHash } from "../../../../core/primitives/strings/computeSt
4
4
  /**
5
5
  * Material configuration for a {@link TubeX}. Used as a cache key in
6
6
  * {@link TubeXPlugin}, so any field that changes the compiled material (texture,
7
- * depth-write mode) participates in {@link equals}/{@link hash}.
7
+ * depth-write mode, lighting) participates in {@link equals}/{@link hash}.
8
8
  */
9
9
  export class TubeXMaterialSpec {
10
10
 
@@ -22,16 +22,24 @@ export class TubeXMaterialSpec {
22
22
  */
23
23
  depthWrite = false;
24
24
 
25
+ /**
26
+ * When true the tube shades as a matte surface under the standard scene
27
+ * lights and receives shadows; when false (default) it renders unlit.
28
+ * @type {boolean}
29
+ */
30
+ lit = false;
31
+
25
32
  equals(other) {
26
33
  return this.diffuse === other.diffuse
27
- && this.depthWrite === other.depthWrite;
34
+ && this.depthWrite === other.depthWrite
35
+ && this.lit === other.lit;
28
36
  }
29
37
 
30
38
  hash() {
31
- return (computeStringHash(this.diffuse) * 2) + (this.depthWrite ? 1 : 0);
39
+ return (computeStringHash(this.diffuse) * 4) + (this.lit ? 2 : 0) + (this.depthWrite ? 1 : 0);
32
40
  }
33
41
 
34
- fromJSON({ diffuse, depthWrite = false }) {
42
+ fromJSON({ diffuse, depthWrite = false, lit = false }) {
35
43
 
36
44
  if (diffuse !== undefined && diffuse !== null) {
37
45
  assert.isString(diffuse, "diffuse");
@@ -43,5 +51,8 @@ export class TubeXMaterialSpec {
43
51
 
44
52
  assert.isBoolean(depthWrite, "depthWrite");
45
53
  this.depthWrite = depthWrite;
54
+
55
+ assert.isBoolean(lit, "lit");
56
+ this.lit = lit;
46
57
  }
47
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TubeXPlugin.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/trail/tube/TubeXPlugin.js"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH;IAOI,8BAWC;IARG,WAAyB;IAU7B;;;OAGG;IACH,4CAFa,aAAa,CAIzB;;CAsCJ;6BA3E4B,iCAAiC;8BAChC,oBAAoB"}
1
+ {"version":3,"file":"TubeXPlugin.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/trail/tube/TubeXPlugin.js"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH;IAOI,8BAWC;IARG,WAAyB;IAU7B;;;OAGG;IACH,4CAFa,aAAa,CAIzB;;CA4CJ;6BAjF4B,iCAAiC;8BAChC,oBAAoB"}
@@ -48,6 +48,7 @@ export class TubeXPlugin extends EnginePlugin {
48
48
  #build_material(spec) {
49
49
 
50
50
  const material = new TubeMaterialX({
51
+ lit: spec.lit,
51
52
  transparent: true,
52
53
  depthWrite: spec.depthWrite,
53
54
  wireframe: false
@@ -75,6 +76,11 @@ export class TubeXPlugin extends EnginePlugin {
75
76
  material.needsUpdate = true;
76
77
  }
77
78
 
78
- return material;
79
+ // Hand the material to the material manager so renderer-wide compile steps
80
+ // apply — under Forward+ the lit variant is rewritten to also receive
81
+ // clustered point lights and decals. With no steps registered this is a
82
+ // pass-through. The async texture callback above still lands: the compiled
83
+ // material shares the source's uniform objects.
84
+ return this.engine.graphics.getMaterialManager().obtain(material).getValue();
79
85
  }
80
86
  }