@needle-tools/engine 5.1.0-beta.2 → 5.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{materialx-DnlAB5wd.min.js → materialx-B5gKeM65.min.js} +1 -1
- package/dist/{materialx-BdEC3ugB.umd.cjs → materialx-CFRcCYWy.umd.cjs} +1 -1
- package/dist/{materialx-BCzyAHFr.js → materialx-DqGmteNO.js} +1 -1
- package/dist/{needle-engine.bundle-BcPtAnC9.umd.cjs → needle-engine.bundle--5M1FCR0.umd.cjs} +153 -141
- package/dist/{needle-engine.bundle-H5HXbUCS.js → needle-engine.bundle-B6JqW8fC.js} +5313 -5260
- package/dist/{needle-engine.bundle-D0UTRn3W.min.js → needle-engine.bundle-D5eqAuZu.min.js} +158 -146
- package/dist/needle-engine.d.ts +19 -8
- package/dist/needle-engine.js +157 -157
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{vendor-DQu6PDTB.min.js → vendor-2--bD1c9.min.js} +4 -4
- package/dist/{vendor-J7NvM88N.js → vendor-BNtcIZkv.js} +819 -816
- package/dist/{vendor-CsClD1Nd.umd.cjs → vendor-vbe9sv1t.umd.cjs} +20 -20
- package/lib/engine/api.d.ts +1 -1
- package/lib/engine/api.js +1 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_init.js +2 -2
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +72 -72
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +3 -3
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -2
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine-components/Component.js +12 -0
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +7 -2
- package/lib/engine-components/ContactShadows.js +65 -45
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/GroundProjection.d.ts +6 -0
- package/lib/engine-components/GroundProjection.js +69 -7
- package/lib/engine-components/GroundProjection.js.map +1 -1
- package/lib/engine-components/NeedleMenu.js +4 -1
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/Skybox.js +9 -0
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/package.json +2 -2
- package/plugins/common/license.js +4 -4
- package/plugins/vite/license.js +4 -4
- package/src/engine/api.ts +1 -1
- package/src/engine/engine_init.ts +2 -2
- package/src/engine/engine_license.ts +69 -69
- package/src/engine/engine_networking_blob.ts +3 -3
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +2 -2
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine-components/Component.ts +11 -0
- package/src/engine-components/ContactShadows.ts +75 -49
- package/src/engine-components/GroundProjection.ts +71 -7
- package/src/engine-components/NeedleMenu.ts +4 -1
- package/src/engine-components/Skybox.ts +8 -0
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
|
@@ -83,7 +83,7 @@ type FitParameters = {
|
|
|
83
83
|
*/
|
|
84
84
|
export class ContactShadows extends Behaviour {
|
|
85
85
|
|
|
86
|
-
private static readonly _instances:
|
|
86
|
+
private static readonly _instances: WeakMap<Context, ContactShadows> = new WeakMap();
|
|
87
87
|
/**
|
|
88
88
|
* Create contact shadows for the scene. Automatically fits the shadows to the scene.
|
|
89
89
|
* The instance of contact shadows will be created only once.
|
|
@@ -179,8 +179,9 @@ export class ContactShadows extends Behaviour {
|
|
|
179
179
|
* in which case ContactShadows scale would affect the projection
|
|
180
180
|
**/
|
|
181
181
|
private readonly shadowsRoot: IGameObject = new Object3D() as IGameObject;
|
|
182
|
-
private shadowCamera?: OrthographicCamera;
|
|
183
182
|
private readonly shadowGroup: Group = new Group();
|
|
183
|
+
|
|
184
|
+
private shadowCamera?: OrthographicCamera;
|
|
184
185
|
|
|
185
186
|
private renderTarget?: WebGLRenderTarget;
|
|
186
187
|
private renderTargetBlur?: WebGLRenderTarget;
|
|
@@ -240,19 +241,86 @@ export class ContactShadows extends Behaviour {
|
|
|
240
241
|
if (debug) console.log("Fitted shadows to scene", this.shadowsRoot.scale.clone());
|
|
241
242
|
}
|
|
242
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Call to update the shadows immediately. Only needed if `manualUpdate` is enabled, usually the shadows will update automatically when the scene changes or when the component is enabled.
|
|
246
|
+
*/
|
|
247
|
+
renderShadowsNow() {
|
|
248
|
+
this._renderShadows();
|
|
249
|
+
}
|
|
243
250
|
|
|
244
251
|
/** @internal */
|
|
245
252
|
awake() {
|
|
246
253
|
ContactShadows._instances.set(this.context, this);
|
|
247
|
-
|
|
254
|
+
}
|
|
248
255
|
|
|
256
|
+
/** @internal */
|
|
257
|
+
onEnable(): void {
|
|
258
|
+
this.shadowsRoot.hideFlags = HideFlags.DontExport;
|
|
249
259
|
// ignore self for autofitting
|
|
250
260
|
setAutoFitEnabled(this.shadowsRoot, false);
|
|
261
|
+
|
|
262
|
+
this._needsUpdate = true;
|
|
263
|
+
this.autoCleanup(this.context.events.on("scene-content-changed", () => {
|
|
264
|
+
if (!this.autoFit) return;
|
|
265
|
+
this._needsFit = true;
|
|
266
|
+
this._needsUpdate = true;
|
|
267
|
+
}));
|
|
268
|
+
|
|
269
|
+
if (this.autoFit) this.fitShadows();
|
|
270
|
+
else this.applyMinSize();
|
|
251
271
|
}
|
|
252
272
|
|
|
273
|
+
/** @internal */
|
|
274
|
+
onDestroy(): void {
|
|
275
|
+
if (debug) console.warn("Destroying ContactShadows on " + this.gameObject.name);
|
|
276
|
+
|
|
277
|
+
const instance = ContactShadows._instances.get(this.context);
|
|
278
|
+
if (instance === this) {
|
|
279
|
+
ContactShadows._instances.delete(this.context);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// dispose the render targets
|
|
283
|
+
this.renderTarget?.dispose();
|
|
284
|
+
this.renderTargetBlur?.dispose();
|
|
285
|
+
|
|
286
|
+
// dispose the materials
|
|
287
|
+
this.depthMaterial?.dispose();
|
|
288
|
+
this.horizontalBlurMaterial?.dispose();
|
|
289
|
+
this.verticalBlurMaterial?.dispose();
|
|
290
|
+
|
|
291
|
+
// dispose the geometries
|
|
292
|
+
this.blurPlane?.geometry.dispose();
|
|
293
|
+
this.plane?.geometry.dispose();
|
|
294
|
+
this.occluderMesh?.geometry.dispose();
|
|
295
|
+
}
|
|
253
296
|
|
|
254
297
|
/** @internal */
|
|
255
|
-
|
|
298
|
+
onBeforeRender(_frame: XRFrame | null): void {
|
|
299
|
+
|
|
300
|
+
if (this._needsFit && this.autoFit) {
|
|
301
|
+
this._needsFit = false;
|
|
302
|
+
this.fitShadows();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (this.manualUpdate) {
|
|
306
|
+
if (!this._needsUpdate) return;
|
|
307
|
+
}
|
|
308
|
+
this._needsUpdate = false;
|
|
309
|
+
this._renderShadows();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private _ensureSetup() {
|
|
313
|
+
|
|
314
|
+
// Make sure the shadows root is parented to the game object. This is needed for the shadows to be in the correct position and scale relative to the game object. We need to do this before checking if the materials are set up because some properties (e.g. darkness) can be changed in the editor before the component is enabled, so we want to avoid re-creating everything in that case.
|
|
315
|
+
if (this.shadowsRoot.parent !== this.gameObject) {
|
|
316
|
+
this.gameObject.add(this.shadowsRoot);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check if everything is already set up. We need to do this because some properties (e.g. darkness) can be changed in the editor before the component is enabled, so we want to avoid re-creating everything in that case.
|
|
320
|
+
if (this.depthMaterial && this.horizontalBlurMaterial && this.verticalBlurMaterial && this.planeMaterial) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
256
324
|
if (debug) console.log("Create ContactShadows on " + this.gameObject.name, this)
|
|
257
325
|
|
|
258
326
|
this.gameObject.add(this.shadowsRoot);
|
|
@@ -363,54 +431,11 @@ export class ContactShadows extends Behaviour {
|
|
|
363
431
|
this.verticalBlurMaterial.depthTest = false;
|
|
364
432
|
|
|
365
433
|
this.shadowGroup.visible = false;
|
|
366
|
-
|
|
367
|
-
if (this.autoFit) this.fitShadows();
|
|
368
|
-
else this.applyMinSize();
|
|
369
434
|
}
|
|
370
435
|
|
|
371
|
-
|
|
372
|
-
this._needsUpdate = true;
|
|
373
|
-
this.autoCleanup(this.context.events.on("scene-content-changed", () => {
|
|
374
|
-
if (!this.autoFit) return;
|
|
375
|
-
this._needsFit = true;
|
|
376
|
-
this._needsUpdate = true;
|
|
377
|
-
}));
|
|
378
|
-
}
|
|
436
|
+
private _renderShadows() {
|
|
379
437
|
|
|
380
|
-
|
|
381
|
-
onDestroy(): void {
|
|
382
|
-
const instance = ContactShadows._instances.get(this.context);
|
|
383
|
-
if (instance === this) {
|
|
384
|
-
ContactShadows._instances.delete(this.context);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// dispose the render targets
|
|
388
|
-
this.renderTarget?.dispose();
|
|
389
|
-
this.renderTargetBlur?.dispose();
|
|
390
|
-
|
|
391
|
-
// dispose the materials
|
|
392
|
-
this.depthMaterial?.dispose();
|
|
393
|
-
this.horizontalBlurMaterial?.dispose();
|
|
394
|
-
this.verticalBlurMaterial?.dispose();
|
|
395
|
-
|
|
396
|
-
// dispose the geometries
|
|
397
|
-
this.blurPlane?.geometry.dispose();
|
|
398
|
-
this.plane?.geometry.dispose();
|
|
399
|
-
this.occluderMesh?.geometry.dispose();
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/** @internal */
|
|
403
|
-
onBeforeRender(_frame: XRFrame | null): void {
|
|
404
|
-
|
|
405
|
-
if (this._needsFit && this.autoFit) {
|
|
406
|
-
this._needsFit = false;
|
|
407
|
-
this.fitShadows();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (this.manualUpdate) {
|
|
411
|
-
if (!this._needsUpdate) return;
|
|
412
|
-
}
|
|
413
|
-
this._needsUpdate = false;
|
|
438
|
+
this._ensureSetup();
|
|
414
439
|
|
|
415
440
|
if (!this.renderTarget || !this.renderTargetBlur ||
|
|
416
441
|
!this.depthMaterial || !this.shadowCamera ||
|
|
@@ -536,6 +561,7 @@ export class ContactShadows extends Behaviour {
|
|
|
536
561
|
scene.background = initialBackground;
|
|
537
562
|
renderer.xr.enabled = prevXRState;
|
|
538
563
|
this.context.scene.matrixWorldAutoUpdate = prevSceneMatrixAutoUpdate;
|
|
564
|
+
|
|
539
565
|
}
|
|
540
566
|
|
|
541
567
|
// renderTarget --> blurPlane (horizontalBlur) --> renderTargetBlur --> blurPlane (verticalBlur) --> renderTarget
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CubeUVReflectionMapping, ShaderMaterial, Texture } from "three";
|
|
1
|
+
import { CubeUVReflectionMapping, Euler, Matrix3, Matrix4, ShaderMaterial, Texture } from "three";
|
|
2
2
|
import { GroundedSkybox as GroundProjection } from 'three/examples/jsm/objects/GroundedSkybox.js';
|
|
3
3
|
|
|
4
4
|
import { Gizmos } from "../engine/engine_gizmos.js";
|
|
@@ -21,8 +21,13 @@ type GroundProjectionShaderUniforms = {
|
|
|
21
21
|
needleGroundProjectionBlending: { value: number };
|
|
22
22
|
needleGroundProjectionAlphaFactor: { value: number };
|
|
23
23
|
needleGroundProjectionBackgroundIntensity: { value: number };
|
|
24
|
+
needleGroundProjectionBackgroundRotation: { value: Matrix3 };
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
// Reused per-frame to build the background-rotation matrix without allocating in onBeforeRender.
|
|
28
|
+
const _backgroundRotationEuler = new Euler();
|
|
29
|
+
const _backgroundRotationMatrix = new Matrix4();
|
|
30
|
+
|
|
26
31
|
const needleCubeUvMapVarying = /* glsl */`
|
|
27
32
|
#ifdef NEEDLE_USE_CUBE_UV_MAP
|
|
28
33
|
varying vec3 vNeedleGroundProjectionWorldDirection;
|
|
@@ -79,7 +84,10 @@ const needleCubeUvMapFragment = /* glsl */`
|
|
|
79
84
|
|
|
80
85
|
#endif
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
// Apply backgroundIntensity uniformly across the whole projection (center included), matching how the
|
|
88
|
+
// skybox applies it (backgroundCube.glsl: texColor.rgb *= backgroundIntensity). Only the blurriness
|
|
89
|
+
// ramps toward the horizon - the intensity must not.
|
|
90
|
+
sampledDiffuseColor.rgb *= needleGroundProjectionBackgroundIntensity;
|
|
83
91
|
diffuseColor *= sampledDiffuseColor;
|
|
84
92
|
|
|
85
93
|
#endif
|
|
@@ -122,7 +130,8 @@ function getGroundProjectionShaderUniforms(material: GroundProjectionMaterial):
|
|
|
122
130
|
needleGroundProjectionBlurriness: { value: 0 },
|
|
123
131
|
needleGroundProjectionBlending: { value: 0 },
|
|
124
132
|
needleGroundProjectionAlphaFactor: { value: 1 },
|
|
125
|
-
needleGroundProjectionBackgroundIntensity: { value: 1 }
|
|
133
|
+
needleGroundProjectionBackgroundIntensity: { value: 1 },
|
|
134
|
+
needleGroundProjectionBackgroundRotation: { value: new Matrix3() }
|
|
126
135
|
};
|
|
127
136
|
}
|
|
128
137
|
|
|
@@ -133,15 +142,25 @@ function configureGroundProjectionMaterial(material: GroundProjectionMaterial, t
|
|
|
133
142
|
shader.uniforms.needleGroundProjectionBlending = projectionUniforms.needleGroundProjectionBlending;
|
|
134
143
|
shader.uniforms.needleGroundProjectionAlphaFactor = projectionUniforms.needleGroundProjectionAlphaFactor;
|
|
135
144
|
shader.uniforms.needleGroundProjectionBackgroundIntensity = projectionUniforms.needleGroundProjectionBackgroundIntensity;
|
|
145
|
+
shader.uniforms.needleGroundProjectionBackgroundRotation = projectionUniforms.needleGroundProjectionBackgroundRotation;
|
|
136
146
|
|
|
137
147
|
shader.vertexShader = shader.vertexShader
|
|
138
|
-
.replace("#include <uv_pars_vertex>", `#include <uv_pars_vertex>\n${needleCubeUvMapVarying}
|
|
148
|
+
.replace("#include <uv_pars_vertex>", `#include <uv_pars_vertex>\n${needleCubeUvMapVarying}
|
|
149
|
+
#ifdef NEEDLE_USE_CUBE_UV_MAP
|
|
150
|
+
uniform mat3 needleGroundProjectionBackgroundRotation;
|
|
151
|
+
#endif`)
|
|
139
152
|
.replace(
|
|
140
153
|
"#include <worldpos_vertex>",
|
|
141
154
|
`#include <worldpos_vertex>
|
|
142
155
|
#ifdef NEEDLE_USE_CUBE_UV_MAP
|
|
143
|
-
//
|
|
144
|
-
|
|
156
|
+
// Sample the environment in the fragment's actual world direction. GroundedSkybox mirrors the geometry
|
|
157
|
+
// on Z (geometry.scale(1,1,-1)) and that mirrored vertex IS the on-screen direction; the skybox and
|
|
158
|
+
// environment lighting are likewise sampled in true world directions. So we must NOT undo the mirror
|
|
159
|
+
// here - doing so reflects the sampling azimuth and flips the panorama left-right vs. the skybox.
|
|
160
|
+
// backgroundRotation is applied to the sampling direction (exactly like the skybox does in
|
|
161
|
+
// backgroundCube.glsl), NOT by rotating the mesh - that keeps the floor flat and matches the skybox at
|
|
162
|
+
// the horizon for any background rotation.
|
|
163
|
+
vNeedleGroundProjectionWorldDirection = needleGroundProjectionBackgroundRotation * transformDirection( position, modelMatrix );
|
|
145
164
|
#endif`
|
|
146
165
|
);
|
|
147
166
|
|
|
@@ -271,6 +290,25 @@ export class GroundProjectedEnv extends Behaviour {
|
|
|
271
290
|
private _projection?: GroundProjection;
|
|
272
291
|
private _watcher?: Watch;
|
|
273
292
|
|
|
293
|
+
/** The scene's real backgroundBlurriness, stored while we temporarily override it for the render. */
|
|
294
|
+
private _savedBackgroundBlur: number | null = null;
|
|
295
|
+
|
|
296
|
+
/** pre_render_callback: force the skybox blur just before the render (after all component onBeforeRender). */
|
|
297
|
+
private readonly _forceBackgroundBlur = (_frame?: XRFrame | null) => {
|
|
298
|
+
if (this._savedBackgroundBlur !== null) return; // already overriding (safety)
|
|
299
|
+
if (!(this.scene.background instanceof Texture)) return; // no skybox to blur
|
|
300
|
+
const current = this.scene.backgroundBlurriness ?? 0;
|
|
301
|
+
this._savedBackgroundBlur = current;
|
|
302
|
+
this.scene.backgroundBlurriness = 1;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/** post_render_callback: restore the scene's real blur right after the render. */
|
|
306
|
+
private readonly _restoreBackgroundBlur = () => {
|
|
307
|
+
if (this._savedBackgroundBlur === null) return;
|
|
308
|
+
this.scene.backgroundBlurriness = this._savedBackgroundBlur;
|
|
309
|
+
this._savedBackgroundBlur = null;
|
|
310
|
+
};
|
|
311
|
+
|
|
274
312
|
|
|
275
313
|
/** @internal */
|
|
276
314
|
awake() {
|
|
@@ -291,11 +329,23 @@ export class GroundProjectedEnv extends Behaviour {
|
|
|
291
329
|
this._needsTextureUpdate = true;
|
|
292
330
|
});
|
|
293
331
|
}
|
|
332
|
+
// Force the skybox blur via the render-bracketing callbacks (these run after every component's
|
|
333
|
+
// onBeforeRender, so the override can't be clobbered by e.g. Camera.applyClearFlags).
|
|
334
|
+
if (this.context.pre_render_callbacks.indexOf(this._forceBackgroundBlur) < 0)
|
|
335
|
+
this.context.pre_render_callbacks.push(this._forceBackgroundBlur);
|
|
336
|
+
if (this.context.post_render_callbacks.indexOf(this._restoreBackgroundBlur) < 0)
|
|
337
|
+
this.context.post_render_callbacks.push(this._restoreBackgroundBlur);
|
|
294
338
|
}
|
|
295
339
|
/** @internal */
|
|
296
340
|
onDisable() {
|
|
297
341
|
this._watcher?.revoke();
|
|
298
342
|
this._projection?.removeFromParent();
|
|
343
|
+
const pre = this.context.pre_render_callbacks.indexOf(this._forceBackgroundBlur);
|
|
344
|
+
if (pre >= 0) this.context.pre_render_callbacks.splice(pre, 1);
|
|
345
|
+
const post = this.context.post_render_callbacks.indexOf(this._restoreBackgroundBlur);
|
|
346
|
+
if (post >= 0) this.context.post_render_callbacks.splice(post, 1);
|
|
347
|
+
// If we were disabled mid-frame (after force, before restore), put the real value back.
|
|
348
|
+
this._restoreBackgroundBlur();
|
|
299
349
|
}
|
|
300
350
|
/** @internal */
|
|
301
351
|
onEnterXR(): void {
|
|
@@ -312,7 +362,21 @@ export class GroundProjectedEnv extends Behaviour {
|
|
|
312
362
|
/** @internal */
|
|
313
363
|
onBeforeRender(): void {
|
|
314
364
|
if (this._projection && this.scene.backgroundRotation) {
|
|
315
|
-
|
|
365
|
+
// Mirror three's WebGLBackground: build the same mat3 it applies to the skybox sampling
|
|
366
|
+
// direction and feed it to our shader. This rotates the sampled environment exactly like the
|
|
367
|
+
// skybox (backgroundCube.glsl) instead of rotating the mesh - keeping the floor flat and the
|
|
368
|
+
// horizon aligned with the skybox for any background rotation.
|
|
369
|
+
_backgroundRotationEuler.copy(this.scene.backgroundRotation);
|
|
370
|
+
// accommodate left-handed frame (same as WebGLBackground.js)
|
|
371
|
+
_backgroundRotationEuler.x *= -1;
|
|
372
|
+
_backgroundRotationEuler.y *= -1;
|
|
373
|
+
_backgroundRotationEuler.z *= -1;
|
|
374
|
+
// Note: the extra cube-texture flip in WebGLBackground only applies to raw CubeTextures, not the
|
|
375
|
+
// PMREM render-target env this shader path samples - so it is intentionally omitted here.
|
|
376
|
+
const uniforms = getGroundProjectionShaderUniforms(this._projection.material);
|
|
377
|
+
uniforms.needleGroundProjectionBackgroundRotation.value.setFromMatrix4(
|
|
378
|
+
_backgroundRotationMatrix.makeRotationFromEuler(_backgroundRotationEuler)
|
|
379
|
+
);
|
|
316
380
|
}
|
|
317
381
|
|
|
318
382
|
if (this._projection && this.context.scene.background instanceof Texture) {
|
|
@@ -102,7 +102,10 @@ export class NeedleMenu extends Behaviour {
|
|
|
102
102
|
this.context.menu.showFullscreenOption(true);
|
|
103
103
|
if (this.createMuteButton === true)
|
|
104
104
|
this.context.menu.showAudioPlaybackOption(true);
|
|
105
|
-
|
|
105
|
+
// Apply the value in BOTH directions: the spatial menu is enabled by
|
|
106
|
+
// default, so unchecking the option must explicitly disable it. Only skip
|
|
107
|
+
// when the field is unset (undefined) to keep the engine default.
|
|
108
|
+
if (this.showSpatialMenu !== undefined)
|
|
106
109
|
this.context.menu.showSpatialMenu(this.showSpatialMenu);
|
|
107
110
|
if (this.createQRCodeButton === true) {
|
|
108
111
|
if (!DeviceUtilities.isMobileDevice()) {
|
|
@@ -56,6 +56,14 @@ function applyRotationAttribute(attr: string, target: { set(x: number, y: number
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function applyTexture(context: IContext, texture: Texture, isBackground: boolean, isEnvironment: boolean) {
|
|
59
|
+
// Textures load asynchronously. The context can be disposed/torn down while a
|
|
60
|
+
// load is in flight (element removed, context disposed, scene switched) — at
|
|
61
|
+
// which point context.scene is set to null on dispose. Bail instead of
|
|
62
|
+
// dereferencing it (the rest of this function also touches context.scene).
|
|
63
|
+
if (!context.scene) {
|
|
64
|
+
if (debug) console.warn("Skybox: skipping applyTexture — context was torn down before the texture finished loading");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
59
67
|
if (!(texture instanceof CubeTexture || texture instanceof CompressedCubeTexture)
|
|
60
68
|
&& texture.mapping !== CubeUVReflectionMapping) {
|
|
61
69
|
texture.mapping = EquirectangularRefractionMapping;
|
|
@@ -3,7 +3,7 @@ import { Euler, Material, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "t
|
|
|
3
3
|
|
|
4
4
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
|
|
5
5
|
import { findObjectOfType } from "../../../engine/engine_components.js";
|
|
6
|
-
import { _$
|
|
6
|
+
import { _$gQv } from "../../../engine/engine_license.js";
|
|
7
7
|
import { serializable } from "../../../engine/engine_serialization.js";
|
|
8
8
|
import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
|
|
9
9
|
import { DeviceUtilities, getParam } from "../../../engine/engine_utils.js";
|
|
@@ -277,7 +277,7 @@ export class USDZExporter extends Behaviour {
|
|
|
277
277
|
let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
|
|
278
278
|
name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
|
|
279
279
|
|
|
280
|
-
if (!_$
|
|
280
|
+
if (!_$gQv()) {
|
|
281
281
|
if (name !== "") name += "-";
|
|
282
282
|
name += "MadeWithNeedle";
|
|
283
283
|
}
|
|
@@ -682,7 +682,7 @@ export class USDZExporter extends Behaviour {
|
|
|
682
682
|
if (debug)
|
|
683
683
|
showBalloonMessage("Quicklook url: " + callToActionURL);
|
|
684
684
|
if (callToActionURL) {
|
|
685
|
-
if (!_$
|
|
685
|
+
if (!_$gQv()) {
|
|
686
686
|
console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
|
|
687
687
|
}
|
|
688
688
|
else {
|
|
@@ -697,7 +697,7 @@ export class USDZExporter extends Behaviour {
|
|
|
697
697
|
private buildQuicklookOverlay(): CustomBranding {
|
|
698
698
|
const obj: CustomBranding = {};
|
|
699
699
|
if (this.customBranding) Object.assign(obj, this.customBranding);
|
|
700
|
-
if (!_$
|
|
700
|
+
if (!_$gQv()) {
|
|
701
701
|
console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
|
|
702
702
|
obj.callToAction = "Close";
|
|
703
703
|
obj.checkoutTitle = "🌵 Made with Needle";
|