@needle-tools/engine 4.10.0-next.f5ce0ae → 4.10.1-next.72b915b
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 +12 -3
- package/README.md +2 -1
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-pI8o_eru.min.js → needle-engine.bundle-BMYuDB60.min.js} +118 -118
- package/dist/{needle-engine.bundle-VqrrECGF.umd.cjs → needle-engine.bundle-DTlp4wcy.umd.cjs} +119 -119
- package/dist/{needle-engine.bundle-DSzlnhCs.js → needle-engine.bundle-oFhUhV7o.js} +4731 -4689
- package/dist/needle-engine.js +2 -2
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/engine_context.js +7 -7
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.d.ts +1 -0
- package/lib/engine/webcomponents/needle-engine.loading.js +5 -13
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine-components/Camera.d.ts +1 -1
- package/lib/engine-components/Camera.js +1 -1
- package/lib/engine-components/CharacterController.d.ts +2 -2
- package/lib/engine-components/CharacterController.js +2 -2
- package/lib/engine-components/OrbitControls.d.ts +1 -1
- package/lib/engine-components/OrbitControls.js +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +1 -1
- package/lib/engine-components/SceneSwitcher.js +1 -1
- package/lib/engine-components/timeline/TimelineModels.d.ts +37 -2
- package/lib/engine-components/timeline/TimelineModels.js +6 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/web/HoverAnimation.js +1 -1
- package/lib/engine-components/web/HoverAnimation.js.map +1 -1
- package/lib/engine-components/web/ScrollFollow.d.ts +1 -0
- package/lib/engine-components/web/ScrollFollow.js +1 -0
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/lib/engine-components/web/ViewBox.d.ts +38 -9
- package/lib/engine-components/web/ViewBox.js +185 -61
- package/lib/engine-components/web/ViewBox.js.map +1 -1
- package/lib/engine-components/webxr/WebARSessionRoot.js +1 -0
- package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/engine_context.ts +8 -8
- package/src/engine/webcomponents/needle-engine.loading.ts +5 -13
- package/src/engine-components/Camera.ts +1 -1
- package/src/engine-components/CharacterController.ts +2 -2
- package/src/engine-components/OrbitControls.ts +1 -1
- package/src/engine-components/SceneSwitcher.ts +1 -1
- package/src/engine-components/timeline/TimelineModels.ts +37 -3
- package/src/engine-components/web/HoverAnimation.ts +1 -1
- package/src/engine-components/web/ScrollFollow.ts +1 -0
- package/src/engine-components/web/ViewBox.ts +197 -55
- package/src/engine-components/webxr/WebARSessionRoot.ts +1 -0
|
@@ -66,6 +66,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
|
|
|
66
66
|
private _loadingElement?: HTMLElement;
|
|
67
67
|
private _loadingTextContainer: HTMLElement | null = null;
|
|
68
68
|
private _loadingBar: HTMLElement | null = null;
|
|
69
|
+
private _loadingBarFinishedColor: string | null = null;
|
|
69
70
|
private _messageContainer: HTMLElement | null = null;
|
|
70
71
|
private _loadingElementOptions?: LoadingElementOptions;
|
|
71
72
|
|
|
@@ -181,6 +182,9 @@ export class EngineLoadingView implements ILoadingViewHandler {
|
|
|
181
182
|
const percent = (t * 100).toFixed(0) + "%";
|
|
182
183
|
if (this._loadingBar) {
|
|
183
184
|
this._loadingBar.style.width = t * 100 + "%";
|
|
185
|
+
if(t >= 1 && this._loadingBarFinishedColor) {
|
|
186
|
+
this._loadingBar.style.background = this._loadingBarFinishedColor;
|
|
187
|
+
}
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
if (this._loadingTextContainer)
|
|
@@ -336,21 +340,9 @@ export class EngineLoadingView implements ILoadingViewHandler {
|
|
|
336
340
|
// `linear-gradient(90deg, #204f49 ${getGradientPos(0)}, #0BA398 ${getGradientPos(.3)}, #66A22F ${getGradientPos(.6)}, #D7DB0A ${getGradientPos(1)})`;
|
|
337
341
|
this._loadingBar.style.backgroundAttachment = "fixed";
|
|
338
342
|
this._loadingBar.style.background = "#c4c4c4ab";
|
|
343
|
+
this._loadingBarFinishedColor = "#ddddddab";
|
|
339
344
|
this._loadingBar.style.width = "0%";
|
|
340
345
|
this._loadingBar.style.height = "100%";
|
|
341
|
-
if (hasLicense && this._element) {
|
|
342
|
-
const primaryColor = this._element.getAttribute("primary-color");
|
|
343
|
-
const secondaryColor = this._element.getAttribute("secondary-color");
|
|
344
|
-
if (primaryColor && secondaryColor) {
|
|
345
|
-
this._loadingBar.style.background = `linear-gradient(90deg, ${primaryColor} ${getGradientPos(0)}, ${secondaryColor} ${getGradientPos(1)})`;
|
|
346
|
-
}
|
|
347
|
-
else if (primaryColor) {
|
|
348
|
-
this._loadingBar.style.background = primaryColor;
|
|
349
|
-
}
|
|
350
|
-
else if (secondaryColor) {
|
|
351
|
-
this._loadingBar.style.background = secondaryColor;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
346
|
|
|
355
347
|
// this._loadingTextContainer = document.createElement("div");
|
|
356
348
|
// this._loadingTextContainer.style.display = "flex";
|
|
@@ -36,7 +36,7 @@ const debugscreenpointtoray = getParam("debugscreenpointtoray");
|
|
|
36
36
|
* Supports both perspective and orthographic cameras with various rendering options.
|
|
37
37
|
* Internally, this component uses {@link PerspectiveCamera} and {@link OrthographicCamera} three.js objects.
|
|
38
38
|
*
|
|
39
|
-
* @category Camera
|
|
39
|
+
* @category Camera
|
|
40
40
|
* @group Components
|
|
41
41
|
*/
|
|
42
42
|
export class Camera extends Behaviour implements ICamera {
|
|
@@ -14,7 +14,7 @@ import { Rigidbody } from "./RigidBody.js";
|
|
|
14
14
|
const debug = getParam("debugcharactercontroller");
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @category Camera
|
|
17
|
+
* @category Camera
|
|
18
18
|
* @group Components
|
|
19
19
|
*/
|
|
20
20
|
export class CharacterController extends Behaviour {
|
|
@@ -107,7 +107,7 @@ export class CharacterController extends Behaviour {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
* @category Camera
|
|
110
|
+
* @category Camera
|
|
111
111
|
* @category Interactivity
|
|
112
112
|
* @group Components
|
|
113
113
|
*/
|
|
@@ -63,7 +63,7 @@ declare module 'three/examples/jsm/controls/OrbitControls.js' {
|
|
|
63
63
|
/** The OrbitControls component is used to control a camera using the [OrbitControls from three.js](https://threejs.org/docs/#examples/en/controls/OrbitControls) library.
|
|
64
64
|
* The three OrbitControls object can be accessed via the `controls` property.
|
|
65
65
|
* The object being controlled by the OrbitControls (usually the camera) can be accessed via the `controllerObject` property.
|
|
66
|
-
* @category Camera
|
|
66
|
+
* @category Camera
|
|
67
67
|
* @group Components
|
|
68
68
|
*/
|
|
69
69
|
export class OrbitControls extends Behaviour implements ICameraController {
|
|
@@ -80,7 +80,7 @@ export interface ISceneEventListener {
|
|
|
80
80
|
* Available scenes are defined in the `scenes` array.
|
|
81
81
|
* Loaded scenes will be added to the SceneSwitcher's GameObject as a child and removed when another scene is loaded by the same SceneSwitcher.
|
|
82
82
|
* Live Examples
|
|
83
|
-
* - [Multi Scenes Sample](https://engine.needle.tools/samples/multi-
|
|
83
|
+
* - [Multi Scenes Sample](https://engine.needle.tools/samples/multi-scene-example) (source code available)
|
|
84
84
|
* - [Needle Website](https://needle.tools)
|
|
85
85
|
* - [Songs Of Cultures](https://app.songsofcultures.com)
|
|
86
86
|
*
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { AnimationClip, Object3D, Quaternion, Vector3 } from "three";
|
|
2
|
-
import { Behavior } from "three-mesh-ui";
|
|
3
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @category Animation and Sequencing
|
|
5
|
+
*/
|
|
4
6
|
export declare type TimelineAssetModel = {
|
|
5
7
|
name: string;
|
|
6
8
|
tracks: TrackModel[];
|
|
7
9
|
}
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @category Animation and Sequencing
|
|
13
|
+
*/
|
|
9
14
|
export enum TrackType {
|
|
10
15
|
Activation = "ActivationTrack",
|
|
11
16
|
Animation = "AnimationTrack",
|
|
@@ -15,6 +20,9 @@ export enum TrackType {
|
|
|
15
20
|
Signal = "SignalTrack",
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @category Animation and Sequencing
|
|
25
|
+
*/
|
|
18
26
|
export enum ClipExtrapolation {
|
|
19
27
|
None = 0,
|
|
20
28
|
Hold = 1,
|
|
@@ -23,6 +31,9 @@ export enum ClipExtrapolation {
|
|
|
23
31
|
Continue = 4
|
|
24
32
|
};
|
|
25
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @category Animation and Sequencing
|
|
36
|
+
*/
|
|
26
37
|
export declare type TrackModel = {
|
|
27
38
|
name: string;
|
|
28
39
|
type: TrackType;
|
|
@@ -37,11 +48,17 @@ export declare type TrackModel = {
|
|
|
37
48
|
declare type Vec3 = { x: number, y: number, z: number };
|
|
38
49
|
declare type Quat = { x: number, y: number, z: number, w: number };
|
|
39
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @category Animation and Sequencing
|
|
53
|
+
*/
|
|
40
54
|
export declare type TrackOffset = {
|
|
41
55
|
position: Vec3 | Vector3;
|
|
42
56
|
rotation: Quat | Quaternion;
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @category Animation and Sequencing
|
|
61
|
+
*/
|
|
45
62
|
export declare type ClipModel = {
|
|
46
63
|
start: number;
|
|
47
64
|
end: number;
|
|
@@ -56,6 +73,9 @@ export declare type ClipModel = {
|
|
|
56
73
|
reversed?: boolean;
|
|
57
74
|
}
|
|
58
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @category Animation and Sequencing
|
|
78
|
+
*/
|
|
59
79
|
export declare type AnimationClipModel = {
|
|
60
80
|
clip: string | number | AnimationClip;
|
|
61
81
|
loop: boolean;
|
|
@@ -65,12 +85,18 @@ export declare type AnimationClipModel = {
|
|
|
65
85
|
rotation?: Quat | Quaternion;
|
|
66
86
|
}
|
|
67
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @category Animation and Sequencing
|
|
90
|
+
*/
|
|
68
91
|
export declare type AudioClipModel = {
|
|
69
92
|
clip: string;
|
|
70
93
|
loop: boolean;
|
|
71
94
|
volume: number;
|
|
72
95
|
}
|
|
73
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @category Animation and Sequencing
|
|
99
|
+
*/
|
|
74
100
|
export declare type ControlClipModel = {
|
|
75
101
|
sourceObject: string | Object3D;
|
|
76
102
|
controlActivation: boolean;
|
|
@@ -81,11 +107,16 @@ export enum MarkerType {
|
|
|
81
107
|
Signal = "SignalEmitter",
|
|
82
108
|
}
|
|
83
109
|
|
|
84
|
-
|
|
110
|
+
/**
|
|
111
|
+
* @category Animation and Sequencing
|
|
112
|
+
*/export declare class MarkerModel {
|
|
85
113
|
type: MarkerType;
|
|
86
114
|
time: number;
|
|
87
115
|
}
|
|
88
116
|
|
|
117
|
+
/**
|
|
118
|
+
* @category Animation and Sequencing
|
|
119
|
+
*/
|
|
89
120
|
export declare class SignalMarkerModel extends MarkerModel {
|
|
90
121
|
retroActive: boolean;
|
|
91
122
|
emitOnce: boolean;
|
|
@@ -99,5 +130,8 @@ export declare class SignalMarkerModel extends MarkerModel {
|
|
|
99
130
|
* ```html
|
|
100
131
|
* <div data-timeline-marker>...</div>
|
|
101
132
|
* ```
|
|
102
|
-
|
|
133
|
+
*
|
|
134
|
+
* @link [Example Project using ScrollMarker](https://scrollytelling-bike-z23hmxb2gnu5a.needle.run/)
|
|
135
|
+
* @category Animation and Sequencing
|
|
136
|
+
*/
|
|
103
137
|
export type ScrollMarkerModel = MarkerModel & { name: string };
|
|
@@ -58,7 +58,7 @@ export class HoverAnimation extends Behaviour {
|
|
|
58
58
|
start() {
|
|
59
59
|
if (!this.idle) this.idle = AnimationUtils.emptyClip();
|
|
60
60
|
|
|
61
|
-
if (!this.hovered) {
|
|
61
|
+
if (!this.hovered || !(this.hovered instanceof AnimationClip)) {
|
|
62
62
|
this.hovered = AnimationUtils.createScaleClip({
|
|
63
63
|
type: "linear",
|
|
64
64
|
duration: this.duration || 0.1,
|
|
@@ -40,6 +40,7 @@ type ScrollFollowEvent = {
|
|
|
40
40
|
*
|
|
41
41
|
* @link Example at https://scrollytelling-2-z23hmxby7c6x-u30ld.needle.run/
|
|
42
42
|
* @link Template at https://github.com/needle-engine/scrollytelling-template
|
|
43
|
+
* @link [Scrollytelling Bike Demo](https://scrollytelling-bike-z23hmxb2gnu5a.needle.run/)
|
|
43
44
|
*
|
|
44
45
|
* ## How to use with an Animator
|
|
45
46
|
* 1. Create an Animator component and set up a float parameter named "scroll".
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Camera, PerspectiveCamera, Quaternion, Scene, Vector2, Vector3 } from "three";
|
|
1
|
+
import { Camera, Matrix4, PerspectiveCamera, Quaternion, Scene, Vector2, Vector3 } from "three";
|
|
2
2
|
|
|
3
3
|
import { isDevEnvironment } from "../../engine/debug/debug.js";
|
|
4
|
+
import type { Context } from "../../engine/engine_context.js";
|
|
4
5
|
import { Gizmos } from "../../engine/engine_gizmos.js";
|
|
6
|
+
import { Mathf } from "../../engine/engine_math.js";
|
|
5
7
|
import { serializable } from "../../engine/engine_serialization_decorator.js";
|
|
6
|
-
import { getTempVector } from "../../engine/engine_three_utils.js";
|
|
8
|
+
import { getTempQuaternion, getTempVector } from "../../engine/engine_three_utils.js";
|
|
7
9
|
import { registerType } from "../../engine/engine_typestore.js";
|
|
8
10
|
import { getParam } from "../../engine/engine_utils.js";
|
|
9
11
|
import { RGBAColor } from "../../engine/js-extensions/RGBAColor.js";
|
|
@@ -11,24 +13,57 @@ import { Behaviour } from "../Component.js";
|
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
const debugParam = getParam("debugviewbox");
|
|
14
|
-
const disabledGizmoColor = new RGBAColor(.5, .5, .5, .
|
|
16
|
+
const disabledGizmoColor = new RGBAColor(.5, .5, .5, .3);
|
|
17
|
+
const enabledGizmoColor = new RGBAColor(.5, .5, 0, 1);
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* This component can be used to automatically fit a certain box area into the camera view - no matter your screen size or aspect ratio.
|
|
18
21
|
*
|
|
19
22
|
* Add the ViewBox to an object into your scene
|
|
23
|
+
*
|
|
24
|
+
* @link [Example on needle.run](https://viewbox-demo-z23hmxbz2gkayo-z1nyzm6.needle.run/)
|
|
25
|
+
* @link [Scrollytelling Demo using animated Viewbox](https://scrollytelling-bike-z23hmxb2gnu5a.needle.run/)
|
|
26
|
+
* @link [Example on Stackblitz](https://stackblitz.com/edit/needle-engine-view-box-example)
|
|
27
|
+
*
|
|
28
|
+
* @example Add a Viewbox component to an object in your scene
|
|
29
|
+
* ```ts
|
|
30
|
+
const viewBox = new Object3D();
|
|
31
|
+
viewBox.scale.set(0, 0, 0);
|
|
32
|
+
viewBox.addComponent(ViewBox, { debug: true });
|
|
33
|
+
scene.add(viewBox);
|
|
34
|
+
* ```
|
|
35
|
+
|
|
36
|
+
* @category Camera
|
|
37
|
+
* @group Components
|
|
38
|
+
* @component
|
|
20
39
|
*/
|
|
21
40
|
@registerType
|
|
22
41
|
export class ViewBox extends Behaviour {
|
|
23
42
|
|
|
43
|
+
/** All known viewbox instances */
|
|
24
44
|
static readonly instances: ViewBox[] = [];
|
|
25
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The currently active viewbox (the last one that was set active). If you have multiple viewboxes in your scene, only the active one will be used.
|
|
48
|
+
* Note that the last viewbox may be inactive if its component is disabled or disabled in the hierarchy.
|
|
49
|
+
* @returns The active viewbox or null if none is active
|
|
50
|
+
*/
|
|
51
|
+
static get activeInstance(): ViewBox | null {
|
|
52
|
+
if (ViewBox.instances.length === 0) return null;
|
|
53
|
+
return ViewBox.instances[ViewBox.instances.length - 1];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A runner instance is used per context to update the viewbox
|
|
58
|
+
*/
|
|
59
|
+
private static readonly runners: WeakMap<Context, Runner> = new WeakMap();
|
|
60
|
+
|
|
26
61
|
/**
|
|
27
62
|
* The reference field of view is used to calculate the box size. This should usually be the same as your camera's fov.
|
|
28
|
-
* @default
|
|
63
|
+
* @default -1 (meaning it will use the camera fov on the first frame)
|
|
29
64
|
*/
|
|
30
65
|
@serializable()
|
|
31
|
-
referenceFieldOfView: number
|
|
66
|
+
referenceFieldOfView: number = -1;
|
|
32
67
|
|
|
33
68
|
/**
|
|
34
69
|
* Enable debug logs and rendering for this component instance
|
|
@@ -36,23 +71,47 @@ export class ViewBox extends Behaviour {
|
|
|
36
71
|
@serializable()
|
|
37
72
|
debug: boolean = false;
|
|
38
73
|
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set this ViewBox as the active one (if you have multiple in your scene). The last active one will be used.
|
|
77
|
+
* @returns self for chaining
|
|
78
|
+
*/
|
|
79
|
+
setActive(): this {
|
|
80
|
+
this.enabled = true;
|
|
81
|
+
const idx = ViewBox.instances.indexOf(this);
|
|
82
|
+
if (idx !== -1) ViewBox.instances.splice(idx, 1);
|
|
83
|
+
ViewBox.instances.push(this);
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @internal */
|
|
39
88
|
onEnable(): void {
|
|
40
89
|
if (debugParam || this.debug || isDevEnvironment()) console.debug("[ViewBox] Using camera fov:", this.referenceFieldOfView);
|
|
41
90
|
// register instance
|
|
42
91
|
ViewBox.instances.push(this);
|
|
92
|
+
|
|
93
|
+
this.removeUpdateCallback();
|
|
94
|
+
this.context.pre_render_callbacks.push(this.internalUpdate);
|
|
43
95
|
}
|
|
44
96
|
|
|
97
|
+
/** @internal */
|
|
45
98
|
onDisable(): void {
|
|
46
99
|
if (debugParam || this.debug) console.debug("[ViewBox] Disabled");
|
|
47
100
|
// unregister instance
|
|
48
101
|
const idx = ViewBox.instances.indexOf(this);
|
|
49
102
|
if (idx !== -1) ViewBox.instances.splice(idx, 1);
|
|
50
|
-
this.
|
|
103
|
+
this.removeUpdateCallback();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private removeUpdateCallback() {
|
|
107
|
+
// remove prerender callback
|
|
108
|
+
const cbIdx = this.context.pre_render_callbacks.indexOf(this.internalUpdate);
|
|
109
|
+
if (cbIdx !== -1) this.context.pre_render_callbacks.splice(cbIdx, 1);
|
|
51
110
|
}
|
|
52
111
|
|
|
53
|
-
|
|
112
|
+
private internalUpdate = () => {
|
|
54
113
|
if (this.context.isInXR) return;
|
|
55
|
-
if (this.destroyed) return;
|
|
114
|
+
if (this.destroyed || !this.activeAndEnabled) return;
|
|
56
115
|
const isActive = ViewBox.instances[ViewBox.instances.length - 1] === this;
|
|
57
116
|
if (!isActive) {
|
|
58
117
|
if (debugParam || this.debug) {
|
|
@@ -60,40 +119,87 @@ export class ViewBox extends Behaviour {
|
|
|
60
119
|
}
|
|
61
120
|
return;
|
|
62
121
|
}
|
|
63
|
-
if (debugParam || this.debug) Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale,
|
|
122
|
+
if (debugParam || this.debug) Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, enabledGizmoColor, 0, true, this.gameObject.worldQuaternion);
|
|
123
|
+
|
|
64
124
|
|
|
65
125
|
// calculate box size to fit the camera frustrum size at the current position (just scale)
|
|
66
126
|
const camera = this.context.mainCamera;
|
|
67
127
|
if (!camera) return;
|
|
68
128
|
if (!(camera instanceof PerspectiveCamera)) {
|
|
69
129
|
// TODO: support orthographic camera
|
|
130
|
+
if (!this["__warnedOrthographic"]) {
|
|
131
|
+
console.warn("[ViewBox] Only perspective cameras are supported.");
|
|
132
|
+
this["__warnedOrthographic"] = true;
|
|
133
|
+
}
|
|
70
134
|
return;
|
|
71
135
|
}
|
|
72
136
|
|
|
73
|
-
if (this.referenceFieldOfView === undefined) {
|
|
137
|
+
if (this.referenceFieldOfView === undefined || this.referenceFieldOfView === -1) {
|
|
74
138
|
this.referenceFieldOfView = camera.fov;
|
|
139
|
+
console.debug("[ViewBox] No referenceFieldOfView set, using camera fov:", this.referenceFieldOfView);
|
|
75
140
|
}
|
|
76
|
-
|
|
77
141
|
if (this.referenceFieldOfView === undefined || this.referenceFieldOfView <= 0) {
|
|
78
142
|
if (debugParam || this.debug) console.warn("[ViewBox] No valid referenceFieldOfView set, cannot adjust box size:", this.referenceFieldOfView);
|
|
79
143
|
return;
|
|
80
144
|
}
|
|
81
145
|
|
|
82
|
-
|
|
83
|
-
|
|
146
|
+
let runner = ViewBox.runners.get(this.context);
|
|
147
|
+
if (!runner) {
|
|
148
|
+
runner = new Runner();
|
|
149
|
+
ViewBox.runners.set(this.context, runner);
|
|
150
|
+
}
|
|
151
|
+
runner.update(this);
|
|
152
|
+
|
|
153
|
+
// BACKLOG: some code for box scale of an object (different component)
|
|
154
|
+
// this.gameObject.worldScale = getTempVector(width, height, worldscale.z);
|
|
155
|
+
// this.gameObject.scale.multiplyScalar(.98)
|
|
156
|
+
// const minscale = Math.min(width, height);
|
|
157
|
+
// console.log(width, height);
|
|
158
|
+
// this.gameObject.worldScale = getTempVector(scale, scale, scale);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
// #region Runner Impl
|
|
167
|
+
|
|
168
|
+
const projectionMatrixCopy: Matrix4 = new Matrix4();
|
|
169
|
+
const projectionMatrixInverseCopy: Matrix4 = new Matrix4();
|
|
170
|
+
|
|
171
|
+
class Runner {
|
|
172
|
+
|
|
173
|
+
private lastActiveViewBox: ViewBox | null = null;
|
|
174
|
+
private lastViewBoxChangeTime: number = -1;
|
|
175
|
+
private currentX: number = 0;
|
|
176
|
+
private currentY: number = 0;
|
|
177
|
+
private currentZoom: number = 1;
|
|
178
|
+
|
|
179
|
+
update(viewBox: ViewBox) {
|
|
180
|
+
|
|
181
|
+
const context = viewBox.context;
|
|
182
|
+
const camera = viewBox.context.mainCamera;
|
|
183
|
+
if (!(camera instanceof PerspectiveCamera)) return;
|
|
184
|
+
|
|
185
|
+
if (this.lastActiveViewBox !== viewBox) {
|
|
186
|
+
if (this.lastActiveViewBox === null)
|
|
187
|
+
this.lastViewBoxChangeTime = -100; // long ago
|
|
188
|
+
else
|
|
189
|
+
this.lastViewBoxChangeTime = context.time.time;
|
|
190
|
+
this.lastActiveViewBox = viewBox;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const domWidth = context.domWidth;
|
|
194
|
+
const domHeight = context.domHeight;
|
|
84
195
|
|
|
85
|
-
let rectPosX = 0;
|
|
86
|
-
let rectPosY = 0;
|
|
87
196
|
let rectWidth = domWidth;
|
|
88
197
|
let rectHeight = domHeight;
|
|
89
198
|
let diffWidth = 1;
|
|
90
199
|
let diffHeight = 1;
|
|
91
200
|
// use focus rect if available
|
|
92
|
-
const focusRectSize =
|
|
201
|
+
const focusRectSize = context.focusRectSize;
|
|
93
202
|
if (focusRectSize) {
|
|
94
|
-
// console.log(focusRectSize)
|
|
95
|
-
rectPosX = focusRectSize.x;
|
|
96
|
-
rectPosY = focusRectSize.y;
|
|
97
203
|
rectWidth = focusRectSize.width;
|
|
98
204
|
rectHeight = focusRectSize.height;
|
|
99
205
|
diffWidth = domWidth / rectWidth;
|
|
@@ -101,22 +207,29 @@ export class ViewBox extends Behaviour {
|
|
|
101
207
|
}
|
|
102
208
|
|
|
103
209
|
|
|
210
|
+
// Copy the projection matrix and restore values so we can reset it later
|
|
211
|
+
projectionMatrixCopy.copy(camera.projectionMatrix);
|
|
212
|
+
projectionMatrixInverseCopy.copy(camera.projectionMatrixInverse);
|
|
104
213
|
const view = camera.view;
|
|
105
214
|
const zoom = camera.zoom;
|
|
106
215
|
const aspect = camera.aspect;
|
|
107
216
|
const fov = camera.fov;
|
|
217
|
+
// Set values to default so we can calculate the box size correctly
|
|
108
218
|
camera.view = null;
|
|
109
219
|
camera.zoom = 1;
|
|
110
|
-
camera.fov =
|
|
220
|
+
camera.fov = viewBox.referenceFieldOfView;
|
|
111
221
|
camera.updateProjectionMatrix();
|
|
112
222
|
|
|
113
223
|
|
|
114
|
-
const boxPosition =
|
|
115
|
-
const boxScale =
|
|
224
|
+
const boxPosition = viewBox.gameObject.worldPosition;
|
|
225
|
+
const boxScale = viewBox.gameObject.worldScale;
|
|
116
226
|
|
|
117
227
|
const cameraPosition = camera.worldPosition;
|
|
118
228
|
const distance = cameraPosition.distanceTo(boxPosition);
|
|
119
229
|
|
|
230
|
+
const timeSinceViewBoxChanged = context.time.time - this.lastViewBoxChangeTime;
|
|
231
|
+
const duration = 1;
|
|
232
|
+
const interpolationDelta = timeSinceViewBoxChanged / duration;// timeSinceViewBoxChanged < 1 ? .2 : 1;
|
|
120
233
|
|
|
121
234
|
// #region camera fixes
|
|
122
235
|
// If the camera is inside the box, move it out
|
|
@@ -124,13 +237,30 @@ export class ViewBox extends Behaviour {
|
|
|
124
237
|
const direction = getTempVector(cameraPosition).sub(boxPosition);
|
|
125
238
|
if (distance < boxSizeMax) {
|
|
126
239
|
// move camera out of bounds
|
|
127
|
-
if (
|
|
240
|
+
if (viewBox.debug || debugParam) console.warn("[ViewBox] Moving camera out of bounds", distance, "<", boxSizeMax);
|
|
128
241
|
const positionDirection = getTempVector(direction);
|
|
129
242
|
positionDirection.y *= .00000001; // stay on horizontal plane mostly
|
|
130
243
|
positionDirection.normalize();
|
|
131
|
-
const lengthToMove = (boxSizeMax - distance);
|
|
244
|
+
const lengthToMove = (boxSizeMax - distance);
|
|
132
245
|
const newPosition = cameraPosition.add(positionDirection.multiplyScalar(lengthToMove));
|
|
133
|
-
camera.worldPosition = newPosition.lerp(cameraPosition, 1 -
|
|
246
|
+
camera.worldPosition = newPosition.lerp(cameraPosition, 1 - context.time.deltaTime);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if (interpolationDelta < 1) {
|
|
251
|
+
if (interpolationDelta > 0) {
|
|
252
|
+
// TODO: this look at animation can lead to slightly janky shifts currently
|
|
253
|
+
const startRotation = getTempQuaternion(camera.quaternion);
|
|
254
|
+
camera.lookAt(boxPosition);
|
|
255
|
+
const newRotation = getTempQuaternion(camera.quaternion);
|
|
256
|
+
camera.quaternion.copy(startRotation);
|
|
257
|
+
camera.quaternion.slerp(newRotation, Math.pow(interpolationDelta, 3));
|
|
258
|
+
camera.updateMatrixWorld();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
camera.lookAt(boxPosition);
|
|
263
|
+
camera.updateMatrixWorld();
|
|
134
264
|
}
|
|
135
265
|
|
|
136
266
|
// Ensure the camera looks at the ViewBox
|
|
@@ -144,18 +274,25 @@ export class ViewBox extends Behaviour {
|
|
|
144
274
|
// camera.worldQuaternion = rotation;
|
|
145
275
|
// camera.updateMatrixWorld();
|
|
146
276
|
// }
|
|
147
|
-
const boxPositionInCameraSpace = getTempVector(boxPosition);
|
|
148
|
-
|
|
149
|
-
camera
|
|
150
|
-
camera.
|
|
277
|
+
// const boxPositionInCameraSpace = getTempVector(boxPosition);
|
|
278
|
+
|
|
279
|
+
// Ensure the camera looks at the box position
|
|
280
|
+
// camera.worldToLocal(boxPositionInCameraSpace);
|
|
281
|
+
// if (interpolationDelta < 1)
|
|
282
|
+
// {
|
|
283
|
+
// const startRotation = interpolationDelta < 1 ? getTempQuaternion(camera.quaternion) : null;
|
|
284
|
+
// if (startRotation !== null) {
|
|
285
|
+
// camera.quaternion.slerpQuaternions(startRotation, camera.quaternion, interpolationDelta);
|
|
286
|
+
// }
|
|
287
|
+
// }
|
|
151
288
|
|
|
152
289
|
|
|
153
290
|
// #region calculate fit
|
|
154
|
-
const vFOV =
|
|
291
|
+
const vFOV = viewBox.referenceFieldOfView * Math.PI / 180; // convert vertical fov to radians
|
|
155
292
|
const height = 2 * Math.tan(vFOV / 2) * distance; // visible height
|
|
156
293
|
const width = height * camera.aspect; // visible width
|
|
157
294
|
|
|
158
|
-
const projectedBox = this.projectBoxIntoCamera(camera, 1);
|
|
295
|
+
const projectedBox = this.projectBoxIntoCamera(viewBox, camera, 1);
|
|
159
296
|
// return
|
|
160
297
|
const boxWidth = (projectedBox.maxX - projectedBox.minX);
|
|
161
298
|
const boxHeight = (projectedBox.maxY - projectedBox.minY);
|
|
@@ -166,32 +303,38 @@ export class ViewBox extends Behaviour {
|
|
|
166
303
|
width / diffWidth,
|
|
167
304
|
height / diffHeight
|
|
168
305
|
);
|
|
306
|
+
const rectZoom = scale / (height * .5);
|
|
169
307
|
// console.log({ scale, width, height, boxWidth: boxWidth * camera.aspect, boxHeight, diffWidth, diffHeight, aspect: camera.aspect, distance })
|
|
170
308
|
// this.context.focusRectSettings.zoom = 1.39;
|
|
171
309
|
// if (!this.context.focusRect) this.context.setCameraFocusRect(this.context.domElement);
|
|
172
310
|
// return
|
|
173
311
|
const vec = getTempVector(boxPosition);
|
|
174
312
|
vec.project(camera);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
313
|
+
|
|
314
|
+
if (interpolationDelta < 1) {
|
|
315
|
+
this.currentX = Mathf.lerp(this.currentX, vec.x, interpolationDelta);
|
|
316
|
+
this.currentY = Mathf.lerp(this.currentY, vec.y, interpolationDelta);
|
|
317
|
+
this.currentZoom = Mathf.lerp(this.currentZoom, rectZoom, interpolationDelta);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this.currentX = vec.x;
|
|
321
|
+
this.currentY = vec.y;
|
|
322
|
+
this.currentZoom = rectZoom;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Apply new values
|
|
326
|
+
context.focusRectSettings.offsetX = this.currentX;
|
|
327
|
+
context.focusRectSettings.offsetY = this.currentY;
|
|
328
|
+
context.focusRectSettings.zoom = this.currentZoom;
|
|
329
|
+
if (!context.focusRect) context.setCameraFocusRect(context.domElement); // if we don't have a focus rect yet, set it to the dom element
|
|
180
330
|
|
|
181
331
|
// Reset values
|
|
182
332
|
camera.view = view;
|
|
183
333
|
camera.zoom = zoom;
|
|
184
334
|
camera.aspect = aspect;
|
|
185
335
|
camera.fov = fov;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// BACKLOG: some code for box scale of an object (different component)
|
|
190
|
-
// this.gameObject.worldScale = getTempVector(width, height, worldscale.z);
|
|
191
|
-
// this.gameObject.scale.multiplyScalar(.98)
|
|
192
|
-
// const minscale = Math.min(width, height);
|
|
193
|
-
// console.log(width, height);
|
|
194
|
-
// this.gameObject.worldScale = getTempVector(scale, scale, scale);
|
|
336
|
+
camera.projectionMatrix.copy(projectionMatrixCopy);
|
|
337
|
+
camera.projectionMatrixInverse.copy(projectionMatrixInverseCopy);
|
|
195
338
|
}
|
|
196
339
|
|
|
197
340
|
|
|
@@ -206,7 +349,7 @@ export class ViewBox extends Behaviour {
|
|
|
206
349
|
|
|
207
350
|
|
|
208
351
|
|
|
209
|
-
private projectBoxIntoCamera(camera: Camera, _factor: number) {
|
|
352
|
+
private projectBoxIntoCamera(comp: ViewBox, camera: Camera, _factor: number) {
|
|
210
353
|
const factor = .5 * _factor;
|
|
211
354
|
|
|
212
355
|
const corners = [
|
|
@@ -225,7 +368,7 @@ export class ViewBox extends Behaviour {
|
|
|
225
368
|
let maxY = Number.NEGATIVE_INFINITY;
|
|
226
369
|
for (let i = 0; i < corners.length; i++) {
|
|
227
370
|
const c = corners[i];
|
|
228
|
-
c.applyMatrix4(
|
|
371
|
+
c.applyMatrix4(comp.gameObject.matrixWorld);
|
|
229
372
|
c.project(camera);
|
|
230
373
|
if (c.x < minX) minX = c.x;
|
|
231
374
|
if (c.x > maxX) maxX = c.x;
|
|
@@ -237,15 +380,15 @@ export class ViewBox extends Behaviour {
|
|
|
237
380
|
if (!this._projectedBoxElement) {
|
|
238
381
|
this._projectedBoxElement = document.createElement("div");
|
|
239
382
|
}
|
|
240
|
-
if (this._projectedBoxElement.parentElement !==
|
|
241
|
-
|
|
383
|
+
if (this._projectedBoxElement.parentElement !== comp.context.domElement)
|
|
384
|
+
comp.context.domElement.appendChild(this._projectedBoxElement);
|
|
242
385
|
this._projectedBoxElement.style.position = "fixed";
|
|
243
386
|
// dotted but with larger gaps
|
|
244
387
|
this._projectedBoxElement.style.outline = "2px dashed rgba(255,0,0,.5)";
|
|
245
|
-
this._projectedBoxElement.style.left = ((minX * .5 + .5) *
|
|
246
|
-
this._projectedBoxElement.style.top = ((-maxY * .5 + .5) *
|
|
247
|
-
this._projectedBoxElement.style.width = ((maxX - minX) * .5 *
|
|
248
|
-
this._projectedBoxElement.style.height = ((maxY - minY) * .5 *
|
|
388
|
+
this._projectedBoxElement.style.left = ((minX * .5 + .5) * comp.context.domWidth) + "px";
|
|
389
|
+
this._projectedBoxElement.style.top = ((-maxY * .5 + .5) * comp.context.domHeight) + "px";
|
|
390
|
+
this._projectedBoxElement.style.width = ((maxX - minX) * .5 * comp.context.domWidth) + "px";
|
|
391
|
+
this._projectedBoxElement.style.height = ((maxY - minY) * .5 * comp.context.domHeight) + "px";
|
|
249
392
|
this._projectedBoxElement.style.pointerEvents = "none";
|
|
250
393
|
this._projectedBoxElement.style.zIndex = "1000";
|
|
251
394
|
}
|
|
@@ -258,5 +401,4 @@ export class ViewBox extends Behaviour {
|
|
|
258
401
|
|
|
259
402
|
|
|
260
403
|
|
|
261
|
-
|
|
262
|
-
}
|
|
404
|
+
}
|