@woosh/meep-engine 2.47.23 → 2.47.26
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/build/meep.cjs +544 -342
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +544 -342
- package/editor/tools/SelectionTool.js +17 -17
- package/editor/view/EditorView.js +4 -5
- package/editor/view/ecs/EntityList.js +0 -2
- package/package.json +1 -1
- package/src/core/geom/3d/topology/samples/sampleFloodFill.js +8 -8
- package/src/core/json/abstractJSONSerializer.js +1 -1
- package/src/engine/ecs/EntityComponentDataset.js +28 -3
- package/src/engine/ecs/EntityManager.js +264 -221
- package/src/engine/ecs/System.d.ts +2 -0
- package/src/engine/ecs/System.js +37 -30
- package/src/engine/ecs/gui/menu/radial/RadialContextMenu.js +0 -4
- package/src/engine/ecs/systems/TimerSystem.js +61 -41
- package/src/engine/ecs/terrain/util/obtainTerrain.js +2 -3
- package/src/engine/graphics/GraphicsEngine.js +4 -3
- package/src/engine/graphics/ecs/camera/Camera.js +37 -0
- package/src/engine/graphics/ecs/camera/filter/setup_filtered_camera_controller.js +78 -0
- package/src/engine/graphics/ecs/camera/pp/NOTES.md +48 -0
- package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +164 -0
- package/src/engine/graphics/ecs/camera/pp/Perfect_Panning.png +0 -0
- package/src/engine/graphics/ecs/camera/pp/Zooming.png +0 -0
- package/src/engine/graphics/ecs/camera/pp/pan_a.png +0 -0
- package/src/engine/graphics/ecs/camera/pp/pan_b.png +0 -0
- package/src/engine/graphics/ecs/mesh/MeshSystem.js +1 -1
- package/src/engine/graphics/make_ray_from_viewport_position.js +28 -0
- package/src/engine/input/devices/PointerDevice.js +32 -0
- package/src/engine/intelligence/behavior/decorator/RepeatBehavior.js +17 -8
- package/src/engine/intelligence/behavior/decorator/RepeatBehaviorSerializationAdapter.js +2 -2
- package/src/engine/intelligence/behavior/primitive/SucceedingBehavior.js +9 -5
|
@@ -8,61 +8,81 @@ import Timer from '../components/Timer.js';
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class TimerSystem extends System {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @type {number}
|
|
14
|
+
*/
|
|
15
|
+
#timeDelta = 0;
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @type {EntityComponentDataset|null}
|
|
19
|
+
*/
|
|
20
|
+
#dataset = null;
|
|
21
|
+
|
|
22
|
+
dependencies = [Timer];
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param {Timer} timer
|
|
28
|
+
* @param {number} entity
|
|
29
|
+
*/
|
|
30
|
+
#visit(timer, entity) {
|
|
31
|
+
if (!timer.active) {
|
|
22
32
|
return;
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let budget = timer.counter + timeDelta;
|
|
31
|
-
const timeout = timer.timeout;
|
|
35
|
+
const timeDelta = this.#timeDelta;
|
|
36
|
+
const dataset = this.#dataset;
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
let budget = timer.counter + timeDelta;
|
|
39
|
+
const timeout = timer.timeout;
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
while (budget > timeout) {
|
|
42
|
+
budget -= timeout;
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
const functions = timer.actions;
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
const action = functions[i];
|
|
46
|
+
const n = functions.length;
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} catch (e) {
|
|
46
|
-
console.error(`entity '${entity}' Timer action[${i}] exception:`, e);
|
|
47
|
-
}
|
|
48
|
+
for (let i = 0; i < n; i++) {
|
|
49
|
+
const action = functions[i];
|
|
48
50
|
|
|
51
|
+
try {
|
|
52
|
+
action();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(`entity '${entity}' Timer action[${i}] exception:`, e);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
if (++timer.ticks > timer.repeat) {
|
|
53
|
-
//already performed too many cycles
|
|
54
|
-
timer.active = false;
|
|
55
|
-
return; //bail out
|
|
56
|
-
}
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
dataset.sendEvent(entity, "timer-timeout", timer);
|
|
60
|
+
if (++timer.ticks > timer.repeat) {
|
|
61
|
+
//already performed too many cycles
|
|
62
|
+
timer.active = false;
|
|
63
|
+
return; //bail out
|
|
62
64
|
}
|
|
63
|
-
timer.counter = budget;
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
if (timeout === 0) {
|
|
67
|
+
// prevent infinite loop
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
timer.counter = budget;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
update(timeDelta) {
|
|
75
|
+
const entityManager = this.entityManager;
|
|
76
|
+
const dataset = entityManager.dataset;
|
|
77
|
+
|
|
78
|
+
if (dataset === null) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.#dataset = dataset;
|
|
83
|
+
this.#timeDelta = timeDelta;
|
|
84
|
+
|
|
85
|
+
dataset.traverseComponents(Timer, this.#visit, this);
|
|
66
86
|
|
|
67
87
|
}
|
|
68
88
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assert } from "../../../../core/assert.js";
|
|
2
2
|
import Terrain from "../ecs/Terrain.js";
|
|
3
|
+
import { noop } from "../../../../core/function/Functions.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
*
|
|
@@ -7,7 +8,7 @@ import Terrain from "../ecs/Terrain.js";
|
|
|
7
8
|
* @param {function(terrain:Terrain,entity:number)} [callback]
|
|
8
9
|
* @returns {Terrain|null}
|
|
9
10
|
*/
|
|
10
|
-
export function obtainTerrain(ecd, callback) {
|
|
11
|
+
export function obtainTerrain(ecd, callback=noop) {
|
|
11
12
|
assert.notEqual(ecd, null, 'ecd is null');
|
|
12
13
|
assert.notEqual(ecd, undefined, 'ecd is undefined');
|
|
13
14
|
|
|
@@ -15,9 +16,7 @@ export function obtainTerrain(ecd, callback) {
|
|
|
15
16
|
|
|
16
17
|
const terrain = object.component;
|
|
17
18
|
|
|
18
|
-
if (terrain !== null && typeof callback === "function") {
|
|
19
19
|
callback(terrain, object.entity);
|
|
20
|
-
}
|
|
21
20
|
|
|
22
21
|
return terrain;
|
|
23
22
|
}
|
|
@@ -359,7 +359,7 @@ GraphicsEngine.prototype.start = function () {
|
|
|
359
359
|
/**
|
|
360
360
|
* @see https://registry.khronos.org/webgl/specs/latest/1.0/#5.2
|
|
361
361
|
*/
|
|
362
|
-
powerPreference:"high-performance"
|
|
362
|
+
powerPreference: "high-performance"
|
|
363
363
|
};
|
|
364
364
|
|
|
365
365
|
const webGLRenderer = this.renderer = new WebGLRenderer(rendererParameters);
|
|
@@ -490,8 +490,9 @@ GraphicsEngine.prototype.normalizeViewportPoint = function (input, result) {
|
|
|
490
490
|
|
|
491
491
|
const viewportSize = this.viewport.size;
|
|
492
492
|
|
|
493
|
-
|
|
494
|
-
const
|
|
493
|
+
// shift by pixel center
|
|
494
|
+
const _x = input.x + 0.5;
|
|
495
|
+
const _y = input.y + 0.5;
|
|
495
496
|
|
|
496
497
|
result.x = (_x / viewportSize.x) * 2 - 1;
|
|
497
498
|
result.y = -(_y / viewportSize.y) * 2 + 1;
|
|
@@ -14,6 +14,7 @@ import { frustum_from_camera } from "./frustum_from_camera.js";
|
|
|
14
14
|
import { invertQuaternionOrientation } from "./InvertQuaternionOrientation.js";
|
|
15
15
|
import { v3_distance_above_plane } from "../../../../core/geom/v3_distance_above_plane.js";
|
|
16
16
|
import { ProjectionType } from "./ProjectionType.js";
|
|
17
|
+
import { computePlaneRayIntersection } from "../../../../core/geom/Plane.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* @class
|
|
@@ -222,6 +223,42 @@ export class Camera {
|
|
|
222
223
|
return true;
|
|
223
224
|
}
|
|
224
225
|
|
|
226
|
+
/**
|
|
227
|
+
*
|
|
228
|
+
* @param {Vector3} out
|
|
229
|
+
* @param {number} origin_x
|
|
230
|
+
* @param {number} origin_y
|
|
231
|
+
* @param {number} origin_z
|
|
232
|
+
* @param {number} direction_x
|
|
233
|
+
* @param {number} direction_y
|
|
234
|
+
* @param {number} direction_z
|
|
235
|
+
* @param {number} plane_index
|
|
236
|
+
*/
|
|
237
|
+
rayPlaneIntersection(
|
|
238
|
+
out,
|
|
239
|
+
origin_x, origin_y, origin_z,
|
|
240
|
+
direction_x, direction_y, direction_z,
|
|
241
|
+
plane_index
|
|
242
|
+
) {
|
|
243
|
+
|
|
244
|
+
assert.isNonNegativeInteger(plane_index, 'plane_index');
|
|
245
|
+
assert.lessThanOrEqual(plane_index, 5, `plane_index must be <= 5, was ${plane_index}`)
|
|
246
|
+
|
|
247
|
+
frustum_from_camera(this.object, scratch_frustum);
|
|
248
|
+
|
|
249
|
+
const plane = scratch_frustum.planes[plane_index];
|
|
250
|
+
|
|
251
|
+
assert.defined(plane, 'plane');
|
|
252
|
+
|
|
253
|
+
computePlaneRayIntersection(
|
|
254
|
+
out,
|
|
255
|
+
origin_x, origin_y, origin_z,
|
|
256
|
+
direction_x, direction_y, direction_z,
|
|
257
|
+
plane.normal.x, plane.normal.y, plane.normal.z,
|
|
258
|
+
plane.constant
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
225
262
|
/**
|
|
226
263
|
*
|
|
227
264
|
* @param {number} x
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { assert } from "../../../../../core/assert.js";
|
|
2
|
+
import TopDownCameraController from "../topdown/TopDownCameraController.js";
|
|
3
|
+
import EntityBuilder from "../../../../ecs/EntityBuilder.js";
|
|
4
|
+
import { BehaviorComponent } from "../../../../intelligence/behavior/ecs/BehaviorComponent.js";
|
|
5
|
+
import { RepeatBehavior } from "../../../../intelligence/behavior/decorator/RepeatBehavior.js";
|
|
6
|
+
import { ActionBehavior } from "../../../../intelligence/behavior/primitive/ActionBehavior.js";
|
|
7
|
+
import { SerializationMetadata } from "../../../../ecs/components/SerializationMetadata.js";
|
|
8
|
+
import { clamp01 } from "../../../../../core/math/clamp01.js";
|
|
9
|
+
import { lerp } from "../../../../../core/math/lerp.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {TopDownCameraController} target
|
|
14
|
+
* @param {EntityComponentDataset} ecd
|
|
15
|
+
* @param {number} [responsiveness] higher value results in sharper movement when following
|
|
16
|
+
* @param {number} [inertia]
|
|
17
|
+
* @returns {{controller:TopDownCameraController, entity:EntityBuilder}}
|
|
18
|
+
*/
|
|
19
|
+
export function setup_filtered_camera_controller({
|
|
20
|
+
target,
|
|
21
|
+
ecd,
|
|
22
|
+
responsiveness = 10,
|
|
23
|
+
inertia = 0.1
|
|
24
|
+
}) {
|
|
25
|
+
|
|
26
|
+
assert.defined(target, 'target');
|
|
27
|
+
assert.defined(ecd, 'ecd');
|
|
28
|
+
assert.isNumber(responsiveness, 'responsiveness');
|
|
29
|
+
|
|
30
|
+
const result = new TopDownCameraController();
|
|
31
|
+
result.copy(target);
|
|
32
|
+
|
|
33
|
+
const remembered_value = new TopDownCameraController();
|
|
34
|
+
remembered_value.copy(result);
|
|
35
|
+
|
|
36
|
+
const entityBuilder = new EntityBuilder();
|
|
37
|
+
|
|
38
|
+
entityBuilder
|
|
39
|
+
.add(BehaviorComponent.fromOne(
|
|
40
|
+
RepeatBehavior.from(
|
|
41
|
+
new ActionBehavior(time_delta => {
|
|
42
|
+
if (!remembered_value.equals(target)) {
|
|
43
|
+
|
|
44
|
+
// external change detected, override filtered result
|
|
45
|
+
result.copy(target);
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
} else {
|
|
49
|
+
|
|
50
|
+
const d = clamp01(1 - Math.exp(-responsiveness * time_delta));
|
|
51
|
+
|
|
52
|
+
target.distance = lerp(target.distance, result.distance, d);
|
|
53
|
+
|
|
54
|
+
target.pitch = result.pitch;
|
|
55
|
+
target.yaw = result.yaw;
|
|
56
|
+
target.roll = result.roll;
|
|
57
|
+
|
|
58
|
+
target.distanceMin = lerp(target.distanceMin, result.distanceMin, d);
|
|
59
|
+
target.distanceMax = lerp(target.distanceMax, result.distanceMax, d);
|
|
60
|
+
|
|
61
|
+
target.target.copy(result.target);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// remember current value
|
|
66
|
+
remembered_value.copy(target);
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
))
|
|
70
|
+
.add(SerializationMetadata.Transient)
|
|
71
|
+
.build(ecd);
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
controller: result,
|
|
76
|
+
entity: entityBuilder
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Perfect panning implementation, based on [this](https://prideout.net/blog/perfect_panning/). Below is a copy of the original article
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Perfect Panning
|
|
6
|
+
===============
|
|
7
|
+
|
|
8
|
+
This is a quick note about _through the lens_ camera control. The user drags the mouse around in their viewport, which causes the camera to slide around within the plane that is parallel to the near and far clipping planes.
|
|
9
|
+
|
|
10
|
+
This is super easy to implement, but it’s somewhat tricky if you want the user to feel like they’re grabbing something in the scene – especially if you consider the distortion caused by perspective projection.
|
|
11
|
+
|
|
12
|
+
For example, the following screenshots show a terrain that has been dragged towards the southwest. Since the mountain top appears to move faster than the coastline, how do you make the 3D scene “stick” to the mouse cursor?
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
To solve this problem the right way, it helps to leverage a raycaster, even if your actual rendering is done with OpenGL. I also find it helpful to pretend that the mouse cursor lives within the near or far planes of the viewing frustum.
|
|
19
|
+
|
|
20
|
+
So, if you cast a ray from the camera’s position to the cursor’s position, you’ll hit the point in the scene that needs to be stable during the pan. In the following diagram, we’ve panned the camera from **A** to **B** such that point **C** has remained stable. The cursor at the start of the pan was projected onto the far plane at **E**, and the cursor’s current position corresponds to point **D**.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
We wish to solve for **B**, given that **A**, **C**, and **E** are known quantities, assuming that your game engine can perform basic raycasts.
|
|
25
|
+
|
|
26
|
+
What’s less obvious is that **D** is also a known quantity (sort of). At any point in time during the drag, you can project the current mouse position onto the current far plane and get **D**. If you stash **A**, **C**, **E** at the start of the pan and never forget them, then you won’t accumulate error while the user pans.
|
|
27
|
+
|
|
28
|
+
The next thing to notice is that **ΔCAB** is similar to **ΔCED**. Applying the law of similar triangles yields:
|
|
29
|
+
|
|
30
|
+
|A-B| / |A-C| == |E-D| / |E-C|
|
|
31
|
+
|
|
32
|
+
This allows you to solve for **|A-B|**:
|
|
33
|
+
|
|
34
|
+
|A-B| = |A-C| * |E-D| / |E-C|
|
|
35
|
+
|
|
36
|
+
Thus, we have a “perfect panning” algorithm:
|
|
37
|
+
|
|
38
|
+
After every mouse-move event, translate the mouse-down camera position **A** in the direction of **(E-D)** by the distance **|A-B|**, as computed above.
|
|
39
|
+
|
|
40
|
+
It’s even easier to implement perfect zooming, when you wish to zoom in at the mouse cursor while keeping the scene’s geometry stable at the point. Simply move the camera along the ray that goes from the camera’s position to the cursor’s position. It might also be desirable to adjust the speed of the zoom according to the distance between the camera and the nearest point in the scene that the ray hits. Here’s a diagram just for completeness:
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
* * *
|
|
45
|
+
|
|
46
|
+
For complete C code implementing the techniques described in this post, look for “map mode” in [par\_camera\_control.h](https://github.com/prideout/par/blob/master/par_camera_control.h), one of my single-file libraries on GitHub.
|
|
47
|
+
|
|
48
|
+
[](https://prideout.net)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import Vector3 from "../../../../../core/geom/Vector3.js";
|
|
2
|
+
import { obtainTerrain } from "../../../../ecs/terrain/util/obtainTerrain.js";
|
|
3
|
+
import { SurfacePoint3 } from "../../../../../core/geom/3d/SurfacePoint3.js";
|
|
4
|
+
import { make_ray_from_viewport_position } from "../../../make_ray_from_viewport_position.js";
|
|
5
|
+
import { CameraSystem } from "../CameraSystem.js";
|
|
6
|
+
import Vector2 from "../../../../../core/geom/Vector2.js";
|
|
7
|
+
import { Transform } from "../../../../ecs/transform/Transform.js";
|
|
8
|
+
|
|
9
|
+
const NEAR_PLANE_INDEX = 5;
|
|
10
|
+
const FAR_PLANE_INDEX = 4;
|
|
11
|
+
|
|
12
|
+
export class PerfectPanner {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Point in world space that was under our cursor when we started panning
|
|
16
|
+
* @type {Vector3}
|
|
17
|
+
*/
|
|
18
|
+
#grab_world_reference = new Vector3();
|
|
19
|
+
#grab_near_projection = new Vector3();
|
|
20
|
+
#grab_far_projection = new Vector3();
|
|
21
|
+
/**
|
|
22
|
+
* Camera position at the start
|
|
23
|
+
* @type {Vector3}
|
|
24
|
+
*/
|
|
25
|
+
#grab_eye_position = new Vector3();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @type {Engine|null}
|
|
30
|
+
*/
|
|
31
|
+
#engine = null;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @type {Camera|null}
|
|
37
|
+
*/
|
|
38
|
+
#camera = null;
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @type {Transform|null}
|
|
42
|
+
*/
|
|
43
|
+
#camera_transform = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* Translation relative to starting point
|
|
48
|
+
* @type {Vector3}
|
|
49
|
+
*/
|
|
50
|
+
#translation = new Vector3();
|
|
51
|
+
|
|
52
|
+
get engine() {
|
|
53
|
+
return this.#engine;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param {Engine} v
|
|
59
|
+
*/
|
|
60
|
+
set engine(v) {
|
|
61
|
+
this.#engine = v;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @returns {Vector3}
|
|
66
|
+
*/
|
|
67
|
+
get translation() {
|
|
68
|
+
return this.#translation;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {Vector3} out
|
|
74
|
+
* @param {number} viewport_x
|
|
75
|
+
* @param {number} viewport_y
|
|
76
|
+
* @param {number} plane
|
|
77
|
+
*/
|
|
78
|
+
#projection_viewport_onto_plane(out, viewport_x, viewport_y, plane) {
|
|
79
|
+
const engine = this.#engine;
|
|
80
|
+
|
|
81
|
+
const ray = make_ray_from_viewport_position(engine, new Vector2(viewport_x, viewport_y));
|
|
82
|
+
|
|
83
|
+
this.#camera.rayPlaneIntersection(
|
|
84
|
+
out,
|
|
85
|
+
ray.origin.x, ray.origin.y, ray.origin.z,
|
|
86
|
+
ray.direction.x, ray.direction.y, ray.direction.z,
|
|
87
|
+
plane
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
*
|
|
93
|
+
* @param {Vector2} screen_position
|
|
94
|
+
*/
|
|
95
|
+
update(screen_position) {
|
|
96
|
+
|
|
97
|
+
const C = this.#grab_world_reference;
|
|
98
|
+
const A = this.#grab_eye_position;
|
|
99
|
+
const E = this.#grab_far_projection;
|
|
100
|
+
|
|
101
|
+
//
|
|
102
|
+
const AC = new Vector3();
|
|
103
|
+
AC.subVectors(A, C);
|
|
104
|
+
|
|
105
|
+
const EC = new Vector3();
|
|
106
|
+
EC.subVectors(E, C);
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
const D = new Vector3();
|
|
110
|
+
|
|
111
|
+
this.#projection_viewport_onto_plane(D, screen_position.x, screen_position.y, FAR_PLANE_INDEX);
|
|
112
|
+
|
|
113
|
+
const translation = new Vector3();
|
|
114
|
+
translation.subVectors(E, D);
|
|
115
|
+
translation.multiplyScalar(AC.length() / EC.length());
|
|
116
|
+
|
|
117
|
+
this.#translation.copy(translation);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
start(screen_position) {
|
|
121
|
+
|
|
122
|
+
// obtain reference point
|
|
123
|
+
|
|
124
|
+
const engine = this.#engine;
|
|
125
|
+
|
|
126
|
+
const ecd = engine.entityManager.dataset;
|
|
127
|
+
|
|
128
|
+
if (ecd === null) {
|
|
129
|
+
throw new Error('No dataset');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// bind camera
|
|
133
|
+
const camera = CameraSystem.getFirstActiveCamera(ecd);
|
|
134
|
+
this.#camera = camera.component;
|
|
135
|
+
this.#camera_transform = ecd.getComponent(camera.entity, Transform);
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
this.#projection_viewport_onto_plane(this.#grab_near_projection, screen_position.x, screen_position.y, NEAR_PLANE_INDEX);
|
|
139
|
+
this.#projection_viewport_onto_plane(this.#grab_far_projection, screen_position.x, screen_position.y, FAR_PLANE_INDEX);
|
|
140
|
+
|
|
141
|
+
const ray = make_ray_from_viewport_position(engine, screen_position);
|
|
142
|
+
|
|
143
|
+
const terrain = obtainTerrain(ecd);
|
|
144
|
+
|
|
145
|
+
const hit = new SurfacePoint3();
|
|
146
|
+
|
|
147
|
+
if (terrain.raycastFirstSync(hit,
|
|
148
|
+
ray.origin.x, ray.origin.y, ray.origin.z,
|
|
149
|
+
ray.direction.x, ray.direction.y, ray.direction.z
|
|
150
|
+
)) {
|
|
151
|
+
// got terrain hit
|
|
152
|
+
this.#grab_world_reference.copy(hit.position);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.#grab_eye_position.copy(this.#camera_transform.position);
|
|
156
|
+
|
|
157
|
+
this.#translation.set(0, 0, 0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
end() {
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -424,7 +424,7 @@ export class MeshSystem extends System {
|
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
|
|
427
|
+
dataset.getComponentAsync(entity, Transform, component.internalApplyTransform, component);
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
update(timeDelta) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Vector2 from "../../core/geom/Vector2.js";
|
|
2
|
+
import Vector3 from "../../core/geom/Vector3.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {Engine} engine
|
|
7
|
+
* @param {Vector2} [position] if not specified, current pointer position will be used
|
|
8
|
+
* @returns {{origin:Vector3, direction:Vector3}}
|
|
9
|
+
*/
|
|
10
|
+
export function make_ray_from_viewport_position(engine, position) {
|
|
11
|
+
|
|
12
|
+
let _p = position;
|
|
13
|
+
|
|
14
|
+
if (position === undefined) {
|
|
15
|
+
_p = engine.devices.pointer.position;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ndc = new Vector2();
|
|
19
|
+
|
|
20
|
+
engine.graphics.normalizeViewportPoint(_p, ndc);
|
|
21
|
+
|
|
22
|
+
const origin = new Vector3();
|
|
23
|
+
const direction = new Vector3();
|
|
24
|
+
|
|
25
|
+
engine.graphics.viewportProjectionRay(ndc.x, ndc.y, origin, direction);
|
|
26
|
+
|
|
27
|
+
return { origin, direction };
|
|
28
|
+
}
|
|
@@ -8,6 +8,7 @@ import { assert } from "../../../core/assert.js";
|
|
|
8
8
|
import { MouseEvents } from "./events/MouseEvents.js";
|
|
9
9
|
import { TouchEvents } from "./events/TouchEvents.js";
|
|
10
10
|
import { sign } from "../../../core/math/sign.js";
|
|
11
|
+
import { InputDeviceSwitch } from "./InputDeviceSwitch.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
|
@@ -371,6 +372,13 @@ export class PointerDevice {
|
|
|
371
372
|
*/
|
|
372
373
|
isRunning = false;
|
|
373
374
|
|
|
375
|
+
/**
|
|
376
|
+
* The MouseEvent.buttons is a 32bit field, which means we can encode up to 32 buttons
|
|
377
|
+
* @readonly
|
|
378
|
+
* @type {InputDeviceSwitch[]}
|
|
379
|
+
*/
|
|
380
|
+
buttons = new Array(32);
|
|
381
|
+
|
|
374
382
|
/**
|
|
375
383
|
*
|
|
376
384
|
* @param {EventTarget} domElement html element
|
|
@@ -379,6 +387,10 @@ export class PointerDevice {
|
|
|
379
387
|
constructor(domElement) {
|
|
380
388
|
assert.defined(domElement, "domElement");
|
|
381
389
|
|
|
390
|
+
// initialize buttons
|
|
391
|
+
for (let i = 0; i < this.buttons.length; i++) {
|
|
392
|
+
this.buttons[i] = new InputDeviceSwitch();
|
|
393
|
+
}
|
|
382
394
|
|
|
383
395
|
/**
|
|
384
396
|
*
|
|
@@ -420,6 +432,16 @@ export class PointerDevice {
|
|
|
420
432
|
#eventHandlerMouseDown = (event) => {
|
|
421
433
|
this.readPointerPositionFromEvent(this.position, event);
|
|
422
434
|
this.on.down.send2(this.position, event);
|
|
435
|
+
|
|
436
|
+
// update button state and dispatch specific signal
|
|
437
|
+
const button_index = event.button;
|
|
438
|
+
|
|
439
|
+
const button = this.buttons[button_index];
|
|
440
|
+
|
|
441
|
+
if (button !== undefined) {
|
|
442
|
+
button.is_down = true;
|
|
443
|
+
button.down.send0();
|
|
444
|
+
}
|
|
423
445
|
}
|
|
424
446
|
|
|
425
447
|
/**
|
|
@@ -447,6 +469,16 @@ export class PointerDevice {
|
|
|
447
469
|
#eventHandlerGlobalMouseUp = (event) => {
|
|
448
470
|
this.readPointerPositionFromEvent(this.position, event);
|
|
449
471
|
this.#globalUp.send2(this.position, event);
|
|
472
|
+
|
|
473
|
+
// update button state and dispatch specific signal
|
|
474
|
+
const button_index = event.button;
|
|
475
|
+
|
|
476
|
+
const button = this.buttons[button_index];
|
|
477
|
+
|
|
478
|
+
if (button !== undefined) {
|
|
479
|
+
button.is_down = false;
|
|
480
|
+
button.up.send0();
|
|
481
|
+
}
|
|
450
482
|
}
|
|
451
483
|
|
|
452
484
|
/**
|