@woosh/meep-engine 2.35.16 → 2.35.17

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.
@@ -22,7 +22,6 @@ import { OrbitingBehavior } from "../../../../../../model/game/util/behavior/Orb
22
22
  import { BehaviorSystem } from "../../../../intelligence/behavior/ecs/BehaviorSystem.js";
23
23
  import { MicronShadedGeometryRenderAdapter } from "../../../micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js";
24
24
  import { MicronRenderContext } from "../../../micron/render/v1/MicronRenderContext.js";
25
- import { randomIntegerBetween } from "../../../../../core/math/random/randomIntegerBetween.js";
26
25
 
27
26
  const eh = new EngineHarness();
28
27
 
@@ -102,8 +101,8 @@ async function main(engine) {
102
101
  // sg.draw_method = DRAW_METHOD_PLAIN;
103
102
  // sg.draw_method = DRAW_METHOD_INSTANCED;
104
103
  // sg.draw_method = 2;
105
- sg.draw_method = randomIntegerBetween(random, 0, 2);
106
- // sg.draw_method = random() > 0.01 ? DRAW_METHOD_INSTANCED : DRAW_METHOD_PLAIN;
104
+ // sg.draw_method = DRAW_METHOD_INSTANCED;
105
+ // sg.draw_method = random() > 0.5 ? DRAW_METHOD_INSTANCED : 2;
107
106
  sg.clearFlag(ShadedGeometryFlags.CastShadow | ShadedGeometryFlags.ReceiveShadow);
108
107
 
109
108
  new EntityBuilder()
@@ -125,7 +125,8 @@ class PathDisplayContext extends SystemEntityContext {
125
125
  try {
126
126
  builder(spec.style, path, this.system, ownedEntities);
127
127
  } catch (e) {
128
- console.error(`Failed to build path of type '${spec.type}': ${e}`);
128
+ console.error(`Failed to build path of type '${spec.type}'`);
129
+ console.error(e);
129
130
  }
130
131
  }
131
132
 
@@ -359,14 +359,14 @@ function main(engine) {
359
359
 
360
360
  const pathDisplay = new PathDisplay();
361
361
  pathDisplay.specs.push(
362
- PathDisplaySpec.from(
363
- PathDisplayType.Ribbon,
364
- trailPathStyle
365
- ),
366
- PathDisplaySpec.from(
367
- PathDisplayType.Entity,
368
- sample_style_entity()
369
- ),
362
+ // PathDisplaySpec.from(
363
+ // PathDisplayType.Ribbon,
364
+ // trailPathStyle
365
+ // ),
366
+ // PathDisplaySpec.from(
367
+ // PathDisplayType.Entity,
368
+ // sample_style_entity()
369
+ // ),
370
370
  PathDisplaySpec.from(
371
371
  PathDisplayType.Tube,
372
372
  sample_style_tube()
@@ -0,0 +1,5 @@
1
+ export enum CapType {
2
+ None,
3
+ Flat,
4
+ Round
5
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @readonly
3
+ * @enum {number}
4
+ */
5
+ export const CapType = {
6
+ None: 0,
7
+ Flat: 1,
8
+ Round: 2
9
+ };
@@ -2,6 +2,7 @@ import {Color} from "../../../../../core/color/Color";
2
2
  import {TubeMaterialType} from "./TubeMaterialType";
3
3
  import {StandardMaterialDefinition} from "./StandardMaterialDefinition";
4
4
  import {MatcapMaterialDefinition} from "./MatcapMaterialDefinition";
5
+ import {CapType} from "./CapType";
5
6
 
6
7
  export class TubePathStyle {
7
8
  public color: Color
@@ -14,4 +15,5 @@ export class TubePathStyle {
14
15
  public cast_shadow: boolean
15
16
  public receive_shadow: boolean
16
17
  public path_mask: number[]
18
+ public cap_type: CapType
17
19
  }
@@ -2,6 +2,7 @@ import { Color } from "../../../../../core/color/Color.js";
2
2
  import { TubeMaterialType } from "./TubeMaterialType.js";
3
3
  import { assert } from "../../../../../core/assert.js";
4
4
  import { MatcapMaterialDefinition } from "./MatcapMaterialDefinition.js";
5
+ import { CapType } from "./CapType.js";
5
6
 
6
7
  const DEFAULT_COLOR = Object.freeze({ r: 1, g: 1, b: 1 });
7
8
 
@@ -71,6 +72,11 @@ export class TubePathStyle {
71
72
  * @type {number[]}
72
73
  */
73
74
  this.path_mask = [0, 1];
75
+
76
+ /**
77
+ * @type {CapType}
78
+ */
79
+ this.cap_type = CapType.Flat;
74
80
  }
75
81
 
76
82
  static fromJSON(j) {
@@ -91,7 +97,8 @@ export class TubePathStyle {
91
97
  radial_resolution = 16,
92
98
  cast_shadow = false,
93
99
  receive_shadow = false,
94
- path_mask = [0, 1]
100
+ path_mask = [0, 1],
101
+ cap_type = CapType.None
95
102
  }) {
96
103
  assert.enum(material_type, TubeMaterialType, 'material_type');
97
104
  assert.isNumber(opacity, 'opacity');
@@ -106,6 +113,7 @@ export class TubePathStyle {
106
113
 
107
114
  assert.greaterThanOrEqual(path_mask.length, 2, 'path_mask length bust be at least 2');
108
115
  assert.ok(path_mask.length % 2 === 0, 'path_mask length bust be multiple of 2');
116
+ assert.enum(cap_type, CapType, 'cap_type');
109
117
 
110
118
  if (typeof color === 'string') {
111
119
  this.color.parse(color);
@@ -123,6 +131,7 @@ export class TubePathStyle {
123
131
  this.receive_shadow = receive_shadow;
124
132
 
125
133
  this.path_mask = path_mask;
134
+ this.cap_type = cap_type;
126
135
 
127
136
  switch (material_type) {
128
137
  case TubeMaterialType.Matcap:
@@ -0,0 +1,90 @@
1
+ import { UintArrayForCount } from "../../../../../../core/collection/array/typed/uint_array_for_count.js";
2
+ import { BufferAttribute, BufferGeometry } from "three";
3
+
4
+ /**
5
+ * Helper class, allowing us to treat geometry build process as a stream without having to create intermediate arrays and performing unnecessary copying
6
+ */
7
+ export class GeometryOutput {
8
+ constructor() {
9
+ /**
10
+ *
11
+ * @type {number}
12
+ */
13
+ this.cursor_vertices = 0;
14
+
15
+ /**
16
+ *
17
+ * @type {number}
18
+ */
19
+ this.cursor_indices = 0;
20
+
21
+ /**
22
+ *
23
+ * @type {Float32Array|number[]|null}
24
+ * @private
25
+ */
26
+ this.__vertex_positions = null;
27
+ /**
28
+ *
29
+ * @type {Float32Array|number[]|null}
30
+ * @private
31
+ */
32
+ this.__vertex_normals = null;
33
+ /**
34
+ *
35
+ * @type {Float32Array|number[]|null}
36
+ * @private
37
+ */
38
+ this.__vertex_uvs = null;
39
+ /**
40
+ *
41
+ * @type {Uint8Array|Uint16Array|Uint32Array|number[]|null}
42
+ * @private
43
+ */
44
+ this.__indices = null;
45
+ }
46
+
47
+ get positions() {
48
+ return this.__vertex_positions;
49
+ }
50
+
51
+ get normals() {
52
+ return this.__vertex_normals;
53
+ }
54
+
55
+ get uvs() {
56
+ return this.__vertex_uvs;
57
+ }
58
+
59
+ get indices() {
60
+ return this.__indices;
61
+ }
62
+
63
+ /**
64
+ *
65
+ * @param {number} vertex_count
66
+ * @param {number} polygon_count
67
+ */
68
+ allocate(vertex_count, polygon_count) {
69
+ this.__vertex_positions = new Float32Array(vertex_count * 3);
70
+ this.__vertex_normals = new Float32Array(vertex_count * 3);
71
+ this.__vertex_uvs = new Float32Array(vertex_count * 2);
72
+
73
+ const INDEX_BUFFER_CLASS = UintArrayForCount(vertex_count);
74
+ this.__indices = new INDEX_BUFFER_CLASS(polygon_count * 3);
75
+ }
76
+
77
+ /**
78
+ * @returns {THREE.BufferGeometry}
79
+ */
80
+ build() {
81
+ const geometry = new BufferGeometry();
82
+
83
+ geometry.setIndex(new BufferAttribute(this.__indices, 1));
84
+ geometry.setAttribute('position', new BufferAttribute(this.__vertex_positions, 3));
85
+ geometry.setAttribute('normal', new BufferAttribute(this.__vertex_normals, 3));
86
+ geometry.setAttribute('uv', new BufferAttribute(this.__vertex_uvs, 2));
87
+
88
+ return geometry;
89
+ }
90
+ }
@@ -36,6 +36,7 @@ export function build_geometry_catmullrom(path, style, segment_start, segment_en
36
36
 
37
37
  const max_i = segment_end * (total_points - 1);
38
38
  for (i = Math.ceil(segment_start * total_points + 0.0001); i < max_i; i++) {
39
+ // TODO initial i value can potentially result in sample being produced before the "segment start"
39
40
  const t = i / (total_points - 1);
40
41
 
41
42
  path.sample_catmull_rom(point, path_length * t);
@@ -45,7 +46,7 @@ export function build_geometry_catmullrom(path, style, segment_start, segment_en
45
46
  added_points++
46
47
  }
47
48
 
48
- if (((i - 1) / (total_points - 1)) - segment_end > 0.0001) {
49
+ if (segment_end - ((i - 1) / (total_points - 1)) > 0.0001) {
49
50
  // end
50
51
 
51
52
  path.sample_catmull_rom(point, segment_end * path_length);
@@ -57,5 +58,5 @@ export function build_geometry_catmullrom(path, style, segment_start, segment_en
57
58
 
58
59
  const frames = computeFrenetFrames(points_f32, false);
59
60
 
60
- return makeTubeGeometry(points_f32, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
61
+ return makeTubeGeometry(points_f32, frames.normals, frames.binormals, style.width, style.radial_resolution, false, style.cap_type);
61
62
  }
@@ -52,5 +52,5 @@ export function build_geometry_linear(path, style, segment_start, segment_end) {
52
52
 
53
53
  const frames = computeFrenetFrames(points, false);
54
54
 
55
- return makeTubeGeometry(points, frames.normals, frames.binormals, style.width, style.radial_resolution, false);
55
+ return makeTubeGeometry(points, frames.normals, frames.binormals, style.width, style.radial_resolution, false, style.cap_type);
56
56
  }
@@ -1,161 +1,423 @@
1
- import { BufferGeometry, Float32BufferAttribute, Vector3 } from "three";
1
+ import { BufferGeometry, Vector3 } from "three";
2
+ import { GeometryOutput } from "./GeometryOutput.js";
3
+ import { CapType } from "../CapType.js";
4
+ import { max2 } from "../../../../../../core/math/max2.js";
5
+
2
6
 
3
7
  /**
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
+ *
9
+ * @param {GeometryOutput} out
10
+ * @param {number} Px
11
+ * @param {number} Py
12
+ * @param {number} Pz
13
+ * @param {Vector3} N normal
14
+ * @param {Vector3} B binormal
15
+ * @param {number} u X component of UV
8
16
  * @param {number} radius
9
- * @param {number} radialSegments
10
- * @param {boolean} [closed]
11
- * @returns {BufferGeometry}
17
+ * @param {number} angular_step
18
+ * @param {number} radial_segments
12
19
  */
13
- export function makeTubeGeometry(points, _normals, binormals, radius, radialSegments, closed = false) {
14
- const geometry = new BufferGeometry();
15
- // helper variables
20
+ function make_ring_vertices(
21
+ out,
22
+ Px, Py, Pz,
23
+ N, B,
24
+ u,
25
+ radius, angular_step, radial_segments
26
+ ) {
27
+ const out_positions = out.positions;
28
+ const out_normals = out.normals;
29
+ const out_uvs = out.uvs;
16
30
 
17
- const vertex = new Vector3();
18
- const normal = new Vector3();
31
+ for (let j = 0; j <= radial_segments; j++) {
32
+ const angle = j * angular_step;
19
33
 
20
- // buffer
34
+ const sin = Math.sin(angle);
35
+ const cos = -Math.cos(angle);
21
36
 
22
- const vertices = [];
23
- const normals = [];
24
- const uvs = [];
25
- const indices = [];
37
+ // normal
26
38
 
27
- const point_count = points.length / 3;
28
- const tubularSegments = point_count - 1;
39
+ const normal_x_r = (cos * N.x + sin * B.x);
40
+ const normal_y_r = (cos * N.y + sin * B.y);
41
+ const normal_z_r = (cos * N.z + sin * B.z);
29
42
 
30
- // create buffer data
43
+ const normal_length = 1 / Math.hypot(normal_x_r, normal_y_r, normal_z_r);
31
44
 
32
- generateBufferData();
45
+ const normal_x = normal_x_r * normal_length;
46
+ const normal_y = normal_y_r * normal_length;
47
+ const normal_z = normal_z_r * normal_length;
33
48
 
34
- // build geometry
49
+ const cv = out.cursor_vertices;
50
+ const cv3 = cv * 3;
35
51
 
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));
52
+ out_normals[cv3] = normal_x;
53
+ out_normals[cv3 + 1] = normal_y;
54
+ out_normals[cv3 + 2] = normal_z;
40
55
 
41
- // functions
56
+ // position
42
57
 
43
- function generateBufferData() {
58
+ const vx = Px + radius * normal_x;
59
+ const vy = Py + radius * normal_y;
60
+ const vz = Pz + radius * normal_z;
44
61
 
45
- for (let i = 0; i < tubularSegments; i++) {
62
+ out_positions[cv3] = vx;
63
+ out_positions[cv3 + 1] = vy;
64
+ out_positions[cv3 + 2] = vz;
46
65
 
47
- generateSegment(i);
66
+ // UV
67
+ const v = j / radial_segments;
68
+
69
+ const cv2 = cv * 2;
70
+
71
+ out_uvs[cv2] = u;
72
+ out_uvs[cv2 + 1] = v;
73
+
74
+ out.cursor_vertices++;
75
+
76
+ }
77
+ }
78
+
79
+ /**
80
+ *
81
+ * @param {GeometryOutput} out
82
+ * @param {number} index_offset
83
+ * @param {number} tubular_segments
84
+ * @param {number} radial_segments
85
+ */
86
+ function make_ring_faces(out, index_offset, tubular_segments, radial_segments) {
87
+ const indices = out.indices;
88
+
89
+ let i = 0, j = 0;
90
+
91
+ for (j = 1; j <= tubular_segments; j++) {
92
+
93
+ for (i = 1; i <= radial_segments; i++) {
94
+
95
+ const s_1 = radial_segments + 1;
96
+
97
+ const a = index_offset + s_1 * (j - 1) + (i - 1);
98
+ const b = index_offset + s_1 * j + (i - 1);
99
+ const c = index_offset + s_1 * j + i;
100
+ const d = index_offset + s_1 * (j - 1) + i;
101
+
102
+ // faces
103
+
104
+ const ci = out.cursor_indices;
105
+
106
+ indices[ci] = a;
107
+ indices[ci + 1] = b;
108
+ indices[ci + 2] = d;
48
109
 
110
+ indices[ci + 3] = b;
111
+ indices[ci + 4] = c;
112
+ indices[ci + 5] = d;
113
+
114
+ out.cursor_indices += 6;
49
115
  }
50
116
 
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)
117
+ }
118
+ }
119
+
120
+ /**
121
+ *
122
+ * @param {number} radial_segments
123
+ * @returns {number}
124
+ */
125
+ function compute_cap_round_segment_count(radial_segments) {
126
+ return max2(3, Math.ceil(radial_segments / 4));
127
+ }
128
+
129
+ /**
130
+ *
131
+ * @param {number} index
132
+ * @param {Float32Array|number[]} in_positions
133
+ * @param {Vector3[]} in_normals
134
+ * @param {Vector3[]} in_binormals
135
+ * @param {GeometryOutput} out
136
+ * @param {number} radius
137
+ * @param {number} radial_segments
138
+ * @param {number} direction
139
+ */
140
+ function make_cap_round(
141
+ out,
142
+ index,
143
+ in_positions, in_normals, in_binormals,
144
+ radius, radial_segments, direction
145
+ ) {
146
+ // how many radial segments will be placed laterally to make up the cap
147
+ const cap_segment_count = compute_cap_round_segment_count(radial_segments);
55
148
 
56
- generateSegment((closed === false) ? tubularSegments : 0);
149
+ const i3 = index * 3;
57
150
 
58
- // uvs are generated in a separate function.
59
- // this makes it easy compute correct values for closed geometries
151
+ const Px = in_positions[i3];
152
+ const Py = in_positions[i3 + 1];
153
+ const Pz = in_positions[i3 + 2];
60
154
 
61
- generateUVs();
155
+ // retrieve corresponding normal and binormal
62
156
 
63
- // finally create faces
157
+ const N = in_normals[index];
158
+ const B = in_binormals[index];
159
+
160
+ const tangent = new Vector3();
161
+ tangent.crossVectors(N, B);
162
+ tangent.normalize();
163
+
164
+ const normal = direction > 0 ? N : N.clone().negate();
165
+
166
+ const angular_step_i = (Math.PI / 2) / cap_segment_count;
167
+ const angular_step_j = (Math.PI * 2) / radial_segments;
168
+
169
+ const uv_u = index / (in_positions.length / 3 - 1);
170
+ let i, j;
64
171
 
65
- generateIndices();
172
+ const index_offset = out.cursor_vertices;
66
173
 
174
+ for (i = 0; i <= cap_segment_count; i++) {
175
+ const angle_b = i * angular_step_i;
176
+
177
+ const sin_b = Math.sin(angle_b);
178
+ const cos_b = Math.cos(angle_b);
179
+
180
+ const cap_ring_radius = sin_b * radius;
181
+
182
+ const _d = -radius * direction;
183
+
184
+ const _px = Px + tangent.x * cos_b * _d;
185
+ const _py = Py + tangent.y * cos_b * _d;
186
+ const _pz = Pz + tangent.z * cos_b * _d;
187
+
188
+ make_ring_vertices(
189
+ out,
190
+ _px, _py, _pz,
191
+ normal, B, uv_u,
192
+ cap_ring_radius, angular_step_j, radial_segments
193
+ );
67
194
  }
68
195
 
69
- function generateSegment(i) {
196
+ make_ring_faces(out, index_offset, cap_segment_count, radial_segments);
197
+ }
70
198
 
71
- // we use getPointAt to sample evenly distributed points from the given path
199
+ /**
200
+ *
201
+ * @param {number} index
202
+ * @param {Float32Array|number[]} in_positions
203
+ * @param {Vector3[]} in_normals
204
+ * @param {Vector3[]} in_binormals
205
+ * @param {GeometryOutput} out
206
+ * @param {number} radius
207
+ * @param {number} radial_segments
208
+ * @param {number} direction
209
+ */
210
+ function make_cap_flat(
211
+ out,
212
+ index,
213
+ in_positions, in_normals, in_binormals,
214
+ radius, radial_segments, direction
215
+ ) {
216
+ // how many radial segments will be placed laterally to make up the cap
217
+ const i3 = index * 3;
72
218
 
73
- const i3 = i * 3;
219
+ const Px = in_positions[i3];
220
+ const Py = in_positions[i3 + 1];
221
+ const Pz = in_positions[i3 + 2];
74
222
 
75
- const Px = points[i3];
76
- const Py = points[i3 + 1];
77
- const Pz = points[i3 + 2];
223
+ // retrieve corresponding normal and binormal
78
224
 
79
- // retrieve corresponding normal and binormal
225
+ const N = in_normals[index];
226
+ const B = in_binormals[index];
80
227
 
81
- const N = _normals[i];
82
- const B = binormals[i];
228
+ const tangent = new Vector3();
229
+ tangent.crossVectors(N, B);
230
+ tangent.normalize();
83
231
 
84
- // generate normals and vertices for the current segment
232
+ const normal = direction > 0 ? N.clone().negate() : N;
233
+
234
+ const angular_step_j = (Math.PI * 2) / radial_segments;
85
235
 
86
- const angular_step = (Math.PI * 2) / radialSegments;
236
+ const uv_u = index / (in_positions.length / 3 - 1);
87
237
 
88
- for (let j = 0; j <= radialSegments; j++) {
89
- const v = j * angular_step;
238
+ const index_offset = out.cursor_vertices;
90
239
 
91
- const sin = Math.sin(v);
92
- const cos = -Math.cos(v);
240
+ make_ring_vertices(
241
+ out,
242
+ Px, Py, Pz,
243
+ normal, B, uv_u,
244
+ radius, angular_step_j, radial_segments
245
+ );
93
246
 
94
- // normal
247
+ make_ring_vertices(
248
+ out,
249
+ Px, Py, Pz,
250
+ normal, B, uv_u,
251
+ 0, angular_step_j, radial_segments
252
+ );
95
253
 
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();
254
+ make_ring_faces(out, index_offset, 1, radial_segments);
255
+ }
100
256
 
101
- normals.push(normal.x, normal.y, normal.z);
257
+ /**
258
+ *
259
+ * @param {number} index
260
+ * @param {Float32Array|number[]} in_positions
261
+ * @param {Vector3[]} in_normals
262
+ * @param {Vector3[]} in_binormals
263
+ * @param {GeometryOutput} out
264
+ * @param {number} radius
265
+ * @param {number} radial_segments
266
+ * @param {number} direction
267
+ * @param {CapType} type
268
+ */
269
+ function make_cap(
270
+ out,
271
+ index,
272
+ in_positions, in_normals, in_binormals,
273
+ radius, radial_segments, direction, type
274
+ ) {
275
+ if (type === CapType.None) {
276
+ // do nothing
277
+ } else if (type === CapType.Round) {
278
+ make_cap_round(
279
+ out,
280
+ index,
281
+ in_positions, in_normals, in_binormals,
282
+ radius, radial_segments,
283
+ direction
284
+ );
285
+ } else if (type === CapType.Flat) {
286
+ make_cap_flat(
287
+ out,
288
+ index,
289
+ in_positions, in_normals, in_binormals,
290
+ radius, radial_segments,
291
+ direction
292
+ );
293
+ } else {
294
+ throw new Error(`Unsupported cap type '${type}'`);
295
+ }
296
+ }
102
297
 
103
- // vertex
298
+ /**
299
+ *
300
+ * @param {number} count how many caps
301
+ * @param {{polygon_count:number, vertex_count:number}} out
302
+ * @param {number} radial_segments
303
+ * @param {CapType} type
304
+ */
305
+ function compute_cap_geometry_size(count, out, radial_segments, type) {
306
+
307
+ if (type === CapType.None) {
308
+ // do nothing
309
+ } else if (type === CapType.Round) {
310
+ const cap_segment_count = compute_cap_round_segment_count(radial_segments);
311
+
312
+ out.vertex_count += count * (cap_segment_count + 1) * (radial_segments + 1);
313
+ out.polygon_count += count * cap_segment_count * (radial_segments) * 2;
314
+ } else if (type === CapType.Flat) {
315
+ out.vertex_count += count * (2) * (radial_segments + 1);
316
+ out.polygon_count += count * (radial_segments) * 2;
317
+ } else {
318
+ throw new Error(`Unsupported cap type '${type}'`);
319
+ }
320
+ }
104
321
 
105
- vertex.x = Px + radius * normal.x;
106
- vertex.y = Py + radius * normal.y;
107
- vertex.z = Pz + radius * normal.z;
322
+ /**
323
+ * @see https://github.com/mrdoob/three.js/blob/master/src/geometries/TubeGeometry.js
324
+ * @param {Float32Array|number[]} in_positions
325
+ * @param {Vector3[]} in_normals
326
+ * @param {Vector3[]} in_binormals
327
+ * @param {number} radius
328
+ * @param {number} radial_segments
329
+ * @param {boolean} [closed]
330
+ * @param {CapType} [cap_type]
331
+ * @returns {BufferGeometry}
332
+ */
333
+ export function makeTubeGeometry(in_positions, in_normals, in_binormals, radius, radial_segments, closed = false, cap_type = CapType.Round) {
108
334
 
109
- vertices.push(vertex.x, vertex.y, vertex.z);
335
+ const out = new GeometryOutput();
110
336
 
111
- }
337
+ // helper variables
338
+
339
+ const point_count = in_positions.length / 3;
340
+ const tubular_segments = point_count - 1;
341
+
342
+
343
+ const geometry_size = {
344
+ vertex_count: (tubular_segments + 1) * (radial_segments + 1),
345
+ polygon_count: tubular_segments * radial_segments * 2
346
+ };
112
347
 
348
+ if (!closed) {
349
+ compute_cap_geometry_size(2, geometry_size, radial_segments, cap_type);
113
350
  }
114
351
 
115
- function generateIndices() {
116
- let i = 0, j = 0;
352
+ out.allocate(
353
+ geometry_size.vertex_count,
354
+ geometry_size.polygon_count
355
+ );
117
356
 
118
- for (j = 1; j <= tubularSegments; j++) {
357
+ // create buffer data
358
+ if (!closed) {
359
+ // start cap
360
+ make_cap(out, 0, in_positions, in_normals, in_binormals, radius, radial_segments, 1, cap_type);
361
+ }
119
362
 
120
- for (i = 1; i <= radialSegments; i++) {
363
+ generateBufferData();
121
364
 
122
- const s_1 = radialSegments + 1;
365
+ if (!closed) {
366
+ // end cap
367
+ make_cap(out, point_count - 1, in_positions, in_normals, in_binormals, radius, radial_segments, -1, cap_type);
368
+ }
123
369
 
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;
370
+ // functions
128
371
 
129
- // faces
372
+ function generateBufferData() {
373
+ const index_offset = out.cursor_vertices;
130
374
 
131
- indices.push(a, b, d);
132
- indices.push(b, c, d);
375
+ for (let i = 0; i < tubular_segments; i++) {
133
376
 
134
- }
377
+ generateSegment(i);
135
378
 
136
379
  }
137
380
 
381
+ // if the geometry is not closed, generate the last row of vertices and normals
382
+ // at the regular position on the given path
383
+ //
384
+ // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)
385
+
386
+ generateSegment((closed === false) ? tubular_segments : 0);
387
+
388
+ // finally create faces
389
+ make_ring_faces(out, index_offset, tubular_segments, radial_segments);
390
+
138
391
  }
139
392
 
140
- function generateUVs() {
141
- let i = 0, j = 0;
393
+ function generateSegment(i) {
142
394
 
143
- for (i = 0; i <= tubularSegments; i++) {
395
+ // we use getPointAt to sample evenly distributed points from the given path
396
+
397
+ const i3 = i * 3;
144
398
 
145
- const u = i / tubularSegments;
399
+ const Px = in_positions[i3];
400
+ const Py = in_positions[i3 + 1];
401
+ const Pz = in_positions[i3 + 2];
146
402
 
147
- for (j = 0; j <= radialSegments; j++) {
403
+ // retrieve corresponding normal and binormal
148
404
 
149
- const v = j / radialSegments;
405
+ const N = in_normals[i];
406
+ const B = in_binormals[i];
150
407
 
151
- uvs.push(u, v);
408
+ // generate normals and vertices for the current segment
152
409
 
153
- }
410
+ const angular_step = (Math.PI * 2) / radial_segments;
154
411
 
155
- }
412
+ make_ring_vertices(
413
+ out,
414
+ Px, Py, Pz,
415
+ N, B, i / tubular_segments,
416
+ radius, angular_step, radial_segments
417
+ );
156
418
 
157
419
  }
158
420
 
159
421
 
160
- return geometry;
422
+ return out.build();
161
423
  }
@@ -0,0 +1,390 @@
1
+ import { EngineHarness } from "../../../../EngineHarness.js";
2
+ import { EngineConfiguration } from "../../../../EngineConfiguration.js";
3
+ import GUIElementSystem from "../../../../ecs/gui/GUIElementSystem.js";
4
+ import HeadsUpDisplaySystem from "../../../../ecs/gui/hud/HeadsUpDisplaySystem.js";
5
+ import ScriptSystem from "../../../../ecs/systems/ScriptSystem.js";
6
+ import FacingDirectionSystem from "../../../../../../model/game/ecs/system/FacingDirectionSystem.js";
7
+ import PathFollowingSystem from "../../../../navigation/ecs/path_following/PathFollowingSystem.js";
8
+ import SteeringSystem from "../../../../ecs/systems/SteeringSystem.js";
9
+ import MotionSystem from "../../../../ecs/systems/MotionSystem.js";
10
+ import TagSystem from "../../../../ecs/systems/TagSystem.js";
11
+ import { AttachmentSystem } from "../../../../ecs/attachment/AttachmentSystem.js";
12
+ import { SoundEmitterSystem } from "../../../../sound/ecs/emitter/SoundEmitterSystem.js";
13
+ import SoundControllerSystem from "../../../../sound/ecs/SoundControllerSystem.js";
14
+ import SoundListenerSystem from "../../../../sound/ecs/SoundListenerSystem.js";
15
+ import TimerSystem from "../../../../ecs/systems/TimerSystem.js";
16
+ import AnimationSystem from "../../../../ecs/systems/AnimationSystem.js";
17
+ import TopDownCameraControllerSystem from "../../camera/topdown/TopDownCameraControllerSystem.js";
18
+ import { TopDownCameraLanderSystem } from "../../camera/topdown/TopDownCameraLanderSystem.js";
19
+ import { CameraSystem } from "../../camera/CameraSystem.js";
20
+ import { MeshSystem } from "../../mesh/MeshSystem.js";
21
+ import ClingToTerrainSystem from "../../../../ecs/terrain/ecs/ClingToTerrainSystem.js";
22
+ import TerrainSystem from "../../../../ecs/terrain/ecs/TerrainSystem.js";
23
+ import WaterSystem from "../../water/WaterSystem.js";
24
+ import Trail2DSystem from "../../trail2d/Trail2DSystem.js";
25
+ import { Foliage2System } from "../../../../ecs/foliage/ecs/Foliage2System.js";
26
+ import ViewportPositionSystem from "../../../../ecs/gui/position/ViewportPositionSystem.js";
27
+ import { GridPosition2TransformSystem } from "../../../../grid/systems/GridPosition2TransformSystem.js";
28
+ import { Transform2GridPositionSystem } from "../../../../grid/transform2grid/Transform2GridPositionSystem.js";
29
+ import SynchronizePositionSystem from "../../../../ecs/systems/SynchronizePositionSystem.js";
30
+ import GridObstacleSystem from "../../../../grid/systems/GridObstacleSystem.js";
31
+ import GridPositionSystem from "../../../../grid/systems/GridPositionSystem.js";
32
+ import InputControllerSystem from "../../../../input/ecs/systems/InputControllerSystem.js";
33
+ import { InputSystem } from "../../../../input/ecs/systems/InputSystem.js";
34
+ import MeshHighlightSystem from "../../highlight/system/MeshHighlightSystem.js";
35
+ import LightSystem from "../../light/LightSystem.js";
36
+ import AnimationControllerSystem from "../../animation/AnimationControllerSystem.js";
37
+ import { AnimationGraphSystem } from "../../animation/animator/AnimationGraphSystem.js";
38
+ import { BehaviorSystem } from "../../../../intelligence/behavior/ecs/BehaviorSystem.js";
39
+ import { SerializationMetadataSystem } from "../../../../ecs/systems/SerializationMetadataSystem.js";
40
+ import { InverseKinematicsSystem } from "../../../../ecs/animation/InverseKinematicsSystem.js";
41
+ import { PathDisplaySystem } from "../PathDisplaySystem.js";
42
+ import { AmbientOcclusionPostProcessEffect } from "../../../render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js";
43
+ import { GameAssetType } from "../../../../asset/GameAssetType.js";
44
+ import { GLTFAssetLoader } from "../../../../asset/loaders/GLTFAssetLoader.js";
45
+ import { JsonAssetLoader } from "../../../../asset/loaders/JsonAssetLoader.js";
46
+ import { TextureAssetLoader } from "../../../../asset/loaders/texture/TextureAssetLoader.js";
47
+ import { ImageRGBADataLoader } from "../../../../asset/loaders/image/ImageRGBADataLoader.js";
48
+ import { ShadedGeometrySystem } from "../../mesh-v2/ShadedGeometrySystem.js";
49
+ import { makeEngineOptionsModel } from "../../../../../../model/game/options/makeEngineOptionsModel.js";
50
+ import { enableEditor } from "../../../../../editor/enableEditor.js";
51
+ import { CanvasView } from "../../../../../view/elements/CanvasView.js";
52
+ import { computeCatmullRomSpline } from "../../../../navigation/ecs/components/computeCatmullRomSpline.js";
53
+ import { EntityPathStyle } from "../entity/EntityPathStyle.js";
54
+ import { EntityPathMarkerDefinition } from "../entity/EntityPathMarkerDefinition.js";
55
+ import { EntityBlueprint } from "../../../../ecs/EntityBlueprint.js";
56
+ import { Transform } from "../../../../ecs/transform/Transform.js";
57
+ import Mesh from "../../mesh/Mesh.js";
58
+ import { Animation } from "../../../../ecs/animation/Animation.js";
59
+ import Highlight from "../../highlight/Highlight.js";
60
+ import { TubePathStyle } from "./TubePathStyle.js";
61
+ import { TubeMaterialType } from "./TubeMaterialType.js";
62
+ import { MatcapMaterialDefinition } from "./MatcapMaterialDefinition.js";
63
+ import Path from "../../../../navigation/ecs/components/Path.js";
64
+ import { InterpolationType } from "../../../../navigation/ecs/components/InterpolationType.js";
65
+ import Vector3 from "../../../../../core/geom/Vector3.js";
66
+ import { RibbonPathStyle } from "../ribbon/RibbonPathStyle.js";
67
+ import { PathDisplay } from "../PathDisplay.js";
68
+ import { PathDisplaySpec } from "../PathDisplaySpec.js";
69
+ import { PathDisplayType } from "../PathDisplayType.js";
70
+ import EntityBuilder from "../../../../ecs/EntityBuilder.js";
71
+ import { BehaviorComponent } from "../../../../intelligence/behavior/ecs/BehaviorComponent.js";
72
+ import { pingpong } from "../../../../../core/math/pingpong.js";
73
+ import { PathEvents } from "../../../../navigation/ecs/components/PathEvents.js";
74
+
75
+ const engineHarness = new EngineHarness();
76
+
77
+ function makeConfig(engine) {
78
+
79
+ const config = new EngineConfiguration();
80
+
81
+ /**
82
+ *
83
+ * @type {SoundEngine}
84
+ */
85
+ const sound = engine.sound;
86
+ const graphics = engine.graphics;
87
+ const assetManager = engine.assetManager;
88
+ const devices = engine.devices;
89
+
90
+ const guiSystem = new GUIElementSystem(engine.gui.view, engine);
91
+ const headsUpDisplaySystem = new HeadsUpDisplaySystem(graphics);
92
+
93
+ config.systems.push(
94
+ new ScriptSystem(),
95
+ new FacingDirectionSystem(),
96
+ new PathFollowingSystem(),
97
+ new SteeringSystem(),
98
+ new MotionSystem(),
99
+ new TagSystem(),
100
+ new AttachmentSystem(),
101
+ new SoundEmitterSystem(assetManager, sound.destination, sound.context),
102
+ new SoundControllerSystem(),
103
+ new SoundListenerSystem(sound.context),
104
+ new TimerSystem(),
105
+ guiSystem,
106
+ new AnimationSystem(graphics.viewport.size),
107
+ new TopDownCameraControllerSystem(graphics),
108
+ new TopDownCameraLanderSystem(),
109
+ new CameraSystem(graphics.scene, graphics),
110
+ new MeshSystem(engine),
111
+ new ClingToTerrainSystem(),
112
+ new TerrainSystem(graphics, assetManager),
113
+ new WaterSystem(graphics),
114
+ new Trail2DSystem(engine),
115
+ new Foliage2System(assetManager, graphics),
116
+ new ViewportPositionSystem(graphics.viewport.size),
117
+ new GridPosition2TransformSystem(),
118
+ new Transform2GridPositionSystem(),
119
+ new SynchronizePositionSystem(),
120
+ new GridObstacleSystem(),
121
+ new GridPositionSystem(),
122
+ new InputControllerSystem(devices),
123
+ new InputSystem(devices),
124
+ new MeshHighlightSystem(engine),
125
+ new LightSystem(engine, {
126
+ shadowResolution: 1024
127
+ }),
128
+ new AnimationControllerSystem(),
129
+ new AnimationGraphSystem(graphics.viewport.size),
130
+ headsUpDisplaySystem,
131
+ new BehaviorSystem(engine),
132
+ new SerializationMetadataSystem(),
133
+ new InverseKinematicsSystem(),
134
+ new PathDisplaySystem(engine),
135
+ );
136
+
137
+ // Plugins
138
+
139
+ config.addPlugin(AmbientOcclusionPostProcessEffect)
140
+
141
+ // Knowledge
142
+
143
+ // Assets
144
+
145
+ config.addLoader(GameAssetType.ModelGLTF, new GLTFAssetLoader());
146
+ config.addLoader(GameAssetType.ModelGLTF_JSON, new GLTFAssetLoader());
147
+ config.addLoader(GameAssetType.JSON, new JsonAssetLoader());
148
+ config.addLoader(GameAssetType.Texture, new TextureAssetLoader());
149
+ config.addLoader(GameAssetType.Image, new ImageRGBADataLoader());
150
+
151
+ return config;
152
+ }
153
+
154
+ /**
155
+ *
156
+ * @param {EngineHarness} harness
157
+ * @return {Promise<void>}
158
+ */
159
+ async function init(harness) {
160
+ const engine = harness.engine;
161
+
162
+ // engine.entityManager.addSystem(new RenderSystem(engine.graphics));
163
+ engine.entityManager.addSystem(new ShadedGeometrySystem(engine));
164
+
165
+ makeEngineOptionsModel(engine.options, engine);
166
+
167
+ await makeConfig(engine).apply(engine);
168
+
169
+ enableEditor(engine);
170
+
171
+ await harness.initialize();
172
+
173
+ main(engine);
174
+ }
175
+
176
+ function catmullRom(points) {
177
+ const cv = new CanvasView();
178
+
179
+ cv.size.set(500, 500);
180
+
181
+ cv.link();
182
+
183
+ cv.css({
184
+ position: "absolute",
185
+ left: 0,
186
+ right: 0,
187
+ zIndex: 99999
188
+ });
189
+
190
+ const sample = [];
191
+
192
+ const ctx = cv.context2d;
193
+ ctx.strokeStyle = "rgba(255,255,255,1)";
194
+ ctx.lineWidth = 2;
195
+ ctx.beginPath();
196
+
197
+ const draw_scale = 50;
198
+
199
+ const result = [];
200
+ const sampleCount = 50;
201
+
202
+ computeCatmullRomSpline(result, points, points.length / 3, 3, sampleCount, 0.5);
203
+
204
+ for (let i = 0; i < sampleCount; i++) {
205
+ const x = result[i * 3];
206
+ const y = result[i * 3 + 1];
207
+ const z = result[i * 3 + 2];
208
+
209
+ ctx.lineTo(x * draw_scale, z * draw_scale);
210
+
211
+ }
212
+ ctx.stroke();
213
+
214
+ document.body.appendChild(cv.el);
215
+ }
216
+
217
+ function sample_style_entity() {
218
+ const style = new EntityPathStyle();
219
+ style.spacing = 0.6;
220
+ style.marker_start = EntityPathMarkerDefinition.from({
221
+ blueprint: EntityBlueprint.from([
222
+ Transform.fromJSON({}),
223
+ Mesh.fromJSON({
224
+ url: "data/models/arrow-symbols/arrow4/model-green.gltf",
225
+ castShadow: true,
226
+ receiveShadow: true
227
+ })
228
+ ]),
229
+ transform: Transform.fromJSON({
230
+ scale: {
231
+ x: 0.3,
232
+ y: 0.3,
233
+ z: 0.3
234
+ },
235
+ rotation: {
236
+ x: 0.5,
237
+ y: -0.5,
238
+ z: 0.5,
239
+ w: 0.5
240
+ },
241
+ position: {
242
+ x: 0,
243
+ y: 1,
244
+ z: 0
245
+ }
246
+ })
247
+ });
248
+ style.marker_end = EntityPathMarkerDefinition.from({
249
+ blueprint: EntityBlueprint.from([
250
+ Transform.fromJSON({}),
251
+ Mesh.fromJSON({
252
+ url: "data/models/arrow-symbols/arrow4/model-red.gltf",
253
+ castShadow: true,
254
+ receiveShadow: true
255
+ })
256
+ ]),
257
+ transform: Transform.fromJSON({
258
+ scale: {
259
+ x: 0.3,
260
+ y: 0.3,
261
+ z: 0.3
262
+ },
263
+ rotation: {
264
+ x: 0.5,
265
+ y: -0.5,
266
+ z: 0.5,
267
+ w: 0.5
268
+ },
269
+ position: {
270
+ x: 0,
271
+ y: 1,
272
+ z: 0
273
+ }
274
+ })
275
+ });
276
+ style.marker_main = EntityPathMarkerDefinition.from({
277
+ blueprint: EntityBlueprint.from([
278
+ Transform.fromJSON({}),
279
+ Mesh.fromJSON({
280
+ url: "data/models/Barel-Roll Droid/handpainted.gltf",
281
+ castShadow: true,
282
+ receiveShadow: true
283
+ }),
284
+ Animation.fromJSON({
285
+ clips: [
286
+ {
287
+ name: "fly",
288
+ repeatCount: Infinity
289
+ }
290
+ ]
291
+ }),
292
+ Highlight.fromOne(1, 0, 0, 1)
293
+ ]),
294
+ transform: Transform.fromJSON({
295
+ scale: {
296
+ x: 0.2,
297
+ y: 0.2,
298
+ z: 0.2
299
+ },
300
+ position: {
301
+ x: 0,
302
+ y: 1,
303
+ z: 0
304
+ }
305
+ })
306
+ });
307
+
308
+ return style;
309
+ }
310
+
311
+ function sample_style_tube() {
312
+ const style = new TubePathStyle();
313
+
314
+ style.color.setRGB(1, 1, 1);
315
+ style.material_type = TubeMaterialType.Matcap;
316
+ style.material = new MatcapMaterialDefinition();
317
+ // style.material.texture = 'data/textures/matcap/matcap-porcelain-white.jpg';
318
+ style.material.texture = "data/textures/matcap/nidorx/3E2335_D36A1B_8E4A2E_2842A5.jpg";
319
+
320
+ style.width = 0.1;
321
+
322
+ style.path_mask = [
323
+ 0, 1,
324
+ ];
325
+
326
+ return style;
327
+ }
328
+
329
+
330
+ function main(engine) {
331
+ EngineHarness.buildBasics({ engine });
332
+
333
+ const _p = new Path();
334
+ _p.interpolation = InterpolationType.CatmullRom;
335
+ // _p.interpolation = InterpolationType.Linear;
336
+
337
+ // _p.setPositionsFromVectorArray(Path.smoothPath([
338
+ // new Vector3(3, 1, 1),
339
+ // new Vector3(3, 1, 3),
340
+ // new Vector3(7, 1, 3),
341
+ // new Vector3(7, 1, 7),
342
+ // new Vector3(3, 1, 7),
343
+ // new Vector3(3, 1, 8),
344
+ // ], 50));
345
+
346
+ _p.setPositionsFromVectorArray([
347
+ new Vector3(3, 1, 1),
348
+ new Vector3(3, 1, 3),
349
+ new Vector3(7, 1, 3),
350
+ new Vector3(7, 1, 7),
351
+ new Vector3(3, 1, 7),
352
+ new Vector3(3, 1, 8),
353
+ ]);
354
+
355
+
356
+ const trailPathStyle = new RibbonPathStyle();
357
+ trailPathStyle.material.diffuse = "data/textures/trail/Circle_04.png";
358
+ trailPathStyle.resolution = 10;
359
+ trailPathStyle.thickness = 0.1;
360
+
361
+ const pathDisplay = new PathDisplay();
362
+ const tube_style = sample_style_tube();
363
+ pathDisplay.specs.push(
364
+ PathDisplaySpec.from(
365
+ PathDisplayType.Tube,
366
+ tube_style
367
+ )
368
+ );
369
+
370
+
371
+ const path_entity = new EntityBuilder();
372
+
373
+ const ecd = engine.entityManager.dataset;
374
+ path_entity
375
+ .add(_p)
376
+ .add(pathDisplay)
377
+ .build(ecd);
378
+
379
+ let time = 0;
380
+ new EntityBuilder()
381
+ .add(BehaviorComponent.looping_function((delta) => {
382
+ time += delta*0.01;
383
+ tube_style.path_mask[1] = pingpong(time, 1);
384
+
385
+ path_entity.sendEvent(PathEvents.Changed);
386
+ }))
387
+ .build(ecd)
388
+ }
389
+
390
+ init(engineHarness);
@@ -31,7 +31,7 @@ export class BehaviorComponent {
31
31
 
32
32
  /**
33
33
  *
34
- * @param {function} f
34
+ * @param {function(timeDelta:number)} f
35
35
  * @param {*} [thisArg]
36
36
  * @returns {BehaviorComponent}
37
37
  */
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "productName": "Meep",
6
6
  "description": "production-ready JavaScript game engine based on Entity Component System Architecture",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.35.16",
8
+ "version": "2.35.17",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",