@woosh/meep-engine 2.35.9 → 2.35.10
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/engine/graphics/ecs/camera/Camera.d.ts +3 -0
- package/engine/graphics/ecs/camera/Camera.js +39 -1
- package/engine/graphics/ecs/camera/CameraSystem.js +25 -6
- package/engine/graphics/ecs/path/PathDisplaySystem.js +1 -1
- package/engine/graphics/ecs/path/testPathDisplaySystem.js +13 -12
- package/engine/graphics/ecs/path/tube/TubePathStyle.d.ts +2 -0
- package/engine/graphics/ecs/path/tube/TubePathStyle.js +43 -1
- package/engine/graphics/ecs/path/tube/build/TubePathBuilder.js +138 -0
- package/engine/graphics/ecs/path/tube/build/build_geometry_catmullrom.js +32 -0
- package/engine/graphics/ecs/path/tube/build/build_geometry_linear.js +16 -0
- package/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +153 -0
- package/engine/graphics/ecs/path/tube/build/estimatePathLength.js +17 -0
- package/engine/graphics/ecs/path/tube/build/estimatePathViaIterativeIntegral.js +37 -0
- package/engine/graphics/ecs/path/tube/build/makeTubeGeometry.js +156 -0
- package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +10 -83
- package/engine/graphics/micron/render/VirtualGeometryBuilder.js +19 -24
- package/engine/graphics/micron/render/v1/MicronRenderContext.js +139 -0
- package/engine/graphics/micron/render/v1/VGThreeRenderer.js +18 -123
- package/package.json +1 -1
- package/engine/graphics/ecs/path/tube/TubePathBuilder.js +0 -522
|
@@ -26,7 +26,7 @@ export const ProjectionType = {
|
|
|
26
26
|
export class Camera {
|
|
27
27
|
constructor() {
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Whether clipping planes should be automatically calculated or not
|
|
30
30
|
* @type {boolean}
|
|
31
31
|
*/
|
|
32
32
|
this.autoClip = true;
|
|
@@ -56,6 +56,44 @@ export class Camera {
|
|
|
56
56
|
* @type {ObservedBoolean}
|
|
57
57
|
*/
|
|
58
58
|
this.active = new ObservedBoolean(false);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Near clipping plane
|
|
62
|
+
* @type {number}
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
this.__clip_far = 0;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Far clipping plane
|
|
69
|
+
* @type {number}
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
this.__clip_near = 0.1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get clip_far() {
|
|
76
|
+
return this.__clip_far;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
set clip_far(v) {
|
|
80
|
+
this.__clip_far = v;
|
|
81
|
+
|
|
82
|
+
if (this.object !== null) {
|
|
83
|
+
this.object.far = v;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get clip_near() {
|
|
88
|
+
return this.__clip_near;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
set clip_near(v) {
|
|
92
|
+
this.__clip_near = v;
|
|
93
|
+
|
|
94
|
+
if (this.object !== null) {
|
|
95
|
+
this.object.near = v;
|
|
96
|
+
}
|
|
59
97
|
}
|
|
60
98
|
|
|
61
99
|
updateMatrices() {
|
|
@@ -9,7 +9,6 @@ import { Transform } from '../../../ecs/transform/Transform.js';
|
|
|
9
9
|
import {
|
|
10
10
|
Frustum as ThreeFrustum,
|
|
11
11
|
Matrix4,
|
|
12
|
-
Matrix4 as ThreeMatrix4,
|
|
13
12
|
OrthographicCamera as ThreeOrthographicCamera,
|
|
14
13
|
PerspectiveCamera as ThreePerspectiveCamera,
|
|
15
14
|
Vector3 as ThreeVector3,
|
|
@@ -76,16 +75,25 @@ function setAspectRatio(camera, width, height) {
|
|
|
76
75
|
* @returns {PerspectiveCamera| OrthographicCamera}
|
|
77
76
|
*/
|
|
78
77
|
function buildThreeCamera(component) {
|
|
78
|
+
|
|
79
|
+
let result;
|
|
79
80
|
switch (component.projectionType.getValue()) {
|
|
80
81
|
default:
|
|
81
82
|
console.error(`Unsupported camera projection type "${component.projectionType.getValue()}", defaulting to perspective projection`);
|
|
82
83
|
//fallthrough
|
|
83
84
|
case Camera.ProjectionType.Perspective:
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
result = new ThreePerspectiveCamera(component.fov.getValue(), 1, 1, 50);
|
|
86
|
+
break;
|
|
86
87
|
case Camera.ProjectionType.Orthographic:
|
|
87
|
-
|
|
88
|
+
result = new ThreeOrthographicCamera(-10, 10, 20, -20, 1, 1);
|
|
89
|
+
break;
|
|
88
90
|
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
result.far = component.clip_far;
|
|
94
|
+
result.near = component.clip_near;
|
|
95
|
+
|
|
96
|
+
return result;
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
export class CameraSystem extends System {
|
|
@@ -289,8 +297,19 @@ export class CameraSystem extends System {
|
|
|
289
297
|
* @param {Camera} c
|
|
290
298
|
*/
|
|
291
299
|
function visitCameraEntity(c) {
|
|
292
|
-
if (c.
|
|
300
|
+
if (c.object !== null) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!c.active.getValue()) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (c.autoClip) {
|
|
293
309
|
autoSetClippingPlanes(c, layers);
|
|
310
|
+
} else {
|
|
311
|
+
c.object.far = c.clip_far;
|
|
312
|
+
c.object.near = c.clip_near;
|
|
294
313
|
}
|
|
295
314
|
}
|
|
296
315
|
|
|
@@ -376,7 +395,7 @@ export class CameraSystem extends System {
|
|
|
376
395
|
}
|
|
377
396
|
}
|
|
378
397
|
|
|
379
|
-
const matrix4 = new
|
|
398
|
+
const matrix4 = new Matrix4();
|
|
380
399
|
|
|
381
400
|
/**
|
|
382
401
|
*
|
|
@@ -10,7 +10,7 @@ import { RibbonXPlugin } from "../../trail/x/RibbonXPlugin.js";
|
|
|
10
10
|
import { RibbonPathBuilder } from "./ribbon/RibbonPathBuilder.js";
|
|
11
11
|
import { PathEvents } from "../../../navigation/ecs/components/PathEvents.js";
|
|
12
12
|
import { assert } from "../../../../core/assert.js";
|
|
13
|
-
import { TubePathBuilder } from "./tube/TubePathBuilder.js";
|
|
13
|
+
import { TubePathBuilder } from "./tube/build/TubePathBuilder.js";
|
|
14
14
|
|
|
15
15
|
const builders = {
|
|
16
16
|
[PathDisplayType.None]: function (style, path, result) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { EngineHarness } from "../../../EngineHarness.js";
|
|
2
|
-
import RenderSystem from "../../../ecs/systems/RenderSystem.js";
|
|
3
2
|
import { makeEngineOptionsModel } from "../../../../../model/game/options/makeEngineOptionsModel.js";
|
|
4
3
|
import Path from "../../../navigation/ecs/components/Path.js";
|
|
5
4
|
import Vector3 from "../../../../core/geom/Vector3.js";
|
|
@@ -68,6 +67,7 @@ import { TubePathStyle } from "./tube/TubePathStyle.js";
|
|
|
68
67
|
import { TubeMaterialType } from "./tube/TubeMaterialType.js";
|
|
69
68
|
import { MatcapMaterialDefinition } from "./tube/MatcapMaterialDefinition.js";
|
|
70
69
|
import { TextureAssetLoader } from "../../../asset/loaders/texture/TextureAssetLoader.js";
|
|
70
|
+
import { ShadedGeometrySystem } from "../mesh-v2/ShadedGeometrySystem.js";
|
|
71
71
|
|
|
72
72
|
const engineHarness = new EngineHarness();
|
|
73
73
|
|
|
@@ -156,7 +156,8 @@ function makeConfig(engine) {
|
|
|
156
156
|
async function init(harness) {
|
|
157
157
|
const engine = harness.engine;
|
|
158
158
|
|
|
159
|
-
engine.entityManager.addSystem(new RenderSystem(engine.graphics));
|
|
159
|
+
// engine.entityManager.addSystem(new RenderSystem(engine.graphics));
|
|
160
|
+
engine.entityManager.addSystem(new ShadedGeometrySystem(engine));
|
|
160
161
|
|
|
161
162
|
makeEngineOptionsModel(engine.options, engine);
|
|
162
163
|
|
|
@@ -323,8 +324,8 @@ function main(engine) {
|
|
|
323
324
|
EngineHarness.buildBasics({ engine });
|
|
324
325
|
|
|
325
326
|
const _p = new Path();
|
|
326
|
-
|
|
327
|
-
_p.interpolation = InterpolationType.Linear;
|
|
327
|
+
_p.interpolation = InterpolationType.CatmullRom;
|
|
328
|
+
// _p.interpolation = InterpolationType.Linear;
|
|
328
329
|
|
|
329
330
|
// _p.setPositionsFromVectorArray(Path.smoothPath([
|
|
330
331
|
// new Vector3(3, 1, 1),
|
|
@@ -352,14 +353,14 @@ function main(engine) {
|
|
|
352
353
|
|
|
353
354
|
const pathDisplay = new PathDisplay();
|
|
354
355
|
pathDisplay.specs.push(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
356
|
+
PathDisplaySpec.from(
|
|
357
|
+
PathDisplayType.Ribbon,
|
|
358
|
+
trailPathStyle
|
|
359
|
+
),
|
|
360
|
+
PathDisplaySpec.from(
|
|
361
|
+
PathDisplayType.Entity,
|
|
362
|
+
sample_style_entity()
|
|
363
|
+
),
|
|
363
364
|
PathDisplaySpec.from(
|
|
364
365
|
PathDisplayType.Tube,
|
|
365
366
|
sample_style_tube()
|
|
@@ -5,8 +5,15 @@ import { MatcapMaterialDefinition } from "./MatcapMaterialDefinition.js";
|
|
|
5
5
|
|
|
6
6
|
export class TubePathStyle {
|
|
7
7
|
constructor() {
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @type {Color}
|
|
11
|
+
*/
|
|
8
12
|
this.color = new Color(1, 1, 1);
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @type {TubeMaterialType}
|
|
16
|
+
*/
|
|
10
17
|
this.material_type = TubeMaterialType.Unknown;
|
|
11
18
|
|
|
12
19
|
/**
|
|
@@ -15,13 +22,43 @@ export class TubePathStyle {
|
|
|
15
22
|
*/
|
|
16
23
|
this.material = null;
|
|
17
24
|
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @type {number}
|
|
28
|
+
*/
|
|
18
29
|
this.opacity = 1;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
19
35
|
this.width = 1;
|
|
20
36
|
|
|
21
37
|
// TODO expose shading style "flat/smooth"
|
|
22
38
|
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @type {number}
|
|
42
|
+
*/
|
|
23
43
|
this.resolution = 10;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @type {number}
|
|
48
|
+
*/
|
|
24
49
|
this.radial_resolution = 16;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* @type {boolean}
|
|
54
|
+
*/
|
|
55
|
+
this.cast_shadow = false;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @type {boolean}
|
|
60
|
+
*/
|
|
61
|
+
this.receive_shadow = false;
|
|
25
62
|
}
|
|
26
63
|
|
|
27
64
|
static fromJSON(j) {
|
|
@@ -39,7 +76,9 @@ export class TubePathStyle {
|
|
|
39
76
|
opacity = 1,
|
|
40
77
|
width = 1,
|
|
41
78
|
resolution = 10,
|
|
42
|
-
radial_resolution = 16
|
|
79
|
+
radial_resolution = 16,
|
|
80
|
+
cast_shadow = false,
|
|
81
|
+
receive_shadow = false
|
|
43
82
|
}) {
|
|
44
83
|
assert.enum(material_type, TubeMaterialType, 'material_type');
|
|
45
84
|
|
|
@@ -55,6 +94,9 @@ export class TubePathStyle {
|
|
|
55
94
|
this.resolution = resolution;
|
|
56
95
|
this.radial_resolution = radial_resolution;
|
|
57
96
|
|
|
97
|
+
this.cast_shadow = cast_shadow;
|
|
98
|
+
this.receive_shadow = receive_shadow;
|
|
99
|
+
|
|
58
100
|
switch (material_type) {
|
|
59
101
|
case TubeMaterialType.Matcap:
|
|
60
102
|
this.material = new MatcapMaterialDefinition();
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { BufferGeometry, MeshMatcapMaterial, MeshStandardMaterial, Vector3 } from "three";
|
|
2
|
+
import { InterpolationType } from "../../../../../navigation/ecs/components/InterpolationType.js";
|
|
3
|
+
import EntityBuilder from "../../../../../ecs/EntityBuilder.js";
|
|
4
|
+
import { Transform } from "../../../../../ecs/transform/Transform.js";
|
|
5
|
+
import { TubeMaterialType } from "../TubeMaterialType.js";
|
|
6
|
+
import { GameAssetType } from "../../../../../asset/GameAssetType.js";
|
|
7
|
+
import { build_geometry_linear } from "./build_geometry_linear.js";
|
|
8
|
+
import { build_geometry_catmullrom } from "./build_geometry_catmullrom.js";
|
|
9
|
+
import { ShadedGeometry } from "../../../mesh-v2/ShadedGeometry.js";
|
|
10
|
+
import { ShadedGeometryFlags } from "../../../mesh-v2/ShadedGeometryFlags.js";
|
|
11
|
+
|
|
12
|
+
export class TubePathBuilder {
|
|
13
|
+
constructor() {
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @type {TubePathStyle|null}
|
|
17
|
+
*/
|
|
18
|
+
this.style = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @type {Path|null}
|
|
23
|
+
*/
|
|
24
|
+
this.path = null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @type {MaterialManager|null}
|
|
29
|
+
*/
|
|
30
|
+
this.materials = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @type {AssetManager|null}
|
|
35
|
+
*/
|
|
36
|
+
this.assetManager = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setPath(path) {
|
|
40
|
+
this.path = path;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setStyle(style) {
|
|
44
|
+
this.style = style;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setMaterials(m) {
|
|
48
|
+
this.materials = m;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setAssetManager(assetManager) {
|
|
52
|
+
this.assetManager = assetManager;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
*
|
|
57
|
+
* @param {EntityBuilder[]} destination
|
|
58
|
+
*/
|
|
59
|
+
build(destination) {
|
|
60
|
+
const style = this.style;
|
|
61
|
+
|
|
62
|
+
// generate three.js curve from path
|
|
63
|
+
const path_component = this.path;
|
|
64
|
+
|
|
65
|
+
const three_points = [];
|
|
66
|
+
|
|
67
|
+
const pointCount = path_component.getPointCount();
|
|
68
|
+
|
|
69
|
+
if (pointCount < 2) {
|
|
70
|
+
// no curve possible without at least 2 points
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < pointCount; i++) {
|
|
75
|
+
const v3 = new Vector3();
|
|
76
|
+
|
|
77
|
+
path_component.getPosition(i, v3);
|
|
78
|
+
|
|
79
|
+
three_points.push(v3);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @type {THREE.BufferGeometry}
|
|
85
|
+
*/
|
|
86
|
+
let geometry;
|
|
87
|
+
|
|
88
|
+
if (path_component.interpolation === InterpolationType.Linear) {
|
|
89
|
+
geometry = build_geometry_linear(three_points, pointCount, style);
|
|
90
|
+
} else if (path_component.interpolation === InterpolationType.CatmullRom) {
|
|
91
|
+
geometry = build_geometry_catmullrom(three_points, pointCount, style);
|
|
92
|
+
} else {
|
|
93
|
+
throw new Error(`Unsupported interpolation type '${path_component.interpolation}'`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
let material_def;
|
|
98
|
+
|
|
99
|
+
if (style.material_type === TubeMaterialType.Standard) {
|
|
100
|
+
material_def = new MeshStandardMaterial({
|
|
101
|
+
metalness: style.material.metalness,
|
|
102
|
+
roughness: style.material.roughness
|
|
103
|
+
});
|
|
104
|
+
} else if (style.material_type === TubeMaterialType.Matcap) {
|
|
105
|
+
|
|
106
|
+
material_def = new MeshMatcapMaterial();
|
|
107
|
+
|
|
108
|
+
this.assetManager.get(style.material.texture, GameAssetType.Texture, asset => {
|
|
109
|
+
material_def.matcap = asset.create();
|
|
110
|
+
|
|
111
|
+
material_def.needsUpdate = true;
|
|
112
|
+
}, console.error);
|
|
113
|
+
|
|
114
|
+
} else {
|
|
115
|
+
throw new Error(`Unsupported material type '${style.material_type}'`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
material_def.color.set(style.color.toUint());
|
|
119
|
+
|
|
120
|
+
if (style.opacity < 1) {
|
|
121
|
+
material_def.transparent = true;
|
|
122
|
+
material_def.opacity = style.opacity;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const entityBuilder = new EntityBuilder();
|
|
126
|
+
|
|
127
|
+
entityBuilder.add(new Transform());
|
|
128
|
+
|
|
129
|
+
const shaded_geometry = ShadedGeometry.from(geometry, material_def);
|
|
130
|
+
|
|
131
|
+
shaded_geometry.writeFlag(ShadedGeometryFlags.CastShadow, style.cast_shadow);
|
|
132
|
+
shaded_geometry.writeFlag(ShadedGeometryFlags.ReceiveShadow, style.receive_shadow);
|
|
133
|
+
|
|
134
|
+
entityBuilder.add(shaded_geometry);
|
|
135
|
+
|
|
136
|
+
destination.push(entityBuilder);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CatmullRomCurve3 } from "three";
|
|
2
|
+
import { computeFrenetFrames } from "./computeFrenetFrames.js";
|
|
3
|
+
import { makeTubeGeometry } from "./makeTubeGeometry.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {THREE.Vector3[]} points
|
|
8
|
+
* @param {number} point_count
|
|
9
|
+
* @param {TubePathStyle} style
|
|
10
|
+
* @return {THREE.BufferGeometry}
|
|
11
|
+
*/
|
|
12
|
+
export function build_geometry_catmullrom(points, point_count, style) {
|
|
13
|
+
|
|
14
|
+
const path = new CatmullRomCurve3(points, false, "centripetal");
|
|
15
|
+
|
|
16
|
+
const sample_points = [];
|
|
17
|
+
|
|
18
|
+
// resample curve
|
|
19
|
+
const total_points = Math.ceil(point_count * style.resolution);
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < total_points; i++) {
|
|
22
|
+
const t = i / (total_points - 1);
|
|
23
|
+
|
|
24
|
+
const point = path.getPoint(t);
|
|
25
|
+
|
|
26
|
+
sample_points.push(point);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const frames = computeFrenetFrames(sample_points, false);
|
|
30
|
+
|
|
31
|
+
return makeTubeGeometry(sample_points, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { computeFrenetFrames } from "./computeFrenetFrames.js";
|
|
2
|
+
import { makeTubeGeometry } from "./makeTubeGeometry.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {THREE.Vector3[]} points
|
|
7
|
+
* @param {number} point_count
|
|
8
|
+
* @param {TubePathStyle} style
|
|
9
|
+
* @return {THREE.BufferGeometry}
|
|
10
|
+
*/
|
|
11
|
+
export function build_geometry_linear(points, point_count, style) {
|
|
12
|
+
|
|
13
|
+
const frames = computeFrenetFrames(points, false);
|
|
14
|
+
|
|
15
|
+
return makeTubeGeometry(points, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
|
|
16
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Vector3 } from "three";
|
|
2
|
+
import { Matrix4 } from "three/src/math/Matrix4.js";
|
|
3
|
+
import { max2 } from "../../../../../../core/math/max2.js";
|
|
4
|
+
import { min2 } from "../../../../../../core/math/min2.js";
|
|
5
|
+
import * as MathUtils from "three/src/math/MathUtils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @see https://github.com/mrdoob/three.js/blob/c12c9a166a1369cdd58622fff2aff7e3a84305d7/src/extras/core/Curve.js#L260
|
|
9
|
+
* @param {Vector3[]} points
|
|
10
|
+
* @param {boolean} [closed]
|
|
11
|
+
*/
|
|
12
|
+
export function computeFrenetFrames(points, closed = false) {
|
|
13
|
+
// see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
|
|
14
|
+
|
|
15
|
+
const normal = new Vector3();
|
|
16
|
+
|
|
17
|
+
const tangents = [];
|
|
18
|
+
const normals = [];
|
|
19
|
+
const binormals = [];
|
|
20
|
+
|
|
21
|
+
const vec = new Vector3();
|
|
22
|
+
const mat = new Matrix4();
|
|
23
|
+
|
|
24
|
+
const segments = points.length - 1;
|
|
25
|
+
|
|
26
|
+
const v3_0 = new Vector3();
|
|
27
|
+
const v3_1 = new Vector3();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param {number} i
|
|
32
|
+
* @param {Vector3} destination
|
|
33
|
+
*/
|
|
34
|
+
function getTangent(i, destination) {
|
|
35
|
+
// get points on either side
|
|
36
|
+
const i0 = max2(i - 1, 0);
|
|
37
|
+
const i1 = min2(i + 1, segments);
|
|
38
|
+
|
|
39
|
+
const p0 = points[i0];
|
|
40
|
+
const pR = points[i];
|
|
41
|
+
const p1 = points[i1];
|
|
42
|
+
|
|
43
|
+
// get points along the lines close to the reference
|
|
44
|
+
v3_0.subVectors(p0, pR);
|
|
45
|
+
v3_0.normalize();
|
|
46
|
+
|
|
47
|
+
v3_1.subVectors(p1, pR);
|
|
48
|
+
v3_1.normalize();
|
|
49
|
+
|
|
50
|
+
destination.subVectors(v3_1, v3_0);
|
|
51
|
+
destination.normalize();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// compute the tangent vectors for each segment on the curve
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i <= segments; i++) {
|
|
57
|
+
|
|
58
|
+
const tangent = new Vector3();
|
|
59
|
+
getTangent(i, tangent);
|
|
60
|
+
|
|
61
|
+
tangents[i] = tangent;
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// select an initial normal vector perpendicular to the first tangent vector,
|
|
66
|
+
// and in the direction of the minimum tangent xyz component
|
|
67
|
+
|
|
68
|
+
normals[0] = new Vector3();
|
|
69
|
+
binormals[0] = new Vector3();
|
|
70
|
+
let min = Number.MAX_VALUE;
|
|
71
|
+
const tx = Math.abs(tangents[0].x);
|
|
72
|
+
const ty = Math.abs(tangents[0].y);
|
|
73
|
+
const tz = Math.abs(tangents[0].z);
|
|
74
|
+
|
|
75
|
+
if (tx <= min) {
|
|
76
|
+
|
|
77
|
+
min = tx;
|
|
78
|
+
normal.set(1, 0, 0);
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (ty <= min) {
|
|
83
|
+
|
|
84
|
+
min = ty;
|
|
85
|
+
normal.set(0, 1, 0);
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (tz <= min) {
|
|
90
|
+
|
|
91
|
+
normal.set(0, 0, 1);
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
vec.crossVectors(tangents[0], normal).normalize();
|
|
96
|
+
|
|
97
|
+
normals[0].crossVectors(tangents[0], vec);
|
|
98
|
+
binormals[0].crossVectors(tangents[0], normals[0]);
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
// compute the slowly-varying normal and binormal vectors for each segment on the curve
|
|
102
|
+
|
|
103
|
+
for (let i = 1; i <= segments; i++) {
|
|
104
|
+
|
|
105
|
+
normals[i] = normals[i - 1].clone();
|
|
106
|
+
|
|
107
|
+
binormals[i] = binormals[i - 1].clone();
|
|
108
|
+
|
|
109
|
+
vec.crossVectors(tangents[i - 1], tangents[i]);
|
|
110
|
+
|
|
111
|
+
if (vec.length() > Number.EPSILON) {
|
|
112
|
+
|
|
113
|
+
vec.normalize();
|
|
114
|
+
|
|
115
|
+
const theta = Math.acos(MathUtils.clamp(tangents[i - 1].dot(tangents[i]), -1, 1)); // clamp for floating pt errors
|
|
116
|
+
|
|
117
|
+
normals[i].applyMatrix4(mat.makeRotationAxis(vec, theta));
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
binormals[i].crossVectors(tangents[i], normals[i]);
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
|
|
126
|
+
|
|
127
|
+
if (closed === true) {
|
|
128
|
+
|
|
129
|
+
let theta = Math.acos(MathUtils.clamp(normals[0].dot(normals[segments]), -1, 1));
|
|
130
|
+
theta /= segments;
|
|
131
|
+
|
|
132
|
+
if (tangents[0].dot(vec.crossVectors(normals[0], normals[segments])) > 0) {
|
|
133
|
+
|
|
134
|
+
theta = -theta;
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (let i = 1; i <= segments; i++) {
|
|
139
|
+
|
|
140
|
+
// twist a little...
|
|
141
|
+
normals[i].applyMatrix4(mat.makeRotationAxis(tangents[i], theta * i));
|
|
142
|
+
binormals[i].crossVectors(tangents[i], normals[i]);
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
tangents: tangents,
|
|
150
|
+
normals: normals,
|
|
151
|
+
binormals: binormals
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InterpolationType } from "../../../../../navigation/ecs/components/InterpolationType.js";
|
|
2
|
+
import { estimatePathViaIterativeIntegral } from "./estimatePathViaIterativeIntegral.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {Path} path
|
|
7
|
+
* @returns {number}
|
|
8
|
+
*/
|
|
9
|
+
function estimatePathLength(path) {
|
|
10
|
+
|
|
11
|
+
if (path.interpolation === InterpolationType.Linear) {
|
|
12
|
+
return path.getLength();
|
|
13
|
+
} else {
|
|
14
|
+
return estimatePathViaIterativeIntegral(path, 0.01);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Vector3 } from "three";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* some useful references:
|
|
5
|
+
* https://stackoverflow.com/questions/29438398/cheap-way-of-calculating-cubic-bezier-length
|
|
6
|
+
* https://stackoverflow.com/questions/1147249/connecting-catmull-rom-splines-together-and-calculating-its-length
|
|
7
|
+
* @param {Path} path
|
|
8
|
+
* @param {number} epsilon
|
|
9
|
+
*/
|
|
10
|
+
export function estimatePathViaIterativeIntegral(path, epsilon) {
|
|
11
|
+
let sum = 0;
|
|
12
|
+
|
|
13
|
+
let p0 = new Vector3();
|
|
14
|
+
let p1 = new Vector3();
|
|
15
|
+
|
|
16
|
+
if (!path.sample(p0, 0)) {
|
|
17
|
+
// path has 0 length
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (let p = epsilon; ; i += epsilon) {
|
|
22
|
+
const ended = path.sample(p1, p);
|
|
23
|
+
|
|
24
|
+
if (ended) {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
sum += p0.distanceTo(p1);
|
|
29
|
+
|
|
30
|
+
const swap = p1;
|
|
31
|
+
|
|
32
|
+
p1 = p0;
|
|
33
|
+
p0 = swap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return sum;
|
|
37
|
+
}
|