@woosh/meep-engine 2.35.9 → 2.35.13
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/core/binary/BinaryBuffer.js +65 -18
- 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/mesh-v2/ShadedGeometrySystem.js +38 -3
- package/engine/graphics/ecs/mesh-v2/sample/prototypeShadedGeometry.js +19 -4
- package/engine/graphics/ecs/path/PathDisplaySystem.js +1 -1
- package/engine/graphics/ecs/path/testPathDisplaySystem.js +19 -12
- package/engine/graphics/ecs/path/tube/TubePathStyle.d.ts +3 -0
- package/engine/graphics/ecs/path/tube/TubePathStyle.js +71 -2
- package/engine/graphics/ecs/path/tube/build/TubePathBuilder.js +160 -0
- package/engine/graphics/ecs/path/tube/build/build_geometry_catmullrom.js +61 -0
- package/engine/graphics/ecs/path/tube/build/build_geometry_linear.js +56 -0
- package/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +165 -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 +161 -0
- package/engine/graphics/micron/build/buildMicronGeometryFromBufferGeometry.js +1 -1
- package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +45 -76
- package/engine/graphics/micron/render/VirtualGeometryBuilder.js +19 -24
- package/engine/graphics/micron/render/instanced/ThreeInstancedAdapter.js +1 -1
- package/engine/graphics/micron/render/v1/MaterialContext.js +1 -11
- package/engine/graphics/micron/render/v1/MaterialVertexSpec.js +55 -2
- package/engine/graphics/micron/render/v1/MicronRenderContext.js +151 -0
- package/engine/graphics/micron/render/v1/VGThreeRenderer.js +24 -117
- package/engine/navigation/ecs/components/Path.js +5 -6
- package/package.json +1 -1
- package/engine/graphics/ecs/path/tube/TubePathBuilder.js +0 -522
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {number} segment_start
|
|
15
|
+
* @param {number} segment_end
|
|
16
|
+
* @returns {BufferGeometry}
|
|
17
|
+
* @param {Path} path_component
|
|
18
|
+
* @param {TubePathStyle} style
|
|
19
|
+
*/
|
|
20
|
+
function make_geometry_segment(path_component, style, segment_start, segment_end) {
|
|
21
|
+
|
|
22
|
+
const interpolation = path_component.interpolation;
|
|
23
|
+
|
|
24
|
+
if (interpolation === InterpolationType.Linear) {
|
|
25
|
+
return build_geometry_linear(path_component, style, segment_start, segment_end);
|
|
26
|
+
} else if (interpolation === InterpolationType.CatmullRom) {
|
|
27
|
+
return build_geometry_catmullrom(path_component, style, segment_start, segment_end);
|
|
28
|
+
} else {
|
|
29
|
+
throw new Error(`Unsupported interpolation type '${interpolation}'`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class TubePathBuilder {
|
|
35
|
+
constructor() {
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @type {TubePathStyle|null}
|
|
39
|
+
*/
|
|
40
|
+
this.style = null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
*
|
|
44
|
+
* @type {Path|null}
|
|
45
|
+
*/
|
|
46
|
+
this.path = null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @type {MaterialManager|null}
|
|
51
|
+
*/
|
|
52
|
+
this.materials = null;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* @type {AssetManager|null}
|
|
57
|
+
*/
|
|
58
|
+
this.assetManager = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setPath(path) {
|
|
62
|
+
this.path = path;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setStyle(style) {
|
|
66
|
+
this.style = style;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setMaterials(m) {
|
|
70
|
+
this.materials = m;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setAssetManager(assetManager) {
|
|
74
|
+
this.assetManager = assetManager;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
*
|
|
79
|
+
* @param {EntityBuilder[]} destination
|
|
80
|
+
*/
|
|
81
|
+
build(destination) {
|
|
82
|
+
const style = this.style;
|
|
83
|
+
|
|
84
|
+
// generate three.js curve from path
|
|
85
|
+
const path_component = this.path;
|
|
86
|
+
|
|
87
|
+
const three_points = [];
|
|
88
|
+
|
|
89
|
+
const pointCount = path_component.getPointCount();
|
|
90
|
+
|
|
91
|
+
if (pointCount < 2) {
|
|
92
|
+
// no curve possible without at least 2 points
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < pointCount; i++) {
|
|
97
|
+
const v3 = new Vector3();
|
|
98
|
+
|
|
99
|
+
path_component.getPosition(i, v3);
|
|
100
|
+
|
|
101
|
+
three_points.push(v3);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
let material_def;
|
|
106
|
+
|
|
107
|
+
if (style.material_type === TubeMaterialType.Standard) {
|
|
108
|
+
material_def = new MeshStandardMaterial({
|
|
109
|
+
metalness: style.material.metalness,
|
|
110
|
+
roughness: style.material.roughness
|
|
111
|
+
});
|
|
112
|
+
} else if (style.material_type === TubeMaterialType.Matcap) {
|
|
113
|
+
|
|
114
|
+
material_def = new MeshMatcapMaterial();
|
|
115
|
+
|
|
116
|
+
this.assetManager.get(style.material.texture, GameAssetType.Texture, asset => {
|
|
117
|
+
material_def.matcap = asset.create();
|
|
118
|
+
|
|
119
|
+
material_def.needsUpdate = true;
|
|
120
|
+
}, console.error);
|
|
121
|
+
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error(`Unsupported material type '${style.material_type}'`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
material_def.color.set(style.color.toUint());
|
|
127
|
+
material_def.wireframe = true;
|
|
128
|
+
|
|
129
|
+
if (style.opacity < 1) {
|
|
130
|
+
material_def.transparent = true;
|
|
131
|
+
material_def.opacity = style.opacity;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const segment_count = style.path_mask.length / 2;
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < segment_count; i++) {
|
|
137
|
+
const i2 = i * 2;
|
|
138
|
+
|
|
139
|
+
const segment_start = style.path_mask[i2];
|
|
140
|
+
const segment_end = style.path_mask[i2 + 1];
|
|
141
|
+
|
|
142
|
+
const geometry = make_geometry_segment(path_component, style, segment_start, segment_end);
|
|
143
|
+
|
|
144
|
+
const entityBuilder = new EntityBuilder();
|
|
145
|
+
|
|
146
|
+
entityBuilder.add(new Transform());
|
|
147
|
+
|
|
148
|
+
const shaded_geometry = ShadedGeometry.from(geometry, material_def);
|
|
149
|
+
|
|
150
|
+
shaded_geometry.writeFlag(ShadedGeometryFlags.CastShadow, style.cast_shadow);
|
|
151
|
+
shaded_geometry.writeFlag(ShadedGeometryFlags.ReceiveShadow, style.receive_shadow);
|
|
152
|
+
|
|
153
|
+
entityBuilder.add(shaded_geometry);
|
|
154
|
+
|
|
155
|
+
destination.push(entityBuilder);
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { computeFrenetFrames } from "./computeFrenetFrames.js";
|
|
2
|
+
import { makeTubeGeometry } from "./makeTubeGeometry.js";
|
|
3
|
+
import Vector3 from "../../../../../../core/geom/Vector3.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {Path} path
|
|
8
|
+
* @param {TubePathStyle} style
|
|
9
|
+
* @param {number} segment_start
|
|
10
|
+
* @param {number} segment_end
|
|
11
|
+
* @return {THREE.BufferGeometry}
|
|
12
|
+
*/
|
|
13
|
+
export function build_geometry_catmullrom(path, style, segment_start, segment_end) {
|
|
14
|
+
|
|
15
|
+
const point_count = path.getPointCount();
|
|
16
|
+
|
|
17
|
+
// resample curve
|
|
18
|
+
const total_points = Math.ceil(point_count * style.resolution);
|
|
19
|
+
|
|
20
|
+
const path_length = path.length;
|
|
21
|
+
|
|
22
|
+
const point = new Vector3();
|
|
23
|
+
|
|
24
|
+
const points_f32 = [];
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
let added_points = 0;
|
|
28
|
+
let i;
|
|
29
|
+
|
|
30
|
+
// initial segment
|
|
31
|
+
path.sample_catmull_rom(point, segment_start * path_length);
|
|
32
|
+
|
|
33
|
+
point.writeToArray(points_f32, added_points * 3);
|
|
34
|
+
|
|
35
|
+
added_points++;
|
|
36
|
+
|
|
37
|
+
const max_i = segment_end * (total_points - 1);
|
|
38
|
+
for (i = Math.ceil(segment_start * total_points + 0.0001); i < max_i; i++) {
|
|
39
|
+
const t = i / (total_points - 1);
|
|
40
|
+
|
|
41
|
+
path.sample_catmull_rom(point, path_length * t);
|
|
42
|
+
|
|
43
|
+
point.writeToArray(points_f32, added_points * 3);
|
|
44
|
+
|
|
45
|
+
added_points++
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (((i - 1) / (total_points - 1)) - segment_end > 0.0001) {
|
|
49
|
+
// end
|
|
50
|
+
|
|
51
|
+
path.sample_catmull_rom(point, segment_end * path_length);
|
|
52
|
+
|
|
53
|
+
point.writeToArray(points_f32, added_points * 3);
|
|
54
|
+
|
|
55
|
+
added_points++;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const frames = computeFrenetFrames(points_f32, false);
|
|
59
|
+
|
|
60
|
+
return makeTubeGeometry(points_f32, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
|
|
61
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { computeFrenetFrames } from "./computeFrenetFrames.js";
|
|
2
|
+
import { makeTubeGeometry } from "./makeTubeGeometry.js";
|
|
3
|
+
import Vector3 from "../../../../../../core/geom/Vector3.js";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const scratch_array_0 = [];
|
|
7
|
+
const scratch_array_1 = [];
|
|
8
|
+
|
|
9
|
+
const scratch_v3 = new Vector3();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {Path} path
|
|
14
|
+
* @param {TubePathStyle} style
|
|
15
|
+
* @param {number} segment_start
|
|
16
|
+
* @param {number} segment_end
|
|
17
|
+
* @return {THREE.BufferGeometry}
|
|
18
|
+
*/
|
|
19
|
+
export function build_geometry_linear(path, style, segment_start, segment_end) {
|
|
20
|
+
const points = [];
|
|
21
|
+
|
|
22
|
+
const path_length = path.length;
|
|
23
|
+
|
|
24
|
+
path.find_index_and_normalized_distance(scratch_array_0, segment_start * path_length);
|
|
25
|
+
path.find_index_and_normalized_distance(scratch_array_1, segment_end * path_length);
|
|
26
|
+
|
|
27
|
+
let points_added = 0;
|
|
28
|
+
|
|
29
|
+
if (scratch_array_0[1] !== 0) {
|
|
30
|
+
path.sample_linear(scratch_v3, segment_start * path_length);
|
|
31
|
+
|
|
32
|
+
scratch_v3.writeToArray(points, points_added * 3);
|
|
33
|
+
points_added++;
|
|
34
|
+
|
|
35
|
+
// offset first sampled point, we're already past it
|
|
36
|
+
scratch_array_0[0]++;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const max_i = scratch_array_1[0];
|
|
40
|
+
for (let i = scratch_array_0[0]; i <= max_i; i++) {
|
|
41
|
+
path.readPositionToArray(i, points, points_added * 3);
|
|
42
|
+
points_added++;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (scratch_array_1[1] !== 0) {
|
|
46
|
+
//add end
|
|
47
|
+
path.sample_linear(scratch_v3, segment_end * path_length);
|
|
48
|
+
|
|
49
|
+
scratch_v3.writeToArray(points, points_added * 3);
|
|
50
|
+
points_added++;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const frames = computeFrenetFrames(points, false);
|
|
54
|
+
|
|
55
|
+
return makeTubeGeometry(points, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
|
|
56
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
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 { clamp } from "../../../../../../core/math/clamp.js";
|
|
6
|
+
import { vec3 } from "gl-matrix";
|
|
7
|
+
import { array_copy } from "../../../../../../core/collection/array/copyArray.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @see https://github.com/mrdoob/three.js/blob/c12c9a166a1369cdd58622fff2aff7e3a84305d7/src/extras/core/Curve.js#L260
|
|
11
|
+
* @param {Float32Array|number[]} points
|
|
12
|
+
* @param {boolean} [closed]
|
|
13
|
+
*/
|
|
14
|
+
export function computeFrenetFrames(points, closed = false) {
|
|
15
|
+
// see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
|
|
16
|
+
|
|
17
|
+
const normal = new Vector3();
|
|
18
|
+
|
|
19
|
+
const tangents = [];
|
|
20
|
+
const normals = [];
|
|
21
|
+
const binormals = [];
|
|
22
|
+
|
|
23
|
+
const vec = new Vector3();
|
|
24
|
+
const mat = new Matrix4();
|
|
25
|
+
|
|
26
|
+
const point_count = points.length / 3;
|
|
27
|
+
const segments = point_count - 1;
|
|
28
|
+
|
|
29
|
+
const v3_0 = [];
|
|
30
|
+
const v3_1 = [];
|
|
31
|
+
|
|
32
|
+
const p0_a = [];
|
|
33
|
+
const pR_a = [];
|
|
34
|
+
const p1_a = [];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param {number} i
|
|
39
|
+
* @param {Vector3} destination
|
|
40
|
+
*/
|
|
41
|
+
function getTangent(i, destination) {
|
|
42
|
+
// get points on either side
|
|
43
|
+
const i0 = max2(i - 1, 0);
|
|
44
|
+
const i1 = min2(i + 1, segments);
|
|
45
|
+
|
|
46
|
+
array_copy(points, i0 * 3, p0_a, 0, 3);
|
|
47
|
+
array_copy(points, i * 3, pR_a, 0, 3);
|
|
48
|
+
array_copy(points, i1 * 3, p1_a, 0, 3);
|
|
49
|
+
|
|
50
|
+
// get points along the lines close to the reference
|
|
51
|
+
|
|
52
|
+
vec3.sub(v3_0, p0_a, pR_a);
|
|
53
|
+
vec3.normalize(v3_0, v3_0);
|
|
54
|
+
|
|
55
|
+
vec3.sub(v3_1, p1_a, pR_a);
|
|
56
|
+
vec3.normalize(v3_1, v3_1);
|
|
57
|
+
|
|
58
|
+
destination.set(
|
|
59
|
+
v3_1[0] - v3_0[0],
|
|
60
|
+
v3_1[1] - v3_0[1],
|
|
61
|
+
v3_1[2] - v3_0[2],
|
|
62
|
+
);
|
|
63
|
+
destination.normalize();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// compute the tangent vectors for each segment on the curve
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i <= segments; i++) {
|
|
69
|
+
|
|
70
|
+
const tangent = new Vector3();
|
|
71
|
+
getTangent(i, tangent);
|
|
72
|
+
|
|
73
|
+
tangents[i] = tangent;
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// select an initial normal vector perpendicular to the first tangent vector,
|
|
78
|
+
// and in the direction of the minimum tangent xyz component
|
|
79
|
+
|
|
80
|
+
normals[0] = new Vector3();
|
|
81
|
+
binormals[0] = new Vector3();
|
|
82
|
+
let min = Number.MAX_VALUE;
|
|
83
|
+
const tx = Math.abs(tangents[0].x);
|
|
84
|
+
const ty = Math.abs(tangents[0].y);
|
|
85
|
+
const tz = Math.abs(tangents[0].z);
|
|
86
|
+
|
|
87
|
+
if (tx <= min) {
|
|
88
|
+
|
|
89
|
+
min = tx;
|
|
90
|
+
normal.set(1, 0, 0);
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (ty <= min) {
|
|
95
|
+
|
|
96
|
+
min = ty;
|
|
97
|
+
normal.set(0, 1, 0);
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (tz <= min) {
|
|
102
|
+
|
|
103
|
+
normal.set(0, 0, 1);
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
vec.crossVectors(tangents[0], normal).normalize();
|
|
108
|
+
|
|
109
|
+
normals[0].crossVectors(tangents[0], vec);
|
|
110
|
+
binormals[0].crossVectors(tangents[0], normals[0]);
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// compute the slowly-varying normal and binormal vectors for each segment on the curve
|
|
114
|
+
|
|
115
|
+
for (let i = 1; i <= segments; i++) {
|
|
116
|
+
|
|
117
|
+
normals[i] = normals[i - 1].clone();
|
|
118
|
+
|
|
119
|
+
binormals[i] = binormals[i - 1].clone();
|
|
120
|
+
|
|
121
|
+
vec.crossVectors(tangents[i - 1], tangents[i]);
|
|
122
|
+
|
|
123
|
+
if (vec.length() > Number.EPSILON) {
|
|
124
|
+
|
|
125
|
+
vec.normalize();
|
|
126
|
+
|
|
127
|
+
const theta = Math.acos(clamp(tangents[i - 1].dot(tangents[i]), -1, 1)); // clamp for floating pt errors
|
|
128
|
+
|
|
129
|
+
normals[i].applyMatrix4(mat.makeRotationAxis(vec, theta));
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
binormals[i].crossVectors(tangents[i], normals[i]);
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
|
|
138
|
+
|
|
139
|
+
if (closed === true) {
|
|
140
|
+
|
|
141
|
+
let theta = Math.acos(clamp(normals[0].dot(normals[segments]), -1, 1));
|
|
142
|
+
theta /= segments;
|
|
143
|
+
|
|
144
|
+
if (tangents[0].dot(vec.crossVectors(normals[0], normals[segments])) > 0) {
|
|
145
|
+
|
|
146
|
+
theta = -theta;
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (let i = 1; i <= segments; i++) {
|
|
151
|
+
|
|
152
|
+
// twist a little...
|
|
153
|
+
normals[i].applyMatrix4(mat.makeRotationAxis(tangents[i], theta * i));
|
|
154
|
+
binormals[i].crossVectors(tangents[i], normals[i]);
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
tangents: tangents,
|
|
162
|
+
normals: normals,
|
|
163
|
+
binormals: binormals
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { BufferGeometry, Float32BufferAttribute, Vector3 } from "three";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @see https://github.com/mrdoob/three.js/blob/master/src/geometries/TubeGeometry.js
|
|
5
|
+
* @param {Float32Array|number[]} points
|
|
6
|
+
* @param {Vector3[]} _normals
|
|
7
|
+
* @param {Vector3[]} binormals
|
|
8
|
+
* @param {number} radius
|
|
9
|
+
* @param {number} radialSegments
|
|
10
|
+
* @param {boolean} [closed]
|
|
11
|
+
* @returns {BufferGeometry}
|
|
12
|
+
*/
|
|
13
|
+
export function makeTubeGeometry(points, _normals, binormals, radius, radialSegments, closed = false) {
|
|
14
|
+
const geometry = new BufferGeometry();
|
|
15
|
+
// helper variables
|
|
16
|
+
|
|
17
|
+
const vertex = new Vector3();
|
|
18
|
+
const normal = new Vector3();
|
|
19
|
+
|
|
20
|
+
// buffer
|
|
21
|
+
|
|
22
|
+
const vertices = [];
|
|
23
|
+
const normals = [];
|
|
24
|
+
const uvs = [];
|
|
25
|
+
const indices = [];
|
|
26
|
+
|
|
27
|
+
const point_count = points.length / 3;
|
|
28
|
+
const tubularSegments = point_count - 1;
|
|
29
|
+
|
|
30
|
+
// create buffer data
|
|
31
|
+
|
|
32
|
+
generateBufferData();
|
|
33
|
+
|
|
34
|
+
// build geometry
|
|
35
|
+
|
|
36
|
+
geometry.setIndex(indices);
|
|
37
|
+
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
|
|
38
|
+
geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3));
|
|
39
|
+
geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
|
|
40
|
+
|
|
41
|
+
// functions
|
|
42
|
+
|
|
43
|
+
function generateBufferData() {
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < tubularSegments; i++) {
|
|
46
|
+
|
|
47
|
+
generateSegment(i);
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// if the geometry is not closed, generate the last row of vertices and normals
|
|
52
|
+
// at the regular position on the given path
|
|
53
|
+
//
|
|
54
|
+
// if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)
|
|
55
|
+
|
|
56
|
+
generateSegment((closed === false) ? tubularSegments : 0);
|
|
57
|
+
|
|
58
|
+
// uvs are generated in a separate function.
|
|
59
|
+
// this makes it easy compute correct values for closed geometries
|
|
60
|
+
|
|
61
|
+
generateUVs();
|
|
62
|
+
|
|
63
|
+
// finally create faces
|
|
64
|
+
|
|
65
|
+
generateIndices();
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function generateSegment(i) {
|
|
70
|
+
|
|
71
|
+
// we use getPointAt to sample evenly distributed points from the given path
|
|
72
|
+
|
|
73
|
+
const i3 = i * 3;
|
|
74
|
+
|
|
75
|
+
const Px = points[i3];
|
|
76
|
+
const Py = points[i3 + 1];
|
|
77
|
+
const Pz = points[i3 + 2];
|
|
78
|
+
|
|
79
|
+
// retrieve corresponding normal and binormal
|
|
80
|
+
|
|
81
|
+
const N = _normals[i];
|
|
82
|
+
const B = binormals[i];
|
|
83
|
+
|
|
84
|
+
// generate normals and vertices for the current segment
|
|
85
|
+
|
|
86
|
+
const angular_step = (Math.PI * 2) / radialSegments;
|
|
87
|
+
|
|
88
|
+
for (let j = 0; j <= radialSegments; j++) {
|
|
89
|
+
const v = j * angular_step;
|
|
90
|
+
|
|
91
|
+
const sin = Math.sin(v);
|
|
92
|
+
const cos = -Math.cos(v);
|
|
93
|
+
|
|
94
|
+
// normal
|
|
95
|
+
|
|
96
|
+
normal.x = (cos * N.x + sin * B.x);
|
|
97
|
+
normal.y = (cos * N.y + sin * B.y);
|
|
98
|
+
normal.z = (cos * N.z + sin * B.z);
|
|
99
|
+
normal.normalize();
|
|
100
|
+
|
|
101
|
+
normals.push(normal.x, normal.y, normal.z);
|
|
102
|
+
|
|
103
|
+
// vertex
|
|
104
|
+
|
|
105
|
+
vertex.x = Px + radius * normal.x;
|
|
106
|
+
vertex.y = Py + radius * normal.y;
|
|
107
|
+
vertex.z = Pz + radius * normal.z;
|
|
108
|
+
|
|
109
|
+
vertices.push(vertex.x, vertex.y, vertex.z);
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function generateIndices() {
|
|
116
|
+
let i = 0, j = 0;
|
|
117
|
+
|
|
118
|
+
for (j = 1; j <= tubularSegments; j++) {
|
|
119
|
+
|
|
120
|
+
for (i = 1; i <= radialSegments; i++) {
|
|
121
|
+
|
|
122
|
+
const s_1 = radialSegments + 1;
|
|
123
|
+
|
|
124
|
+
const a = s_1 * (j - 1) + (i - 1);
|
|
125
|
+
const b = s_1 * j + (i - 1);
|
|
126
|
+
const c = s_1 * j + i;
|
|
127
|
+
const d = s_1 * (j - 1) + i;
|
|
128
|
+
|
|
129
|
+
// faces
|
|
130
|
+
|
|
131
|
+
indices.push(a, b, d);
|
|
132
|
+
indices.push(b, c, d);
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function generateUVs() {
|
|
141
|
+
let i = 0, j = 0;
|
|
142
|
+
|
|
143
|
+
for (i = 0; i <= tubularSegments; i++) {
|
|
144
|
+
|
|
145
|
+
const u = i / tubularSegments;
|
|
146
|
+
|
|
147
|
+
for (j = 0; j <= radialSegments; j++) {
|
|
148
|
+
|
|
149
|
+
const v = j / radialSegments;
|
|
150
|
+
|
|
151
|
+
uvs.push(u, v);
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
return geometry;
|
|
161
|
+
}
|
|
@@ -121,7 +121,7 @@ const PRINT_VERBOSE_STATS = false;
|
|
|
121
121
|
/**
|
|
122
122
|
*
|
|
123
123
|
* @param {THREE.BufferGeometry} input
|
|
124
|
-
* @returns {MicronGeometry}
|
|
124
|
+
* @returns {Promise<MicronGeometry>}
|
|
125
125
|
*/
|
|
126
126
|
export async function buildMicronGeometryFromBufferGeometry(input) {
|
|
127
127
|
const source_geometry = deinterleaveBufferGeometry(input);
|