@needle-tools/engine 4.10.5-next.a5d5bf4 → 4.11.0-beta
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/CHANGELOG.md +10 -0
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-D56E0HeK.min.js → needle-engine.bundle-BJ2hrLWW.min.js} +130 -130
- package/dist/{needle-engine.bundle-B2qX4saI.js → needle-engine.bundle-DZS0TuER.js} +5974 -5826
- package/dist/{needle-engine.bundle-DPHrCUDs.umd.cjs → needle-engine.bundle-JCl0_y_J.umd.cjs} +133 -133
- package/dist/needle-engine.d.ts +14 -0
- package/dist/needle-engine.js +321 -320
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/codegen/register_types.js +2 -0
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_gizmos.js +2 -2
- package/lib/engine/engine_gizmos.js.map +1 -1
- package/lib/engine/engine_physics.js +19 -12
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/js-extensions/Object3D.d.ts +14 -0
- package/lib/engine/js-extensions/Object3D.js +13 -0
- package/lib/engine/js-extensions/Object3D.js.map +1 -1
- package/lib/engine-components/Renderer.js +33 -32
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/RendererLightmap.d.ts +7 -5
- package/lib/engine-components/RendererLightmap.js +29 -30
- package/lib/engine-components/RendererLightmap.js.map +1 -1
- package/lib/engine-components/SeeThrough.d.ts +70 -0
- package/lib/engine-components/SeeThrough.js +223 -0
- package/lib/engine-components/SeeThrough.js.map +1 -0
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/ui/Graphic.js +13 -1
- package/lib/engine-components/ui/Graphic.js.map +1 -1
- package/lib/engine-components/ui/RaycastUtils.js +5 -3
- package/lib/engine-components/ui/RaycastUtils.js.map +1 -1
- package/lib/engine-components/utils/LookAt.js +4 -2
- package/lib/engine-components/utils/LookAt.js.map +1 -1
- package/lib/engine-components/web/Clickthrough.d.ts +2 -1
- package/lib/engine-components/web/Clickthrough.js +2 -1
- package/lib/engine-components/web/Clickthrough.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +7 -1
- package/lib/engine-components/web/CursorFollow.js +35 -2
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/package.json +2 -2
- package/src/engine/codegen/register_types.ts +2 -0
- package/src/engine/engine_gizmos.ts +3 -3
- package/src/engine/engine_physics.ts +24 -12
- package/src/engine/js-extensions/Object3D.ts +32 -0
- package/src/engine-components/Renderer.ts +38 -37
- package/src/engine-components/RendererLightmap.ts +31 -33
- package/src/engine-components/SeeThrough.ts +256 -0
- package/src/engine-components/codegen/components.ts +1 -0
- package/src/engine-components/ui/Graphic.ts +13 -1
- package/src/engine-components/ui/RaycastUtils.ts +9 -8
- package/src/engine-components/utils/LookAt.ts +4 -1
- package/src/engine-components/web/Clickthrough.ts +2 -1
- package/src/engine-components/web/CursorFollow.ts +48 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AxesHelper, Box3, BoxGeometry, BufferAttribute, BufferGeometry, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Material,Matrix4, Mesh, MeshBasicMaterial, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
|
|
1
|
+
import { AxesHelper, Box3, BoxGeometry, BufferAttribute, BufferGeometry, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Material, Matrix4, Mesh, MeshBasicMaterial, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
|
|
2
2
|
import ThreeMeshUI, { Inline, Text } from "three-mesh-ui"
|
|
3
3
|
import { type Options } from 'three-mesh-ui/build/types/core/elements/MeshUIBaseElement.js';
|
|
4
4
|
|
|
@@ -289,11 +289,11 @@ export class Gizmos {
|
|
|
289
289
|
const mesh = Internal.getMesh(options.duration ?? 0);
|
|
290
290
|
if ("mesh" in options) {
|
|
291
291
|
mesh.geometry = options.mesh.geometry;
|
|
292
|
-
mesh.
|
|
292
|
+
mesh.matrixWorld.copy(options.mesh.matrixWorld);
|
|
293
293
|
}
|
|
294
294
|
else {
|
|
295
295
|
mesh.geometry = options.geometry;
|
|
296
|
-
mesh.
|
|
296
|
+
mesh.matrixWorld.copy(options.matrix);
|
|
297
297
|
}
|
|
298
298
|
mesh.matrixAutoUpdate = false;
|
|
299
299
|
mesh.matrixWorldAutoUpdate = false;
|
|
@@ -325,8 +325,12 @@ export class Physics {
|
|
|
325
325
|
const mesh = obj as Mesh | SkinnedMesh;
|
|
326
326
|
const geo = mesh.geometry;
|
|
327
327
|
|
|
328
|
+
if (obj.raycastAllowed === false) {
|
|
329
|
+
shouldIntersectObject = false;
|
|
330
|
+
}
|
|
331
|
+
|
|
328
332
|
// We need to run this first because of "EventSystem.testObject" implementation
|
|
329
|
-
if (options.testObject) {
|
|
333
|
+
if (shouldIntersectObject && options.testObject) {
|
|
330
334
|
const testResult = options.testObject?.(obj);
|
|
331
335
|
if (testResult === false) {
|
|
332
336
|
continue;
|
|
@@ -335,8 +339,7 @@ export class Physics {
|
|
|
335
339
|
shouldIntersectObject = false;
|
|
336
340
|
}
|
|
337
341
|
}
|
|
338
|
-
|
|
339
|
-
if (shouldIntersectObject) {
|
|
342
|
+
else if (shouldIntersectObject) {
|
|
340
343
|
if (!geo) {
|
|
341
344
|
shouldIntersectObject = false;
|
|
342
345
|
}
|
|
@@ -348,18 +351,24 @@ export class Physics {
|
|
|
348
351
|
|
|
349
352
|
|
|
350
353
|
if (shouldIntersectObject) {
|
|
351
|
-
const raycastMesh = getRaycastMesh(obj);
|
|
352
|
-
if (raycastMesh) mesh.geometry = raycastMesh as any;
|
|
353
354
|
const lastResultsCount = results.length;
|
|
355
|
+
const preference = obj.raycastPreference || "lod";
|
|
354
356
|
|
|
355
|
-
let usePrecise =
|
|
357
|
+
let usePrecise = preference !== "bounds";
|
|
356
358
|
if (options.precise === false) usePrecise = false;
|
|
357
359
|
usePrecise ||= geo.getAttribute("position")?.array?.length < 64;
|
|
358
360
|
if (mesh instanceof GroundedSkybox) {
|
|
359
361
|
usePrecise = false;
|
|
360
362
|
}
|
|
361
363
|
|
|
362
|
-
|
|
364
|
+
|
|
365
|
+
if (preference === "lod") {
|
|
366
|
+
const raycastMesh = getRaycastMesh(obj);
|
|
367
|
+
if (raycastMesh) mesh.geometry = raycastMesh as any;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
if (!usePrecise && boundsRaycast(mesh, raycaster, results)) {
|
|
363
372
|
// did handle raycast
|
|
364
373
|
}
|
|
365
374
|
else if (options.useAcceleratedRaycast !== false) {
|
|
@@ -369,13 +378,16 @@ export class Physics {
|
|
|
369
378
|
raycaster.intersectObject(mesh, false, results);
|
|
370
379
|
}
|
|
371
380
|
|
|
381
|
+
// Restore
|
|
382
|
+
mesh.geometry = geo;
|
|
383
|
+
|
|
384
|
+
// Debug
|
|
372
385
|
if (debugPhysics && results.length != lastResultsCount) {
|
|
373
386
|
const latestResult = results[results.length - 1];
|
|
374
|
-
|
|
375
|
-
Gizmos.
|
|
376
|
-
Gizmos.DrawWireMesh({ mesh: obj as Mesh, depthTest: false, duration: .2, color: col });
|
|
387
|
+
Gizmos.DrawWireSphere(latestResult.point, .1, 0x770000, 1, false);
|
|
388
|
+
Gizmos.DrawWireMesh({ mesh: obj as Mesh, depthTest: false, duration: .2, color: 0x770000 });
|
|
377
389
|
}
|
|
378
|
-
|
|
390
|
+
|
|
379
391
|
}
|
|
380
392
|
|
|
381
393
|
if (options.recursive !== false) {
|
|
@@ -481,7 +493,7 @@ const normalUpMatrix = new Matrix3();
|
|
|
481
493
|
/**
|
|
482
494
|
* @returns false if custom raycasting can not run, otherwise true
|
|
483
495
|
*/
|
|
484
|
-
function
|
|
496
|
+
function boundsRaycast(mesh: Mesh, raycaster: Raycaster, results: Intersection[]): boolean {
|
|
485
497
|
const originalComputeIntersectionsFn = mesh["_computeIntersections"];
|
|
486
498
|
if (!originalComputeIntersectionsFn) {
|
|
487
499
|
return false;
|
|
@@ -34,6 +34,23 @@ declare module 'three' {
|
|
|
34
34
|
*/
|
|
35
35
|
hideFlags: HideFlags;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* If false the object will be ignored for raycasting (e.g. pointer events). Default is true.
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
raycastAllowed: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Set a raycast preference for the object:
|
|
45
|
+
* - `lod` will use the raycast mesh lod if available (default). This is usually a simplified mesh for raycasting.
|
|
46
|
+
* - `bounds` will use the bounding box of the object for raycasting. This is very fast but not very accurate.
|
|
47
|
+
* - `full` will use the full mesh for raycasting. This is the most accurate but also the slowest option.
|
|
48
|
+
*
|
|
49
|
+
* **NOTE:** Needle Engine's Raycast system will use Mesh BVH by default - so event 'full' is usually faster than default three.js raycasting.
|
|
50
|
+
*/
|
|
51
|
+
raycastPreference?: 'lod' | 'bounds' | 'full';
|
|
52
|
+
|
|
53
|
+
|
|
37
54
|
/**
|
|
38
55
|
* Add a Needle Engine component to the {@link Object3D}.
|
|
39
56
|
* @param comp The component instance or constructor to add.
|
|
@@ -242,6 +259,21 @@ if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "activeSelf")) {
|
|
|
242
259
|
});
|
|
243
260
|
}
|
|
244
261
|
|
|
262
|
+
|
|
263
|
+
if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "raycastAllowed")) {
|
|
264
|
+
Object.defineProperty(Object3D.prototype, "raycastAllowed", {
|
|
265
|
+
get: function () {
|
|
266
|
+
return this.userData && this.userData.raycastAllowed !== false;
|
|
267
|
+
},
|
|
268
|
+
set: function (val: boolean) {
|
|
269
|
+
const self = this as Object3D;
|
|
270
|
+
if (!self.userData) self.userData = {};
|
|
271
|
+
self.userData.raycastAllowed = val;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
245
277
|
if (!Object.getOwnPropertyDescriptor(Object3D.prototype, "worldPosition")) {
|
|
246
278
|
Object.defineProperty(Object3D.prototype, "worldPosition", {
|
|
247
279
|
get: function () {
|
|
@@ -359,7 +359,7 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
359
359
|
get sharedMaterials(): SharedMaterialArray {
|
|
360
360
|
|
|
361
361
|
// @ts-ignore (original materials will be set during deserialization)
|
|
362
|
-
if(this._originalMaterials === undefined) return null;
|
|
362
|
+
if (this._originalMaterials === undefined) return null;
|
|
363
363
|
|
|
364
364
|
// @ts-ignore during deserialization code might access this property *before* the setter and then create an empty array
|
|
365
365
|
if (this.__isDeserializing === true) return null;
|
|
@@ -476,41 +476,43 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
476
476
|
? this._lightmapTextureOverride
|
|
477
477
|
: this.context.lightmaps.tryGetLightmap(this.sourceId, this.lightmapIndex);
|
|
478
478
|
if (tex) {
|
|
479
|
-
if (!this._lightmaps)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
479
|
+
if (!this._lightmaps) this._lightmaps = [];
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
const rm = new RendererLightmap(this);
|
|
483
|
+
rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex);
|
|
484
|
+
this._lightmaps.push(rm);
|
|
485
|
+
|
|
486
|
+
// if (type === "Mesh") {
|
|
487
|
+
// const mat = this.gameObject["material"];
|
|
488
|
+
// if (!mat?.isMeshBasicMaterial) {
|
|
489
|
+
// if (this._lightmaps.length <= 0) {
|
|
490
|
+
// }
|
|
491
|
+
// const rm = this._lightmaps[0];
|
|
492
|
+
// rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex);
|
|
493
|
+
// }
|
|
494
|
+
// else {
|
|
495
|
+
// if (mat)
|
|
496
|
+
// console.warn("Lightmapping is not supported on MeshBasicMaterial", mat.name)
|
|
497
|
+
// }
|
|
498
|
+
// }
|
|
499
|
+
// // for multi materials we need to loop through children
|
|
500
|
+
// // and then we add a lightmap renderer component to each of them
|
|
501
|
+
// else if (this.isMultiMaterialObject(this.gameObject) && this.sharedMaterials.length > 0) {
|
|
502
|
+
// for (let i = 0; i < this.gameObject.children.length; i++) {
|
|
503
|
+
// const child = this.gameObject.children[i];
|
|
504
|
+
// if (!child["material"]?.isMeshBasicMaterial) {
|
|
505
|
+
// let rm: RendererLightmap | undefined = undefined;
|
|
506
|
+
// if (i >= this._lightmaps.length) {
|
|
507
|
+
// rm = new RendererLightmap(child as Mesh, this.context);
|
|
508
|
+
// this._lightmaps.push(rm);
|
|
509
|
+
// }
|
|
510
|
+
// else
|
|
511
|
+
// rm = this._lightmaps[i];
|
|
512
|
+
// rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex);
|
|
513
|
+
// }
|
|
514
|
+
// }
|
|
515
|
+
// }
|
|
514
516
|
}
|
|
515
517
|
else {
|
|
516
518
|
if (debugRenderer) console.warn("Lightmap not found", this.sourceId, this.lightmapIndex);
|
|
@@ -742,7 +744,6 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
742
744
|
const environmentIntensity = this.context.mainCameraComponent?.environmentIntensity ?? 1;
|
|
743
745
|
material.envMapIntensity = Math.max(0, environmentIntensity * this.context.sceneLighting.environmentIntensity / factor);
|
|
744
746
|
}
|
|
745
|
-
|
|
746
747
|
if (this._lightmaps) {
|
|
747
748
|
for (const lm of this._lightmaps) {
|
|
748
749
|
lm.updateLightmapUniforms(material);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { NEEDLE_progressive } from "@needle-tools/gltf-progressive";
|
|
2
|
-
import { Material, Mesh, MeshPhysicalMaterial, ShaderMaterial, Texture, Vector4, WebGLProgramParametersWithUniforms } from "three";
|
|
2
|
+
import { Group, Material, Mesh, MeshPhysicalMaterial, Object3D, ShaderMaterial, Texture, Vector4, WebGLProgramParametersWithUniforms } from "three";
|
|
3
3
|
|
|
4
4
|
import type { Context } from "../engine/engine_setup.js";
|
|
5
5
|
import { getParam } from "../engine/engine_utils.js";
|
|
6
|
+
import { type Renderer } from "./Renderer.js";
|
|
6
7
|
|
|
7
8
|
const debug = getParam("debuglightmaps");
|
|
8
9
|
|
|
@@ -31,15 +32,15 @@ export class RendererLightmap {
|
|
|
31
32
|
private lightmapIndex: number = -1;
|
|
32
33
|
private lightmapScaleOffset: Vector4 = new Vector4(1, 1, 0, 0);
|
|
33
34
|
|
|
34
|
-
private
|
|
35
|
-
private
|
|
35
|
+
private readonly renderer: Renderer;
|
|
36
|
+
private get context(): Context { return this.renderer.context; }
|
|
37
|
+
private get gameObject() { return this.renderer.gameObject; }
|
|
36
38
|
private lightmapTexture: Texture | null = null;
|
|
37
39
|
private lightmapScaleOffsetUniform = { value: new Vector4(1, 1, 0, 0) };
|
|
38
40
|
private lightmapUniform: { value: Texture | null } = { value: null };
|
|
39
41
|
|
|
40
|
-
constructor(
|
|
41
|
-
this.
|
|
42
|
-
this.context = context;
|
|
42
|
+
constructor(renderer: Renderer) {
|
|
43
|
+
this.renderer = renderer;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
init(lightmapIndex: number, lightmapScaleOffset: Vector4, lightmapTexture: Texture) {
|
|
@@ -55,7 +56,7 @@ export class RendererLightmap {
|
|
|
55
56
|
console.log("Lightmap:", this.gameObject.name, lightmapIndex, "\nScaleOffset:", lightmapScaleOffset, "\nTexture:", lightmapTexture)
|
|
56
57
|
this.setLightmapDebugMaterial();
|
|
57
58
|
}
|
|
58
|
-
else if(debug) console.log("Use debuglightmaps=show to render lightmaps only in the scene.")
|
|
59
|
+
else if (debug) console.log("Use debuglightmaps=show to render lightmaps only in the scene.")
|
|
59
60
|
this.applyLightmap();
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -77,42 +78,38 @@ export class RendererLightmap {
|
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.gameObject["Needle:Multimaterial-LightmapWarning"] = true;
|
|
83
|
-
console.warn("Lightmap on multimaterial object is not supported yet... please open a feature request on https://github.com/needle-tools/needle-engine-support if your project requires it");
|
|
84
|
-
}
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
81
|
+
const mesh = this.gameObject as unknown as (Mesh | Group);
|
|
82
|
+
this.ensureLightmapUvs(mesh);
|
|
87
83
|
|
|
88
|
-
|
|
84
|
+
for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
mesh.geometry.setAttribute("uv1", mesh.geometry.getAttribute("uv"));
|
|
86
|
+
const mat = this.renderer.sharedMaterials[i];
|
|
87
|
+
if (!mat) continue;
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
mats[i] = this.ensureLightmapMaterial(mats[i]);
|
|
89
|
+
const newMat = this.ensureLightmapMaterial(mat);
|
|
90
|
+
if (mat !== newMat) {
|
|
91
|
+
this.renderer.sharedMaterials[i] = newMat;
|
|
98
92
|
}
|
|
99
93
|
}
|
|
100
|
-
else {
|
|
101
|
-
this.gameObject.material = this.ensureLightmapMaterial(this.gameObject.material);
|
|
102
|
-
}
|
|
103
94
|
|
|
104
95
|
if (this.lightmapIndex >= 0 && this.lightmapTexture) {
|
|
105
96
|
// always on channel 1 for now. We could optimize this by passing the correct lightmap index along
|
|
106
97
|
this.lightmapTexture.channel = 1;
|
|
107
|
-
const mat
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
for (const mat of this.renderer.sharedMaterials) {
|
|
99
|
+
if (mat) this.assignLightmapTexture(mat);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
111
103
|
|
|
112
|
-
|
|
104
|
+
private ensureLightmapUvs(object: Object3D | Group | Mesh) {
|
|
105
|
+
if (object instanceof Mesh) {
|
|
106
|
+
if (!object.geometry.getAttribute("uv1")) {
|
|
107
|
+
object.geometry.setAttribute("uv1", object.geometry.getAttribute("uv"));
|
|
113
108
|
}
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
}
|
|
110
|
+
else if (object instanceof Group) {
|
|
111
|
+
for (const child of object.children) {
|
|
112
|
+
this.ensureLightmapUvs(child);
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
115
|
}
|
|
@@ -127,7 +124,7 @@ export class RendererLightmap {
|
|
|
127
124
|
if (material["NEEDLE:lightmap-material-version"] == undefined) {
|
|
128
125
|
if (debug) console.warn("Cloning material for lightmap " + material.name);
|
|
129
126
|
const mat: Material = material.clone();
|
|
130
|
-
if(!mat.name?.includes("(lightmap)")) mat.name = material.name + " (lightmap)";
|
|
127
|
+
if (!mat.name?.includes("(lightmap)")) mat.name = material.name + " (lightmap)";
|
|
131
128
|
material = mat;
|
|
132
129
|
material.onBeforeCompile = this.onBeforeCompile;
|
|
133
130
|
}
|
|
@@ -152,6 +149,7 @@ export class RendererLightmap {
|
|
|
152
149
|
|
|
153
150
|
// assign the lightmap
|
|
154
151
|
material.lightMap = this.lightmapTexture;
|
|
152
|
+
material.needsUpdate = true;
|
|
155
153
|
// store the version of the material
|
|
156
154
|
material["NEEDLE:lightmap-material-version"] = material.version;
|
|
157
155
|
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Material, Object3D, Vector3 } from "three";
|
|
2
|
+
|
|
3
|
+
import { Gizmos } from "../engine/engine_gizmos.js";
|
|
4
|
+
import { Mathf } from "../engine/engine_math.js";
|
|
5
|
+
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
6
|
+
import { getTempVector } from "../engine/engine_three_utils.js";
|
|
7
|
+
import { getParam } from "../engine/engine_utils.js";
|
|
8
|
+
import { Behaviour } from "./Component.js";
|
|
9
|
+
import { Renderer } from "./Renderer.js";
|
|
10
|
+
|
|
11
|
+
const debugSeeThrough = getParam("debugseethrough");
|
|
12
|
+
|
|
13
|
+
type MaterialState = {
|
|
14
|
+
opacity: number,
|
|
15
|
+
transparent: boolean,
|
|
16
|
+
alphaHash: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type MaterialWithState = Material & {
|
|
20
|
+
/** Original values */
|
|
21
|
+
userData: {
|
|
22
|
+
seeThrough: {
|
|
23
|
+
initial: MaterialState,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let i = 0;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Makes the object fade out when it is obscuring the reference point from the camera. This component can be put on any object in the scene. It will affect all Renderer components on the same object and child objects.
|
|
33
|
+
*
|
|
34
|
+
* Useful for e.g. making walls transparent when the camera is outside or hiding object's that would otherwise block the view.
|
|
35
|
+
*
|
|
36
|
+
* Requires a Renderer component on the same object or a child object.
|
|
37
|
+
*
|
|
38
|
+
* - Example https://see-through-walls-z23hmxbz1kjfjn.needle.run/
|
|
39
|
+
*/
|
|
40
|
+
export class SeeThrough extends Behaviour {
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Assign a reference point - if this point will be obscured from the camera by this object then this object will fade out.
|
|
44
|
+
* If no reference point is assigned the scene's root object will be used as reference point.
|
|
45
|
+
*/
|
|
46
|
+
@serializable(Object3D)
|
|
47
|
+
referencePoint: Object3D | null = null;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fade Duration in seconds
|
|
51
|
+
* @default 0.05
|
|
52
|
+
*/
|
|
53
|
+
@serializable()
|
|
54
|
+
fadeDuration: number = .05;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Minimum alpha value when fading out (0-1)
|
|
58
|
+
* @default 0
|
|
59
|
+
*/
|
|
60
|
+
@serializable()
|
|
61
|
+
minAlpha: number = 0;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* When useAlphaHash is enabled the object will fade out using alpha hashing, this means the object can stay opaque. If disabled the object will set to be transparent when fading out.
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
@serializable()
|
|
68
|
+
useAlphaHash: boolean = true;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set this to force updating the reference point position and direction
|
|
72
|
+
*/
|
|
73
|
+
set needsUpdate(val: boolean) {
|
|
74
|
+
this._needsUpdate = val;
|
|
75
|
+
}
|
|
76
|
+
get needsUpdate() {
|
|
77
|
+
return this._needsUpdate;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Override the alpha value, -1 means no override
|
|
82
|
+
* @default -1
|
|
83
|
+
*/
|
|
84
|
+
@serializable()
|
|
85
|
+
overrideAlpha: number = -1;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
*/
|
|
90
|
+
@serializable()
|
|
91
|
+
autoUpdate: boolean = true;
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
private readonly _referencePointVector: Vector3 = new Vector3();
|
|
95
|
+
private readonly _referencePointDir: Vector3 = new Vector3();
|
|
96
|
+
private _distance: number = 0;
|
|
97
|
+
private _renderer: Renderer[] | null = null;
|
|
98
|
+
private _needsUpdate = true;
|
|
99
|
+
private _id = i++;
|
|
100
|
+
|
|
101
|
+
/** * @internal */
|
|
102
|
+
onEnable() {
|
|
103
|
+
this._needsUpdate = true;
|
|
104
|
+
this._renderer = null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @internal */
|
|
108
|
+
onDisable() {
|
|
109
|
+
this._renderer?.forEach(r => {
|
|
110
|
+
const original = this.rendererMaterialsOriginal.get(r);
|
|
111
|
+
for (let i = 0; i < r.sharedMaterials.length; i++) {
|
|
112
|
+
const mat = r.sharedMaterials[i];
|
|
113
|
+
if (!mat) continue;
|
|
114
|
+
if (original && original[i]) {
|
|
115
|
+
r.sharedMaterials[i] = original[i];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.rendererMaterials.delete(r);
|
|
119
|
+
this.rendererMaterialsOriginal.delete(r);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
update(): void {
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if (this._needsUpdate) {
|
|
130
|
+
this._needsUpdate = false;
|
|
131
|
+
this._renderer = this.gameObject.getComponentsInChildren(Renderer);
|
|
132
|
+
|
|
133
|
+
// NOTE: instead of using the object's anchor (gameObject.worldPosition) we could also get the object's bounding box center:
|
|
134
|
+
// getBoundingBox(this.gameObject); // < import { getBoundingBox } from "@needle-tools/engine";
|
|
135
|
+
this.updateDirection();
|
|
136
|
+
}
|
|
137
|
+
else if (this.autoUpdate && (this.context.time.frame + this._id) % 20 === 0) {
|
|
138
|
+
this.updateDirection();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if (!this.autoUpdate) return;
|
|
144
|
+
if (!this.referencePoint) return;
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
const dot = this._referencePointDir.dot(this.context.mainCamera.worldForward);
|
|
148
|
+
const shouldHide = dot > .2;
|
|
149
|
+
|
|
150
|
+
if (debugSeeThrough && this.referencePoint) {
|
|
151
|
+
const wp = this.gameObject.worldPosition;
|
|
152
|
+
Gizmos.DrawArrow(getTempVector(wp), wp.sub(this._referencePointDir), shouldHide ? 0xFF0000 : 0x00FF00);
|
|
153
|
+
Gizmos.DrawWireSphere(this.referencePoint.worldPosition, .05, 0x0000FF);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (shouldHide) {
|
|
157
|
+
this.updateAlpha(this.minAlpha, this.fadeDuration);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
this.updateAlpha(1, this.fadeDuration);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private readonly rendererMaterials = new WeakMap<Renderer, Array<MaterialWithState>>();
|
|
165
|
+
private readonly rendererMaterialsOriginal = new WeakMap<Renderer, Array<Material>>();
|
|
166
|
+
|
|
167
|
+
private updateDirection() {
|
|
168
|
+
this.referencePoint ??= this.context.scene;
|
|
169
|
+
this._referencePointVector.copy(this.gameObject.worldPosition.sub(this.referencePoint.worldPosition));
|
|
170
|
+
this._distance = this._referencePointVector.length();
|
|
171
|
+
this._referencePointDir.copy(this._referencePointVector)
|
|
172
|
+
.multiply(getTempVector(1, .5, 1)) // Reduce vertical influence
|
|
173
|
+
.normalize();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Update the alpha of the object's materials towards the target alpha over the given duration.
|
|
179
|
+
* @param targetAlpha Target alpha value (0-1)
|
|
180
|
+
* @param duration Duration in seconds to reach the target alpha. 0 means immediate. Default is the component's fadeDuration.
|
|
181
|
+
*/
|
|
182
|
+
updateAlpha(targetAlpha: number, duration: number = this.fadeDuration) {
|
|
183
|
+
|
|
184
|
+
if (this.overrideAlpha !== undefined && this.overrideAlpha !== -1) {
|
|
185
|
+
targetAlpha = this.overrideAlpha;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this._renderer?.forEach(renderer => {
|
|
189
|
+
|
|
190
|
+
if (targetAlpha < .9) {
|
|
191
|
+
renderer.gameObject.raycastAllowed = false;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
renderer.gameObject.raycastAllowed = true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!this.rendererMaterials.has(renderer)) {
|
|
198
|
+
const originalMaterials = new Array<Material>();
|
|
199
|
+
const clonedMaterials = new Array<MaterialWithState>();
|
|
200
|
+
|
|
201
|
+
// We clone the materials once and store them, so we can modify the opacity without affecting other objects using the same material. This could potentially be optimized further to re-use materials between renderers if multiple renderers use the same material.
|
|
202
|
+
for (let i = 0; i < renderer.sharedMaterials.length; i++) {
|
|
203
|
+
const mat = renderer.sharedMaterials[i];
|
|
204
|
+
if (!mat) continue;
|
|
205
|
+
originalMaterials.push(mat);
|
|
206
|
+
const matClone = mat.clone() as MaterialWithState;
|
|
207
|
+
// @ts-ignore
|
|
208
|
+
matClone.userData = mat.userData || {};
|
|
209
|
+
matClone.userData.seeThrough = {
|
|
210
|
+
initial: {
|
|
211
|
+
opacity: matClone.opacity,
|
|
212
|
+
transparent: matClone.transparent,
|
|
213
|
+
alphaHash: matClone.alphaHash
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
clonedMaterials.push(matClone);
|
|
217
|
+
renderer.sharedMaterials[i] = matClone;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.rendererMaterials.set(renderer, clonedMaterials);
|
|
221
|
+
this.rendererMaterialsOriginal.set(renderer, originalMaterials);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const materials = renderer.hasLightmap ? renderer.sharedMaterials : this.rendererMaterials.get(renderer);
|
|
225
|
+
if (!materials) return;
|
|
226
|
+
|
|
227
|
+
for (const mat of materials) {
|
|
228
|
+
if (!mat) continue;
|
|
229
|
+
|
|
230
|
+
let newAlpha = Mathf.lerp(mat.opacity, targetAlpha, duration <= 0 ? 1 : this.context.time.deltaTime / duration);;
|
|
231
|
+
if (newAlpha >= 0.99) newAlpha = 1;
|
|
232
|
+
else if (newAlpha <= 0.01) newAlpha = 0;
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
const wasTransparent = mat.transparent;
|
|
236
|
+
const wasAlphaHash = mat.alphaHash;
|
|
237
|
+
|
|
238
|
+
mat.alphaHash = this.useAlphaHash;
|
|
239
|
+
|
|
240
|
+
if (mat.userData && "seeThrough" in mat.userData) {
|
|
241
|
+
const initial = mat.userData.seeThrough.initial as MaterialState;
|
|
242
|
+
mat.opacity = initial.opacity * newAlpha;
|
|
243
|
+
mat.transparent = mat.opacity >= 1 ? initial.transparent : !this.useAlphaHash;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
mat.transparent = mat.opacity >= 1 ? false : !this.useAlphaHash;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (wasTransparent != mat.transparent || wasAlphaHash != mat.alphaHash) {
|
|
250
|
+
mat.needsUpdate = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
}
|
|
@@ -145,6 +145,7 @@ export { RendererLightmap } from "../RendererLightmap.js";
|
|
|
145
145
|
export { Rigidbody } from "../RigidBody.js";
|
|
146
146
|
export { SceneSwitcher } from "../SceneSwitcher.js";
|
|
147
147
|
export { ScreenCapture } from "../ScreenCapture.js";
|
|
148
|
+
export { SeeThrough } from "../SeeThrough.js";
|
|
148
149
|
export { ShadowCatcher } from "../ShadowCatcher.js";
|
|
149
150
|
export { RemoteSkybox } from "../Skybox.js";
|
|
150
151
|
export { SmoothFollow } from "../SmoothFollow.js";
|
|
@@ -59,7 +59,19 @@ export class Graphic extends BaseUIComponent implements IGraphic, IRectTransform
|
|
|
59
59
|
this.sRGBColor.copy(this._color);
|
|
60
60
|
this.sRGBColor.convertLinearToSRGB();
|
|
61
61
|
_colorStateObject.backgroundColor = this.sRGBColor;
|
|
62
|
-
_colorStateObject.backgroundOpacity = this._color.alpha
|
|
62
|
+
_colorStateObject.backgroundOpacity = this._color.alpha;
|
|
63
|
+
|
|
64
|
+
// E.g. when a button is setting states, we need to merge the state color with the base color
|
|
65
|
+
const activeStateName = this.uiObject["_simpleState__activeStates"]?.[0];
|
|
66
|
+
if (activeStateName) {
|
|
67
|
+
const active = this.uiObject["_simpleState__states"]?.[activeStateName];
|
|
68
|
+
if (active) {
|
|
69
|
+
if ("backgroundColor" in active) _colorStateObject.backgroundColor = active["backgroundColor"];
|
|
70
|
+
if ("backgroundOpacity" in active) _colorStateObject.backgroundOpacity = active["backgroundOpacity"];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_colorStateObject.backgroundOpacity *= this._alphaFactor;
|
|
63
75
|
this.applyEffects(_colorStateObject, this._alphaFactor);
|
|
64
76
|
this.uiObject.set(_colorStateObject);
|
|
65
77
|
this.markDirty();
|