@3driseai/3drise-core 1.0.7 → 1.0.9

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.
@@ -12,6 +12,10 @@ export interface SkyTimeState {
12
12
  delta: number;
13
13
  elapsedTime: number;
14
14
  autoAnimate: boolean;
15
+ /** Time is moving on its own — autoAnimate OR real-time sync. Consumers that
16
+ * schedule expensive work off a sun change (a cube-camera capture) use this
17
+ * to stand down and let the environment probe's own timer do it once. */
18
+ advancing: boolean;
15
19
  }
16
20
  export declare class SkySystemManager {
17
21
  private static instance;
@@ -30,12 +34,35 @@ export declare class SkySystemManager {
30
34
  private syncWithRealTime;
31
35
  private lastSyncedMinute;
32
36
  private onTimeSync;
37
+ /** The last timeOfDay this manager and its host agree on. A host pushing the
38
+ * same value back (its own echo of `onTimeSync`) must NOT rewind the clock;
39
+ * only a genuinely different value is an intentional seek. */
40
+ private lastAppliedTimeOfDay;
41
+ /** Real seconds since the last write-back, so playback dispatches a few times
42
+ * a second instead of once per frame. */
43
+ private syncAccumulator;
44
+ /** Whoever is allowed to advance the clock. The manager is a singleton, so a
45
+ * second mounted SkySystem (a market preview, a second canvas) would
46
+ * otherwise double-tick it and stomp its settings. */
47
+ private driver;
33
48
  constructor();
34
49
  static getInstance(): SkySystemManager;
35
50
  static resetInstance(): void;
51
+ /** Claim the right to drive the clock. Returns true if `token` owns it —
52
+ * the first claimant wins and keeps it until it releases. Everyone else
53
+ * reads. */
54
+ claimDriver(token: object): boolean;
55
+ releaseDriver(token: object): void;
56
+ hasDriver(): boolean;
36
57
  setTimeSettings(settings: TimeSettings): void;
37
58
  setOnTimeSync(callback: ((timeOfDay: number) => void) | null): void;
38
59
  setTime(hours: number, minutes?: number): void;
60
+ /**
61
+ * One frame of clock. Safe to call every frame whenever time is enabled —
62
+ * it advances `timeInHours` only in an advancing mode, but `elapsedTime` and
63
+ * `delta` are what every consumer's smoothing and twinkle read, so they must
64
+ * keep running in manual mode too.
65
+ */
39
66
  tick(delta: number): void;
40
67
  getState(): SkyTimeState;
41
68
  getStateByTime(timeOfDay: number): SkyTimeState;
@@ -45,10 +72,47 @@ export declare class SkySystemManager {
45
72
  getTimeInHours(): number;
46
73
  isEnabled(): boolean;
47
74
  isAutoAnimating(): boolean;
75
+ isAdvancing(): boolean;
48
76
  private advanceTime;
77
+ /**
78
+ * Push the animating time back to the host a few times a second.
79
+ *
80
+ * Without this the manager was the only thing that knew what time it was:
81
+ * the slider sat frozen at the last value the user set, and that stale value
82
+ * was what got saved with the project. Throttled in REAL seconds, not
83
+ * simulated ones, so the dispatch rate does not scale with timescale.
84
+ *
85
+ * `lastAppliedTimeOfDay` is set to exactly what is published, so the value
86
+ * coming back through the host's state is recognised as our own echo by
87
+ * `setTimeSettings` and does not rewind the clock.
88
+ */
89
+ private publishTime;
49
90
  private syncToRealTime;
50
91
  private recalculateState;
92
+ /**
93
+ * Sun elevation and azimuth for a given hour.
94
+ *
95
+ * Both curves are continuous across the whole 24 h, which they were not:
96
+ *
97
+ * - Azimuth used to be `180 + 180 * normalizedTime` with `normalizedTime`
98
+ * restarting at 0 at both SUNRISE and SUNSET, so the sun teleported to
99
+ * the opposite horizon twice a cycle. The night arc now CONTINUES the
100
+ * day arc (360 -> 540, which is 180 again mod 360), so the sun keeps
101
+ * travelling the same way round and meets the next dawn exactly where
102
+ * the day arc starts.
103
+ *
104
+ * - Night elevation used to be the day curve halved, and the day curve is
105
+ * positive in the middle — so the sun rose again after sunset and stood
106
+ * +18 degrees up at 02:00. Anything reading the direction rather than
107
+ * `isDaytime` (the clouds and the ocean, through useCloudSun) was lit as
108
+ * if it were afternoon in the middle of the night. The night arc is now
109
+ * its own curve, below the horizon for all of it, meeting the day curve
110
+ * at -5 degrees at both ends.
111
+ */
51
112
  private calculateTimeState;
113
+ /** Scene azimuth in [-180, 180). Normalised into range rather than left to
114
+ * JS's signed `%`, so the night arc's 360-540 degrees maps onto the same
115
+ * directions the day arc's 180-360 does instead of running off the end. */
52
116
  private transformAzimuth;
53
117
  private updateSunDirection;
54
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SkySystemManager.d.ts","sourceRoot":"","sources":["../../src/services/SkySystemManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE3E,MAAM,WAAW,YAAY;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACxB;AAMD,qBAAa,gBAAgB;IACzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAQ;IAC/B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,UAAU,CAAQ;;IAI1B,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAMtC,MAAM,CAAC,aAAa,IAAI,IAAI;IAG5B,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAa7C,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI;IAGnE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAU,GAAG,IAAI;IAIjD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAezB,QAAQ,IAAI,YAAY;IAcxB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY;IAmB/C,OAAO,IAAI,IAAI;IAKf,YAAY,IAAI,cAAc;IAG9B,eAAe,IAAI,KAAK,CAAC,OAAO;IAGhC,cAAc,IAAI,MAAM;IAGxB,SAAS,IAAI,OAAO;IAGpB,eAAe,IAAI,OAAO;IAG1B,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,gBAAgB;IAGxB,OAAO,CAAC,kBAAkB;CAQ7B;AACD,eAAO,MAAM,gBAAgB,EAAE,gBAAiD,CAAC;AACjF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"SkySystemManager.d.ts","sourceRoot":"","sources":["../../src/services/SkySystemManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE3E,MAAM,WAAW,YAAY;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB;;8EAE0E;IAC1E,SAAS,EAAE,OAAO,CAAC;CACtB;AAaD,qBAAa,gBAAgB;IACzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAQ;IAC/B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,UAAU,CAAQ;IAC1B;;mEAE+D;IAC/D,OAAO,CAAC,oBAAoB,CAAuB;IACnD;8CAC0C;IAC1C,OAAO,CAAC,eAAe,CAAK;IAC5B;;2DAEuD;IACvD,OAAO,CAAC,MAAM,CAAuB;;IAIrC,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAMtC,MAAM,CAAC,aAAa,IAAI,IAAI;IAG5B;;iBAEa;IACb,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAMnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKlC,SAAS,IAAI,OAAO;IAGpB,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IA6B7C,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI;IAGnE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAU,GAAG,IAAI;IAQjD;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAczB,QAAQ,IAAI,YAAY;IAexB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY;IAoB/C,OAAO,IAAI,IAAI;IAKf,YAAY,IAAI,cAAc;IAG9B,eAAe,IAAI,KAAK,CAAC,OAAO;IAGhC,cAAc,IAAI,MAAM;IAGxB,SAAS,IAAI,OAAO;IAGpB,eAAe,IAAI,OAAO;IAG1B,WAAW,IAAI,OAAO;IAGtB,OAAO,CAAC,WAAW;IAWnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,gBAAgB;IASxB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;gFAE4E;IAC5E,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,kBAAkB;CAQ7B;AACD,eAAO,MAAM,gBAAgB,EAAE,gBAAiD,CAAC;AACjF,eAAe,gBAAgB,CAAC"}
@@ -2,6 +2,13 @@ import * as THREE from 'three';
2
2
  const SUNRISE = 6;
3
3
  const SUNSET = 21;
4
4
  const MAX_ELEVATION = 42;
5
+ /** Where the day arc meets the horizon at SUNRISE and SUNSET, and where the
6
+ * night arc has to meet it back for the curve to be continuous. */
7
+ const HORIZON_OFFSET = 5;
8
+ /** How far below the horizon the sun gets at the middle of the night. */
9
+ const MAX_NIGHT_DEPTH = 21;
10
+ /** Real seconds between playback write-backs to the host. */
11
+ const SYNC_INTERVAL_SECONDS = 0.25;
5
12
  export class SkySystemManager {
6
13
  static instance = null;
7
14
  timeInHours = 12;
@@ -19,6 +26,17 @@ export class SkySystemManager {
19
26
  syncWithRealTime = false;
20
27
  lastSyncedMinute = -1;
21
28
  onTimeSync = null;
29
+ /** The last timeOfDay this manager and its host agree on. A host pushing the
30
+ * same value back (its own echo of `onTimeSync`) must NOT rewind the clock;
31
+ * only a genuinely different value is an intentional seek. */
32
+ lastAppliedTimeOfDay = null;
33
+ /** Real seconds since the last write-back, so playback dispatches a few times
34
+ * a second instead of once per frame. */
35
+ syncAccumulator = 0;
36
+ /** Whoever is allowed to advance the clock. The manager is a singleton, so a
37
+ * second mounted SkySystem (a market preview, a second canvas) would
38
+ * otherwise double-tick it and stomp its settings. */
39
+ driver = null;
22
40
  constructor() {
23
41
  this.updateSunDirection();
24
42
  }
@@ -31,16 +49,49 @@ export class SkySystemManager {
31
49
  static resetInstance() {
32
50
  SkySystemManager.instance = null;
33
51
  }
52
+ /** Claim the right to drive the clock. Returns true if `token` owns it —
53
+ * the first claimant wins and keeps it until it releases. Everyone else
54
+ * reads. */
55
+ claimDriver(token) {
56
+ if (this.driver === null) {
57
+ this.driver = token;
58
+ }
59
+ return this.driver === token;
60
+ }
61
+ releaseDriver(token) {
62
+ if (this.driver === token) {
63
+ this.driver = null;
64
+ }
65
+ }
66
+ hasDriver() {
67
+ return this.driver !== null;
68
+ }
34
69
  setTimeSettings(settings) {
35
70
  this.enabled = settings.enabled;
36
71
  this.timescale = settings.timescale;
37
72
  this.autoAnimate = settings.autoAnimate;
38
73
  this.syncWithRealTime = settings.syncWithRealTime;
39
74
  if (this.syncWithRealTime) {
75
+ // Wall clock wins outright; the settings' timeOfDay is only ever an
76
+ // echo of what this manager last published. Adopting it here is what
77
+ // used to put one frame of yesterday's sun on screen.
78
+ this.lastAppliedTimeOfDay = settings.timeOfDay;
40
79
  this.syncToRealTime();
80
+ this.recalculateState();
81
+ return;
41
82
  }
42
- if (Math.abs(this.timeInHours - settings.timeOfDay) > 0.01) {
83
+ // A running clock is ahead of whatever the host last stored, so a plain
84
+ // `timeInHours !== settings.timeOfDay` test is true on every settings
85
+ // object and would rewind playback on any unrelated change (a speed
86
+ // button, a project load, a re-render). Only a timeOfDay that differs
87
+ // from the last one we agreed on is a deliberate seek.
88
+ const seeked = this.lastAppliedTimeOfDay === null
89
+ || Math.abs(settings.timeOfDay - this.lastAppliedTimeOfDay) > 0.001;
90
+ if (seeked) {
91
+ this.lastAppliedTimeOfDay = settings.timeOfDay;
43
92
  this.timeInHours = settings.timeOfDay;
93
+ this.delta = 0;
94
+ this.syncAccumulator = 0;
44
95
  this.recalculateState();
45
96
  }
46
97
  }
@@ -49,20 +100,29 @@ export class SkySystemManager {
49
100
  }
50
101
  setTime(hours, minutes = 0) {
51
102
  this.timeInHours = hours + minutes / 60;
103
+ this.lastAppliedTimeOfDay = this.timeInHours;
104
+ // An explicit seek is a cut, not a movement: zero delta tells every
105
+ // consumer's smoothing lerp to snap rather than glide from the old sun.
106
+ this.delta = 0;
52
107
  this.recalculateState();
53
108
  }
109
+ /**
110
+ * One frame of clock. Safe to call every frame whenever time is enabled —
111
+ * it advances `timeInHours` only in an advancing mode, but `elapsedTime` and
112
+ * `delta` are what every consumer's smoothing and twinkle read, so they must
113
+ * keep running in manual mode too.
114
+ */
54
115
  tick(delta) {
55
116
  if (!this.enabled)
56
117
  return;
57
- // console.log(delta)
58
- // this.delta = delta;
118
+ this.delta = delta;
59
119
  this.elapsedTime += delta;
60
120
  if (this.syncWithRealTime) {
61
121
  this.syncToRealTime();
62
122
  }
63
123
  else if (this.autoAnimate && this.timescale > 0) {
64
- // console.log(delta)
65
124
  this.advanceTime(delta);
125
+ this.publishTime(delta);
66
126
  }
67
127
  this.recalculateState();
68
128
  }
@@ -78,6 +138,7 @@ export class SkySystemManager {
78
138
  delta: this.delta,
79
139
  elapsedTime: this.elapsedTime,
80
140
  autoAnimate: this.autoAnimate,
141
+ advancing: this.isAdvancing(),
81
142
  };
82
143
  }
83
144
  getStateByTime(timeOfDay) {
@@ -97,6 +158,7 @@ export class SkySystemManager {
97
158
  delta: 0,
98
159
  elapsedTime: this.elapsedTime,
99
160
  autoAnimate: this.autoAnimate,
161
+ advancing: this.isAdvancing(),
100
162
  };
101
163
  }
102
164
  getTime() {
@@ -119,16 +181,45 @@ export class SkySystemManager {
119
181
  isAutoAnimating() {
120
182
  return this.autoAnimate;
121
183
  }
184
+ isAdvancing() {
185
+ return this.enabled && (this.syncWithRealTime || (this.autoAnimate && this.timescale > 0));
186
+ }
122
187
  advanceTime(delta) {
123
188
  const hoursPerSecond = this.timescale / 60;
124
189
  this.timeInHours += hoursPerSecond * delta;
125
- if (this.timeInHours >= 24) {
126
- this.timeInHours -= 24;
127
- }
128
- else if (this.timeInHours < 0) {
129
- this.timeInHours += 24;
190
+ // Modulo rather than a single +/- 24: at a high timescale one long frame
191
+ // (a backgrounded tab handing back a multi-second delta) can carry the
192
+ // clock past 24 by more than a day, and a single subtraction leaves it
193
+ // out of range for every consumer that indexes by hour.
194
+ if (this.timeInHours >= 24 || this.timeInHours < 0) {
195
+ this.timeInHours = ((this.timeInHours % 24) + 24) % 24;
130
196
  }
131
197
  }
198
+ /**
199
+ * Push the animating time back to the host a few times a second.
200
+ *
201
+ * Without this the manager was the only thing that knew what time it was:
202
+ * the slider sat frozen at the last value the user set, and that stale value
203
+ * was what got saved with the project. Throttled in REAL seconds, not
204
+ * simulated ones, so the dispatch rate does not scale with timescale.
205
+ *
206
+ * `lastAppliedTimeOfDay` is set to exactly what is published, so the value
207
+ * coming back through the host's state is recognised as our own echo by
208
+ * `setTimeSettings` and does not rewind the clock.
209
+ */
210
+ publishTime(delta) {
211
+ if (!this.onTimeSync)
212
+ return;
213
+ this.syncAccumulator += delta;
214
+ if (this.syncAccumulator < SYNC_INTERVAL_SECONDS)
215
+ return;
216
+ this.syncAccumulator = 0;
217
+ const rounded = Math.round(this.timeInHours * 100) / 100;
218
+ if (rounded === this.lastAppliedTimeOfDay)
219
+ return;
220
+ this.lastAppliedTimeOfDay = rounded;
221
+ this.onTimeSync(rounded);
222
+ }
132
223
  syncToRealTime() {
133
224
  const now = new Date();
134
225
  const currentMinute = now.getHours() * 60 + now.getMinutes();
@@ -136,7 +227,9 @@ export class SkySystemManager {
136
227
  now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600;
137
228
  if (currentMinute !== this.lastSyncedMinute) {
138
229
  this.lastSyncedMinute = currentMinute;
139
- this.onTimeSync?.(Math.round(this.timeInHours * 100) / 100);
230
+ const rounded = Math.round(this.timeInHours * 100) / 100;
231
+ this.lastAppliedTimeOfDay = rounded;
232
+ this.onTimeSync?.(rounded);
140
233
  }
141
234
  }
142
235
  recalculateState() {
@@ -148,20 +241,41 @@ export class SkySystemManager {
148
241
  this.sunAzimuth = state.sunAzimuth;
149
242
  this.updateSunDirection();
150
243
  }
244
+ /**
245
+ * Sun elevation and azimuth for a given hour.
246
+ *
247
+ * Both curves are continuous across the whole 24 h, which they were not:
248
+ *
249
+ * - Azimuth used to be `180 + 180 * normalizedTime` with `normalizedTime`
250
+ * restarting at 0 at both SUNRISE and SUNSET, so the sun teleported to
251
+ * the opposite horizon twice a cycle. The night arc now CONTINUES the
252
+ * day arc (360 -> 540, which is 180 again mod 360), so the sun keeps
253
+ * travelling the same way round and meets the next dawn exactly where
254
+ * the day arc starts.
255
+ *
256
+ * - Night elevation used to be the day curve halved, and the day curve is
257
+ * positive in the middle — so the sun rose again after sunset and stood
258
+ * +18 degrees up at 02:00. Anything reading the direction rather than
259
+ * `isDaytime` (the clouds and the ocean, through useCloudSun) was lit as
260
+ * if it were afternoon in the middle of the night. The night arc is now
261
+ * its own curve, below the horizon for all of it, meeting the day curve
262
+ * at -5 degrees at both ends.
263
+ */
151
264
  calculateTimeState(timeInHours) {
152
265
  const isDaytime = timeInHours >= SUNRISE && timeInHours <= SUNSET;
153
266
  let normalizedTime;
267
+ let sunElevation;
268
+ let sunAzimuth;
154
269
  if (isDaytime) {
155
270
  normalizedTime = (timeInHours - SUNRISE) / (SUNSET - SUNRISE);
271
+ sunElevation = Math.cos(Math.PI * (normalizedTime - 0.5)) * MAX_ELEVATION - HORIZON_OFFSET;
272
+ sunAzimuth = 180 + 180 * normalizedTime;
156
273
  }
157
274
  else {
158
275
  const nightHour = timeInHours >= SUNSET ? timeInHours : timeInHours + 24;
159
276
  normalizedTime = (nightHour - SUNSET) / (24 - SUNSET + SUNRISE);
160
- }
161
- let sunElevation = Math.cos(Math.PI * (normalizedTime - 0.5)) * MAX_ELEVATION - 5;
162
- const sunAzimuth = 180 + 180 * normalizedTime;
163
- if (!isDaytime) {
164
- sunElevation *= 0.5;
277
+ sunElevation = -HORIZON_OFFSET - Math.cos(Math.PI * (normalizedTime - 0.5)) * MAX_NIGHT_DEPTH;
278
+ sunAzimuth = 360 + 180 * normalizedTime;
165
279
  }
166
280
  let timeOfDay = 'Nighttime';
167
281
  if (isDaytime) {
@@ -174,8 +288,12 @@ export class SkySystemManager {
174
288
  }
175
289
  return { normalizedTime, isDaytime, timeOfDay, sunElevation, sunAzimuth };
176
290
  }
291
+ /** Scene azimuth in [-180, 180). Normalised into range rather than left to
292
+ * JS's signed `%`, so the night arc's 360-540 degrees maps onto the same
293
+ * directions the day arc's 180-360 does instead of running off the end. */
177
294
  transformAzimuth(rawAzimuth) {
178
- return ((270 - rawAzimuth) % 360) - 180;
295
+ const wrapped = (((270 - rawAzimuth) % 360) + 360) % 360;
296
+ return wrapped - 180;
179
297
  }
180
298
  updateSunDirection() {
181
299
  const el = THREE.MathUtils.degToRad(this.sunElevation);
@@ -1 +1 @@
1
- {"version":3,"file":"SkySystemV.d.ts","sourceRoot":"","sources":["../../src/three/SkySystemV.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClH,MAAM,WAAW,iBAAiB;IAC9B,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,KAAK,CAAC,iBAAiB,GAAG,KAAK,CAAC,kBAAkB,CAAC;IAC3D,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,qBAAqB,CAAC,EAAE,WAAW,CAAC;IACpC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACxE;AAGD,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAC;IACf,OAAO,CAAC,MAAM,CAAC;IACf,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,iBAAiB,CAAQ;gBACrB,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,iBAAiB;IAwC9D,OAAO,CAAC,QAAQ;IAQhB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,MAAc,GAAG,IAAI;IAkBrD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAa9C,OAAO,CAAC,eAAe,GAAE,OAAc,GAAG,IAAI;CAMjD"}
1
+ {"version":3,"file":"SkySystemV.d.ts","sourceRoot":"","sources":["../../src/three/SkySystemV.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClH,MAAM,WAAW,iBAAiB;IAC9B,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,KAAK,CAAC,iBAAiB,GAAG,KAAK,CAAC,kBAAkB,CAAC;IAC3D,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,qBAAqB,CAAC,EAAE,WAAW,CAAC;IACpC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACxE;AAGD,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAC;IACf,OAAO,CAAC,MAAM,CAAC;IACf,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,iBAAiB,CAAQ;gBACrB,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,iBAAiB;IAwC9D,OAAO,CAAC,QAAQ;IAQhB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,MAAc,GAAG,IAAI;IAoBrD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAa9C,OAAO,CAAC,eAAe,GAAE,OAAc,GAAG,IAAI;CAMjD"}
@@ -67,15 +67,17 @@ export class SkySystemV {
67
67
  const { timeSettings, onTimeOfDayChange } = this.config;
68
68
  if (!timeSettings.enabled)
69
69
  return;
70
- if (timeSettings.autoAnimate) {
71
- skySystemManager.tick(delta);
72
- const current = skySystemManager.getTimeOfDay();
73
- if (this.previousTimeOfDay !== null && this.previousTimeOfDay !== current) {
74
- const state = skySystemManager.getState();
75
- onTimeOfDayChange?.(current, state.elapsedTime);
76
- }
77
- this.previousTimeOfDay = current;
70
+ // Ticked whenever time is enabled, not only while it animates: the
71
+ // manager's `delta` and `elapsedTime` come out of here and drive every
72
+ // consumer's colour smoothing. Advancing the hour is the manager's own
73
+ // decision. Kept in step with the R3F SkySystem deliberately.
74
+ skySystemManager.tick(delta);
75
+ const current = skySystemManager.getTimeOfDay();
76
+ if (this.previousTimeOfDay !== null && this.previousTimeOfDay !== current) {
77
+ const state = skySystemManager.getState();
78
+ onTimeOfDayChange?.(current, state.elapsedTime);
78
79
  }
80
+ this.previousTimeOfDay = current;
79
81
  this.skybox?.update(_elapsed, delta);
80
82
  this.lensflare?.update(_elapsed, delta);
81
83
  this.sunLight?.update(_elapsed, delta);
@@ -90,7 +90,7 @@ export const updateCurrentLayoutSettings = (currentSettings, newSettings) => {
90
90
  ? currentSettings.imageUrls
91
91
  : createNumberOfDefaultImages(galleryState.numImages ?? 20), galleryState);
92
92
  const layoutSettings = getLayoutSettings(currentSettings.layout, galleryState);
93
- return { frames, layoutSettings };
93
+ return { frames, layoutSettings: galleryState };
94
94
  };
95
95
  // =============================================================================
96
96
  // DEFAULT STATE
@@ -3,6 +3,7 @@ import { MaterialInstance } from '../services/MaterialRegistry';
3
3
  export declare const MATERIAL_INSTANCE_KEY = "_3driseMaterialInstance";
4
4
  export declare const EDGES_GROUP_KEY = "__isEdgesGroup";
5
5
  export declare const GEREATIVE_EFFECTS_KEY = "__isGenerativeEffectsGroup";
6
+ export declare const IMAGE_KEY = "__isImage";
6
7
  /**
7
8
  * Set by a renderer that builds and owns its object's material itself — today the
8
9
  * points renderers, while shader effects are active. Two writers on one
@@ -1 +1 @@
1
- {"version":3,"file":"materialApplicationUtils.d.ts","sourceRoot":"","sources":["../../src/utils/materialApplicationUtils.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChD,OAA4B,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrF,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAE/D,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAChD,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAClE;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAC3D;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,CAOxH;AAKD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAI/G;AACD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAQlH;AACD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI,CAsCrF;AACD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1F;AACD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGtD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,GAAG,CAAC,MAAM,CAGtC,CAAC;AACH;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAC3D,OAAO,EAAE,KAAK,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,MAAM,EAAE,KAAK,CAAC;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;CACN,CA6BJ"}
1
+ {"version":3,"file":"materialApplicationUtils.d.ts","sourceRoot":"","sources":["../../src/utils/materialApplicationUtils.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChD,OAA4B,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrF,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAE/D,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAChD,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAClE,eAAO,MAAM,SAAS,cAAc,CAAC;AACrC;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAC3D;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,CAOxH;AAKD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAI/G;AACD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAQlH;AACD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI,CAsCrF;AACD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1F;AACD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGtD;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAElE;AACD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,GAAG,CAAC,MAAM,CAGtC,CAAC;AACH;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;IAC3D,OAAO,EAAE,KAAK,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,MAAM,EAAE,KAAK,CAAC;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;IACH,QAAQ,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;KACd,CAAC,CAAC;CACN,CA6BJ"}
@@ -18,6 +18,7 @@ export const MATERIAL_INSTANCE_KEY = '_3driseMaterialInstance';
18
18
  // Key placed on edges group userData to prevent material bleed-through
19
19
  export const EDGES_GROUP_KEY = '__isEdgesGroup';
20
20
  export const GEREATIVE_EFFECTS_KEY = '__isGenerativeEffectsGroup';
21
+ export const IMAGE_KEY = '__isImage';
21
22
  /**
22
23
  * Set by a renderer that builds and owns its object's material itself — today the
23
24
  * points renderers, while shader effects are active. Two writers on one
@@ -33,7 +34,7 @@ export const SELF_OWNED_MATERIAL_KEY = '__ownsItsMaterial';
33
34
  * apply its own material via useMaterialApplication).
34
35
  */
35
36
  export function traverseSkippingEdgesAndEffects(obj, isRoot, callback) {
36
- if (!isRoot && (obj.userData[EDGES_GROUP_KEY] || obj.userData[GEREATIVE_EFFECTS_KEY] || obj.userData[SELF_OWNED_MATERIAL_KEY]))
37
+ if (!isRoot && (obj.userData[EDGES_GROUP_KEY] || obj.userData[GEREATIVE_EFFECTS_KEY] || obj.userData[SELF_OWNED_MATERIAL_KEY] || obj.userData[IMAGE_KEY]))
37
38
  return;
38
39
  callback(obj);
39
40
  for (const child of obj.children) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3driseai/3drise-core",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",