@geoblocks/cesium-walk 0.2.2 → 0.3.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/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
- walkOn(walkMode, mesh, {eye: 1.6, step: 0.4, reach: 0.5});
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
 
@@ -15,6 +15,8 @@ const SETTLED = 1e-3;
15
15
  // at a concave corner the slide along one wall heads into the other: a step is checked again along its slide, this
16
16
  // many times at most, before it is taken
17
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;
18
20
 
19
21
  // the default bindings: W, A, S, D or the arrows to walk, Shift to sprint and Space to jump; on a gamepad, the
20
22
  // left stick, at any speed and in any direction, Cross to sprint and Square to jump, as in GTA 5. Keys by
@@ -37,6 +39,10 @@ const normalScratch = new Cartesian3();
37
39
  const forwardScratch = new Cartesian3();
38
40
  const stepScratch = new Cartesian3();
39
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();
40
46
  const targetScratch = new Cartesian3();
41
47
  const changeScratch = new Cartesian3();
42
48
 
@@ -173,6 +179,21 @@ export default class CesiumWalk {
173
179
  */
174
180
  this.obstacleAhead = (toward, key) => this.obstacleProbe_.ahead(toward, key);
175
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
+
176
197
  /**
177
198
  * The height of the ground under a position, in meters above the ellipsoid, or undefined when unknown
178
199
  * yet. The terrain by default, none without a globe; a model's floors otherwise, for a walker on several levels.
@@ -349,6 +370,40 @@ export default class CesiumWalk {
349
370
  if (length > 0) {
350
371
  camera.move(toward, length);
351
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);
406
+ }
352
407
  }
353
408
 
354
409
  clampCameraToTerrain_() {
@@ -409,17 +464,40 @@ export function rayHeights(eye, step) {
409
464
  /**
410
465
  * Walks a CesiumWalk on a triangle mesh of cesium-obstacles: it stands on the highest floor below its knees, up
411
466
  * the stairs, under the arches, else on what it stood on before (the terrain); the walls ahead stop it, from rays
412
- * 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.
413
470
  * @param {CesiumWalk} walk
414
471
  * @param {import("@geoblocks/cesium-obstacles").TriangleMesh} mesh
415
- * @param {{eye: number, step: number, reach: number}} options In meters: the eyes above the floor, the
416
- * 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).
417
474
  */
418
- export function walkOn(walk, mesh, {eye, step, reach}) {
475
+ export function walkOn(walk, mesh, {eye, step, reach, radius = 0}) {
419
476
  const camera = walk.viewer.scene.camera;
420
477
  const fallback = walk.groundHeight;
421
478
  const offsets = rayHeights(eye, step).map(height => new Cartesian3(0, 0, height - eye));
422
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
+ }
423
501
  const ground = meshGroundHeight(mesh, {below: eye - step});
424
502
  walk.groundHeight = cartographic => ground(cartographic) ?? fallback(cartographic);
425
503
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoblocks/cesium-walk",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
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.2.2"
25
+ "@geoblocks/cesium-obstacles": "^0.4.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "@cesium/core": ">=0.1.0",
29
29
  "@cesium/engine": ">=10"
30
30
  },
31
- "gitHead": "5c6ee12b4e35f46ff733cf628da105f55121fbf3"
31
+ "gitHead": "c1e407611f437b0da1057ef0373c74ace28ad58a"
32
32
  }