@needle-tools/engine 4.10.5-next.a5d5bf4 → 4.11.0-beta.1
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 +11 -0
- package/components.needle.json +1 -1
- package/dist/{gltf-progressive-B63NpN_i.js → gltf-progressive-CXVECA3a.js} +1 -1
- package/dist/{needle-engine.bundle-D56E0HeK.min.js → needle-engine.bundle-6j5gE-aQ.min.js} +137 -137
- package/dist/{needle-engine.bundle-B2qX4saI.js → needle-engine.bundle-BDZ09xyt.js} +6589 -6438
- package/dist/{needle-engine.bundle-DPHrCUDs.umd.cjs → needle-engine.bundle-CFc4UIqz.umd.cjs} +139 -139
- 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 +44 -14
- 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/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +2 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.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/lib/engine-components/web/ScrollFollow.js +1 -2
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/package.json +2 -2
- package/plugins/common/needle-engine.js +41 -0
- package/plugins/common/worker.js +129 -0
- package/plugins/vite/asap.js +5 -23
- package/plugins/vite/dependencies.js +21 -11
- package/plugins/vite/index.js +7 -0
- package/plugins/vite/needle-app.js +194 -0
- package/src/engine/codegen/register_types.ts +2 -0
- package/src/engine/engine_gizmos.ts +3 -3
- package/src/engine/engine_physics.ts +51 -14
- package/src/engine/js-extensions/Object3D.ts +32 -0
- package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +3 -1
- 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
- package/src/engine-components/web/ScrollFollow.ts +1 -1
|
@@ -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();
|
|
@@ -11,9 +11,8 @@ export class UIRaycastUtils {
|
|
|
11
11
|
/** returns the real object when dealing with shadow UI */
|
|
12
12
|
static getObject(obj: Object3D): Object3D {
|
|
13
13
|
const shadowOwner = obj[$shadowDomOwner];
|
|
14
|
-
if(shadowOwner)
|
|
15
|
-
|
|
16
|
-
if((shadowOwner as IComponent).isComponent === true) obj = (shadowOwner as IComponent).gameObject;
|
|
14
|
+
if (shadowOwner) {
|
|
15
|
+
if ((shadowOwner as IComponent).isComponent === true) obj = (shadowOwner as IComponent).gameObject;
|
|
17
16
|
else obj = shadowOwner;
|
|
18
17
|
}
|
|
19
18
|
return obj;
|
|
@@ -29,8 +28,8 @@ export class UIRaycastUtils {
|
|
|
29
28
|
|
|
30
29
|
obj = this.getObject(obj);
|
|
31
30
|
|
|
32
|
-
if(!obj.visible) return false;
|
|
33
|
-
|
|
31
|
+
if (!obj.visible) return false;
|
|
32
|
+
|
|
34
33
|
const canvasGroup = this.tryFindCanvasGroup(obj);
|
|
35
34
|
if (canvasGroup?.isCanvasGroup === true) {
|
|
36
35
|
if (out) out.canvasGroup = canvasGroup as ICanvasGroup;
|
|
@@ -38,7 +37,7 @@ export class UIRaycastUtils {
|
|
|
38
37
|
if (canvasGroup.interactable === false) return false;
|
|
39
38
|
}
|
|
40
39
|
// handle Graphic Raycast target
|
|
41
|
-
const graphic
|
|
40
|
+
const graphic: IGraphic | undefined = foreachComponent(obj, c => {
|
|
42
41
|
if ((c as unknown as IGraphic).isGraphic === true) return c;
|
|
43
42
|
return undefined;
|
|
44
43
|
}, false);
|
|
@@ -58,8 +57,10 @@ export class UIRaycastUtils {
|
|
|
58
57
|
if (!obj) return null;
|
|
59
58
|
// test for canvas groups
|
|
60
59
|
const res = foreachComponent(obj, c => {
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
if (c.activeAndEnabled) {
|
|
61
|
+
const gr = c as unknown as ICanvasGroup;
|
|
62
|
+
if (gr.blocksRaycasts !== undefined && gr.interactable !== undefined) return gr;
|
|
63
|
+
}
|
|
63
64
|
return undefined;
|
|
64
65
|
}, false);
|
|
65
66
|
if (res !== undefined) return res;
|
|
@@ -45,7 +45,10 @@ export class LookAt extends Behaviour implements UsdzBehaviour {
|
|
|
45
45
|
let target: Object3D | null | undefined = this.target;
|
|
46
46
|
if (!target) {
|
|
47
47
|
target = this.context.mainCamera;
|
|
48
|
-
if (isDevEnvironment()
|
|
48
|
+
if (isDevEnvironment() && !this["__did_warn"]) {
|
|
49
|
+
this["__did_warn"] = true;
|
|
50
|
+
console.debug(`[LookAt] No target set on ${this.name}, using main camera as target.`);
|
|
51
|
+
}
|
|
49
52
|
}
|
|
50
53
|
if (!target) return;
|
|
51
54
|
|
|
@@ -29,7 +29,8 @@ onStart(ctx => {
|
|
|
29
29
|
* - Add the ClickThrough component to any GameObject in your scene.
|
|
30
30
|
* - Alternatively, add the `clickthrough` attribute to the `<needle-engine>` HTML element (e.g. `<needle-engine clickthrough></needle-engine>`).
|
|
31
31
|
*
|
|
32
|
-
*
|
|
32
|
+
* - Example https://stackblitz.com/~/github.com/needle-engine/sample-3d-over-html
|
|
33
|
+
*
|
|
33
34
|
* @category Web
|
|
34
35
|
* @group Components
|
|
35
36
|
* @component
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { Ray } from "three";
|
|
2
|
+
|
|
3
|
+
import { Gizmos } from "../../engine/engine_gizmos.js";
|
|
1
4
|
import { serializable } from "../../engine/engine_serialization_decorator.js";
|
|
2
5
|
import { getTempVector } from "../../engine/engine_three_utils.js";
|
|
6
|
+
import { getParam } from "../../engine/engine_utils.js";
|
|
3
7
|
import { Behaviour } from "../Component.js";
|
|
4
8
|
|
|
9
|
+
|
|
10
|
+
const debug = getParam("debugcursor");
|
|
11
|
+
|
|
5
12
|
/**
|
|
6
13
|
* The CursorFollow component makes the object follow the cursor (or touch) position on screen.
|
|
7
14
|
*
|
|
@@ -34,9 +41,15 @@ export class CursorFollow extends Behaviour {
|
|
|
34
41
|
@serializable()
|
|
35
42
|
keepDistance: boolean = true;
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
/**
|
|
45
|
+
* If true, the object will attempt to snap to the surface of other objects in the scene using a raycast.
|
|
46
|
+
*/
|
|
47
|
+
@serializable()
|
|
48
|
+
snapToSurface: boolean = false;
|
|
49
|
+
|
|
50
|
+
|
|
38
51
|
private _distance: number = -1;
|
|
39
|
-
updateDistance(force:boolean = false) {
|
|
52
|
+
updateDistance(force: boolean = false) {
|
|
40
53
|
if (!force && (this.keepDistance && this._distance !== -1)) {
|
|
41
54
|
return;
|
|
42
55
|
}
|
|
@@ -48,10 +61,12 @@ export class CursorFollow extends Behaviour {
|
|
|
48
61
|
this._distance = -1;
|
|
49
62
|
}
|
|
50
63
|
|
|
64
|
+
/** @internal */
|
|
51
65
|
onEnable(): void {
|
|
52
66
|
this._distance = -1;
|
|
53
67
|
window.addEventListener('pointermove', this._onPointerMove);
|
|
54
68
|
}
|
|
69
|
+
/** @internal */
|
|
55
70
|
onDisable(): void {
|
|
56
71
|
window.removeEventListener('pointermove', this._onPointerMove);
|
|
57
72
|
}
|
|
@@ -59,8 +74,8 @@ export class CursorFollow extends Behaviour {
|
|
|
59
74
|
private _ndc_x = 0;
|
|
60
75
|
private _ndc_y = 0;
|
|
61
76
|
|
|
62
|
-
private _onPointerMove = (e:PointerEvent) => {
|
|
63
|
-
if(!this.useFullPage) return;
|
|
77
|
+
private _onPointerMove = (e: PointerEvent) => {
|
|
78
|
+
if (!this.useFullPage) return;
|
|
64
79
|
const x = e.clientX;
|
|
65
80
|
const y = e.clientY;
|
|
66
81
|
const domx = this.context.domX;
|
|
@@ -73,7 +88,7 @@ export class CursorFollow extends Behaviour {
|
|
|
73
88
|
|
|
74
89
|
|
|
75
90
|
/** @internal */
|
|
76
|
-
|
|
91
|
+
lateUpdate() {
|
|
77
92
|
// continuously update distance in case camera or object moves
|
|
78
93
|
this.updateDistance();
|
|
79
94
|
|
|
@@ -89,16 +104,42 @@ export class CursorFollow extends Behaviour {
|
|
|
89
104
|
rayDirection.sub(cameraPosition).normalize();
|
|
90
105
|
|
|
91
106
|
// position object at initial distance along the ray
|
|
92
|
-
const newPosition = rayDirection.multiplyScalar(this._distance).add(cameraPosition);
|
|
107
|
+
const newPosition = getTempVector(rayDirection).multiplyScalar(this._distance).add(cameraPosition);
|
|
108
|
+
let _position = newPosition;
|
|
109
|
+
|
|
110
|
+
|
|
93
111
|
if (this.damping > 0) {
|
|
94
112
|
const pos = this.gameObject.worldPosition;
|
|
95
113
|
pos.lerp(newPosition, this.context.time.deltaTime / this.damping);
|
|
96
114
|
this.gameObject.worldPosition = pos;
|
|
115
|
+
_position = pos;
|
|
97
116
|
}
|
|
98
117
|
else {
|
|
99
118
|
this.gameObject.worldPosition = newPosition;
|
|
100
119
|
}
|
|
101
120
|
|
|
121
|
+
|
|
122
|
+
if (this.snapToSurface) {
|
|
123
|
+
ray.origin = _position;
|
|
124
|
+
ray.direction = rayDirection.multiplyScalar(-1);
|
|
125
|
+
const hits = this.context.physics.raycastFromRay(ray);
|
|
126
|
+
if (hits?.length) {
|
|
127
|
+
const hit = hits[0];
|
|
128
|
+
if (this.damping > 0) {
|
|
129
|
+
this.gameObject.worldPosition = _position.lerp(hit.point, this.context.time.deltaTime / this.damping);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.gameObject.worldPosition = hit.point;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if(debug) {
|
|
136
|
+
Gizmos.DrawLine(hit.point, hit.normal!.add(hit.point), 0x00FF00);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
102
141
|
}
|
|
103
142
|
|
|
104
|
-
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ray = new Ray();
|
|
@@ -473,7 +473,7 @@ function tryGetElementsForSelector(index: number): Element | null {
|
|
|
473
473
|
|
|
474
474
|
if (!needsScrollMarkerRefresh) {
|
|
475
475
|
const element = needleScrollMarkerCache[index] || null;
|
|
476
|
-
|
|
476
|
+
return element;
|
|
477
477
|
}
|
|
478
478
|
needsScrollMarkerRefresh = false;
|
|
479
479
|
needleScrollMarkerCache.length = 0;
|