@geoblocks/cesium-walk 0.2.1 → 0.3.0
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/README.md +7 -3
- package/cesium-walk.js +103 -11
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -92,10 +92,14 @@ viewer.scene.primitives.add(await Model.fromGltfAsync({url, modelMatrix}));
|
|
|
92
92
|
const mesh = createTriangleMeshFromGltf(await (await fetch(url)).arrayBuffer(), modelMatrix);
|
|
93
93
|
|
|
94
94
|
const walkMode = new CesiumWalk(viewer);
|
|
95
|
-
// stand on the highest floor below the knees, climb steps up to 0.4 m, stop at the walls 0.5 m ahead
|
|
96
|
-
|
|
95
|
+
// stand on the highest floor below the knees, climb steps up to 0.4 m, stop at the walls 0.5 m ahead, keep
|
|
96
|
+
// 0.4 m from the walls beside
|
|
97
|
+
walkOn(walkMode, mesh, {eye: 1.6, step: 0.4, reach: 0.5, radius: 0.4});
|
|
97
98
|
```
|
|
98
99
|
|
|
99
100
|
The walker stands on the highest floor below its knees, up the stairs and under the arches, else on the ground
|
|
100
101
|
it had (the terrain). The walls stop it in every direction, from rays cast at its knees, waist and eyes
|
|
101
|
-
(`rayHeights(eye, step)`); the floors and stairs are left to its ground.
|
|
102
|
+
(`rayHeights(eye, step)`); the floors and stairs are left to its ground. With a `radius`, its body at those
|
|
103
|
+
heights is pushed out of any wall nearer than that, the side or the corner of a piece it walks past, which no ray
|
|
104
|
+
ahead meets, and which the camera's near plane would otherwise cut open; a step's riser under the knees is
|
|
105
|
+
climbed, not kept from. Without, the walker is a point, as on the depth probe.
|
package/cesium-walk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Cartesian3, Math as CesiumMath} from "@cesium/core";
|
|
1
|
+
import {Cartesian3, Ellipsoid, FixedFrameTransforms, Math as CesiumMath, Matrix4} from "@cesium/core";
|
|
2
2
|
import Controls from "@geoblocks/cesium-input";
|
|
3
3
|
import {ObstacleProbe, blockedStep, meshGroundHeight, meshObstacleAhead} from "@geoblocks/cesium-obstacles";
|
|
4
4
|
|
|
@@ -12,6 +12,11 @@ const LANDING = 0.15;
|
|
|
12
12
|
const BOB_TIME = 0.15;
|
|
13
13
|
// in meters: nearer its height, the camera stays, so that a walker standing still renders no frame
|
|
14
14
|
const SETTLED = 1e-3;
|
|
15
|
+
// at a concave corner the slide along one wall heads into the other: a step is checked again along its slide, this
|
|
16
|
+
// many times at most, before it is taken
|
|
17
|
+
const SLIDE_PASSES = 3;
|
|
18
|
+
// in a corner, pushed out of one wall, the body may be in the other: it is pushed again, this many times at most
|
|
19
|
+
const CLEAR_PASSES = 3;
|
|
15
20
|
|
|
16
21
|
// the default bindings: W, A, S, D or the arrows to walk, Shift to sprint and Space to jump; on a gamepad, the
|
|
17
22
|
// left stick, at any speed and in any direction, Cross to sprint and Square to jump, as in GTA 5. Keys by
|
|
@@ -34,6 +39,10 @@ const normalScratch = new Cartesian3();
|
|
|
34
39
|
const forwardScratch = new Cartesian3();
|
|
35
40
|
const stepScratch = new Cartesian3();
|
|
36
41
|
const allowedScratch = new Cartesian3();
|
|
42
|
+
const awayScratch = new Cartesian3();
|
|
43
|
+
const frameScratch = new Matrix4();
|
|
44
|
+
const bodyScratch = new Cartesian3();
|
|
45
|
+
const upScratch = new Cartesian3();
|
|
37
46
|
const targetScratch = new Cartesian3();
|
|
38
47
|
const changeScratch = new Cartesian3();
|
|
39
48
|
|
|
@@ -170,6 +179,21 @@ export default class CesiumWalk {
|
|
|
170
179
|
*/
|
|
171
180
|
this.obstacleAhead = (toward, key) => this.obstacleProbe_.ahead(toward, key);
|
|
172
181
|
|
|
182
|
+
/**
|
|
183
|
+
* The walker's body: how far from the walls it keeps, in meters, pushed out of any it walks past, sideways
|
|
184
|
+
* or at a corner, where no ray ahead meets it; 0 by default, a walker without a body.
|
|
185
|
+
* @type {number}
|
|
186
|
+
*/
|
|
187
|
+
this.radius = 0;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The surface nearest the walker's body, within its radius: a point of it, the surface's normal toward the
|
|
191
|
+
* body, and the point of the body it is nearest, if any; asked after each step while collision is on. None by
|
|
192
|
+
* default, as the obstacles in view tell nothing about what is beside the walker; walkOn gives the mesh's.
|
|
193
|
+
* @type {() => {point: Cartesian3, normal: Cartesian3, origin: Cartesian3} | undefined}
|
|
194
|
+
*/
|
|
195
|
+
this.surfaceNear = () => undefined;
|
|
196
|
+
|
|
173
197
|
/**
|
|
174
198
|
* The height of the ground under a position, in meters above the ellipsoid, or undefined when unknown
|
|
175
199
|
* yet. The terrain by default, none without a globe; a model's floors otherwise, for a walker on several levels.
|
|
@@ -322,18 +346,63 @@ export default class CesiumWalk {
|
|
|
322
346
|
}
|
|
323
347
|
|
|
324
348
|
/**
|
|
325
|
-
* Moves the camera a step, stopping at obstacles or sliding along them
|
|
349
|
+
* Moves the camera a step, stopping at obstacles or sliding along them; a slide is checked against the obstacles
|
|
350
|
+
* ahead of it in turn, so that at a concave corner it stops at the second wall rather than going through it.
|
|
326
351
|
* @param {Cartesian3} direction The unit direction of the step, level.
|
|
327
352
|
* @param {number} step The length of the step, negative backward, in meters.
|
|
328
353
|
* @param {string} kind Which steps these are, for the obstacle looked for last time.
|
|
329
354
|
*/
|
|
330
355
|
step_(direction, step, kind) {
|
|
331
356
|
const camera = this.viewer.camera;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
357
|
+
let toward = Cartesian3.multiplyByScalar(direction, Math.sign(step), stepScratch);
|
|
358
|
+
let length = Math.abs(step);
|
|
359
|
+
for (let pass = 0; pass < SLIDE_PASSES && length > 0; pass++) {
|
|
360
|
+
// a slide's look is its own, not the step's: the probe keeps its last look in each direction by its key
|
|
361
|
+
const obstacle = this.collision ? this.obstacleAhead(toward, `${kind}${Math.sign(step)}${pass ? "slide" : ""}`) : undefined;
|
|
362
|
+
const allowed = blockedStep(toward, length, camera.positionWC, obstacle, allowedScratch);
|
|
363
|
+
const same = Cartesian3.equalsEpsilon(allowed.direction, toward, CesiumMath.EPSILON9);
|
|
364
|
+
length = allowed.length;
|
|
365
|
+
toward = Cartesian3.clone(allowed.direction, stepScratch);
|
|
366
|
+
if (same) {
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (length > 0) {
|
|
371
|
+
camera.move(toward, length);
|
|
372
|
+
}
|
|
373
|
+
if (this.collision) {
|
|
374
|
+
this.keepClear_();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Pushes the camera level out of the walls the walker's body is in, each in turn.
|
|
380
|
+
*/
|
|
381
|
+
keepClear_() {
|
|
382
|
+
const camera = this.viewer.camera;
|
|
383
|
+
for (let pass = 0; pass < CLEAR_PASSES; pass++) {
|
|
384
|
+
const surface = this.surfaceNear();
|
|
385
|
+
if (!surface) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const up = this.surfaceNormal_();
|
|
389
|
+
// level, from the surface to the body; a surface straight above or below it is left to the walker's ground
|
|
390
|
+
const away = Cartesian3.subtract(surface.origin, surface.point, awayScratch);
|
|
391
|
+
Cartesian3.subtract(away, Cartesian3.projectVector(away, up, stepScratch), away);
|
|
392
|
+
const distance = Cartesian3.magnitude(away);
|
|
393
|
+
if (distance >= this.radius) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (distance > CesiumMath.EPSILON6) {
|
|
397
|
+
Cartesian3.divideByScalar(away, distance, away);
|
|
398
|
+
} else {
|
|
399
|
+
Cartesian3.subtract(surface.normal, Cartesian3.projectVector(surface.normal, up, stepScratch), away);
|
|
400
|
+
if (Cartesian3.magnitude(away) < CesiumMath.EPSILON6) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
Cartesian3.normalize(away, away);
|
|
404
|
+
}
|
|
405
|
+
camera.move(away, this.radius - distance);
|
|
337
406
|
}
|
|
338
407
|
}
|
|
339
408
|
|
|
@@ -395,17 +464,40 @@ export function rayHeights(eye, step) {
|
|
|
395
464
|
/**
|
|
396
465
|
* Walks a CesiumWalk on a triangle mesh of cesium-obstacles: it stands on the highest floor below its knees, up
|
|
397
466
|
* the stairs, under the arches, else on what it stood on before (the terrain); the walls ahead stop it, from rays
|
|
398
|
-
* cast at the knees, the waist and the eyes, the floors and stairs left to the ground below, in every direction
|
|
467
|
+
* cast at the knees, the waist and the eyes, the floors and stairs left to the ground below, in every direction;
|
|
468
|
+
* with a radius, its body at those heights is kept that far from the walls beside it too, a step's riser under
|
|
469
|
+
* the knees excepted.
|
|
399
470
|
* @param {CesiumWalk} walk
|
|
400
471
|
* @param {import("@geoblocks/cesium-obstacles").TriangleMesh} mesh
|
|
401
|
-
* @param {{eye: number, step: number, reach: number}} options In meters: the eyes above the
|
|
402
|
-
* highest step, how far ahead the walls are looked for.
|
|
472
|
+
* @param {{eye: number, step: number, reach: number, radius?: number}} options In meters: the eyes above the
|
|
473
|
+
* floor, the highest step, how far ahead the walls are looked for, the body's radius (none by default).
|
|
403
474
|
*/
|
|
404
|
-
export function walkOn(walk, mesh, {eye, step, reach}) {
|
|
475
|
+
export function walkOn(walk, mesh, {eye, step, reach, radius = 0}) {
|
|
405
476
|
const camera = walk.viewer.scene.camera;
|
|
406
477
|
const fallback = walk.groundHeight;
|
|
407
478
|
const offsets = rayHeights(eye, step).map(height => new Cartesian3(0, 0, height - eye));
|
|
408
479
|
walk.obstacleAhead = meshObstacleAhead(mesh, () => camera.positionWC, {offsets, reach});
|
|
480
|
+
walk.radius = radius;
|
|
481
|
+
if (radius > 0) {
|
|
482
|
+
walk.surfaceNear = () => {
|
|
483
|
+
const position = camera.positionWC;
|
|
484
|
+
const frame = FixedFrameTransforms.eastNorthUpToFixedFrame(position, Ellipsoid.WGS84, frameScratch);
|
|
485
|
+
const up = Ellipsoid.WGS84.geodeticSurfaceNormal(position, upScratch);
|
|
486
|
+
let nearest;
|
|
487
|
+
for (const [k, offset] of offsets.entries()) {
|
|
488
|
+
const origin = Matrix4.multiplyByPoint(frame, offset, bodyScratch);
|
|
489
|
+
const hit = mesh.nearest(origin, radius, {walls: true});
|
|
490
|
+
// under the knees, a step's riser: climbed, not kept from
|
|
491
|
+
if (!hit || (k === 0 && Cartesian3.dot(Cartesian3.subtract(hit.position, origin, awayScratch), up) < 0)) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (!nearest || hit.distance < nearest.distance) {
|
|
495
|
+
nearest = {point: hit.position, normal: hit.normal, origin: Cartesian3.clone(origin), distance: hit.distance};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return nearest;
|
|
499
|
+
};
|
|
500
|
+
}
|
|
409
501
|
const ground = meshGroundHeight(mesh, {below: eye - step});
|
|
410
502
|
walk.groundHeight = cartographic => ground(cartographic) ?? fallback(cartographic);
|
|
411
503
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geoblocks/cesium-walk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@geoblocks/cesium-input": "^0.1.0",
|
|
25
|
-
"@geoblocks/cesium-obstacles": "^0.
|
|
25
|
+
"@geoblocks/cesium-obstacles": "^0.3.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@cesium/core": ">=0.1.0",
|
|
29
29
|
"@cesium/engine": ">=10"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "18519930ac006985391a708114d9bd746eec4328"
|
|
32
32
|
}
|