@woosh/meep-engine 2.43.9 → 2.43.11

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.
@@ -0,0 +1,28 @@
1
+ /**
2
+ *
3
+ * @param {string} url
4
+ * @returns {string}
5
+ */
6
+ export async function url_to_data_url(url) {
7
+ return fetch(url)
8
+ .then(response => {
9
+ return response.blob();
10
+ })
11
+ .then(blob => {
12
+ const fr = new FileReader();
13
+
14
+ return new Promise((resolve, reject) => {
15
+
16
+ fr.onload = () => {
17
+ resolve(fr.result);
18
+ };
19
+
20
+ fr.onerror = reject;
21
+ fr.onabort = reject;
22
+
23
+ fr.readAsDataURL(blob);
24
+
25
+ });
26
+
27
+ });
28
+ }
@@ -302,7 +302,7 @@ Deque.prototype.peek = Deque.prototype.getFirst;
302
302
  Deque.prototype.push = Deque.prototype.addFirst;
303
303
  Deque.prototype.pop = Deque.prototype.removeFirst;
304
304
 
305
- /*
306
- Standard queue method
305
+ /**
306
+ * Standard queue method
307
307
  */
308
308
  Deque.prototype.add = Deque.prototype.addLast;
@@ -5,6 +5,7 @@ import ImageView from "../../../../view/elements/image/ImageView.js";
5
5
  import ObservedString from "../../../../core/model/ObservedString.js";
6
6
  import { ObservedStringEditor } from "./ObservedStringEditor.js";
7
7
  import ButtonView from "../../../../view/elements/button/ButtonView.js";
8
+ import { url_to_data_url } from "../../../../core/binary/url_to_data_url.js";
8
9
 
9
10
  /**
10
11
  *
@@ -19,35 +20,6 @@ function is_data_url(url) {
19
20
  return url.startsWith('data:');
20
21
  }
21
22
 
22
- /**
23
- *
24
- * @param {string} url
25
- * @returns {string}
26
- */
27
- async function url_to_data_url(url) {
28
- return fetch(url)
29
- .then(response => {
30
- return response.blob();
31
- })
32
- .then(blob => {
33
- const fr = new FileReader();
34
-
35
- return new Promise((resolve, reject) => {
36
-
37
- fr.onload = () => {
38
- resolve(fr.result);
39
- };
40
-
41
- fr.onerror = reject;
42
- fr.onabort = reject;
43
-
44
- fr.readAsDataURL(blob);
45
-
46
- });
47
-
48
- });
49
- }
50
-
51
23
  export class ImagePathEditor extends TypeEditor {
52
24
  inline = true;
53
25
 
@@ -11,6 +11,7 @@ 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
13
  import { TubePathBuilder } from "./tube/build/TubePathBuilder.js";
14
+ import { Deque } from "../../../../core/collection/queue/Deque.js";
14
15
 
15
16
  const builders = {
16
17
  [PathDisplayType.None]: function (style, path, result) {
@@ -82,6 +83,14 @@ const builders = {
82
83
  }
83
84
  };
84
85
 
86
+ /**
87
+ * Maximum amount of time allowed per system tick to spend on processing update queue
88
+ * in milliseconds
89
+ * @readonly
90
+ * @type {number}
91
+ */
92
+ const UPDATE_PROCESSING_BUDGET_MS = 10;
93
+
85
94
  class PathDisplayContext extends SystemEntityContext {
86
95
  constructor() {
87
96
  super();
@@ -100,6 +109,8 @@ class PathDisplayContext extends SystemEntityContext {
100
109
  const ownedEntities = this.__owned_entities;
101
110
  ownedEntities.splice(0, ownedEntities.length);
102
111
 
112
+ // todo check that the main entity still exists before we decide to spawn new entities
113
+
103
114
  /**
104
115
  * @type {PathDisplay}
105
116
  */
@@ -180,6 +191,16 @@ class PathDisplayContext extends SystemEntityContext {
180
191
  this.__build();
181
192
  }
182
193
 
194
+ request_update() {
195
+ /**
196
+ *
197
+ * @type {PathDisplaySystem}
198
+ */
199
+ const system = this.system;
200
+
201
+ system.request_entity_update(this.entity);
202
+ }
203
+
183
204
  link() {
184
205
  super.link();
185
206
 
@@ -189,8 +210,8 @@ class PathDisplayContext extends SystemEntityContext {
189
210
 
190
211
  const entity = this.entity;
191
212
 
192
- ecd.addEntityEventListener(entity, PathEvents.Changed, this.rebuild, this);
193
- ecd.addEntityEventListener(entity, PathDisplayEvents.Changed, this.rebuild, this);
213
+ ecd.addEntityEventListener(entity, PathEvents.Changed, this.request_update, this);
214
+ ecd.addEntityEventListener(entity, PathDisplayEvents.Changed, this.request_update, this);
194
215
  }
195
216
 
196
217
  unlink() {
@@ -200,8 +221,16 @@ class PathDisplayContext extends SystemEntityContext {
200
221
 
201
222
  const entity = this.entity;
202
223
 
203
- ecd.removeEntityEventListener(entity, PathEvents.Changed, this.rebuild, this);
204
- ecd.removeEntityEventListener(entity, PathDisplayEvents.Changed, this.rebuild, this);
224
+ ecd.removeEntityEventListener(entity, PathEvents.Changed, this.request_update, this);
225
+ ecd.removeEntityEventListener(entity, PathDisplayEvents.Changed, this.request_update, this);
226
+
227
+ /**
228
+ *
229
+ * @type {PathDisplaySystem}
230
+ */
231
+ const system = this.system;
232
+
233
+ system.cancel_entity_update(entity);
205
234
 
206
235
  // destroy existing owned entities
207
236
  this.__destroy_existing_entities();
@@ -238,6 +267,35 @@ export class PathDisplaySystem extends AbstractContextSystem {
238
267
  * @type {Reference<RibbonXPlugin>}
239
268
  */
240
269
  this.plugin = null;
270
+
271
+ /**
272
+ * Deferred queue of entities slated to be rebuilt
273
+ * @type {Deque<number>}
274
+ * @private
275
+ */
276
+ this.__rebuild_queue = new Deque();
277
+ }
278
+
279
+ /**
280
+ * Request that path displays be rebuilt
281
+ * @package
282
+ * @param {number} entity
283
+ */
284
+ request_entity_update(entity) {
285
+ assert.isNonNegativeInteger(entity, 'entity');
286
+
287
+ if (!this.__rebuild_queue.has(entity)) {
288
+ this.__rebuild_queue.add(entity);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Cancel any pending updates
294
+ * @package
295
+ * @param {number} entity
296
+ */
297
+ cancel_entity_update(entity) {
298
+ this.__rebuild_queue.remove(entity);
241
299
  }
242
300
 
243
301
  async startup(entityManager, readyCallback, errorCallback) {
@@ -253,4 +311,34 @@ export class PathDisplaySystem extends AbstractContextSystem {
253
311
 
254
312
  super.shutdown(entityManager, readyCallback, errorCallback);
255
313
  }
314
+
315
+ update(time_delta) {
316
+ // process update queue
317
+
318
+ const queue = this.__rebuild_queue;
319
+ if (queue.isEmpty()) {
320
+ return;
321
+ }
322
+
323
+ const t0 = performance.now();
324
+
325
+ do {
326
+ // note that we don't need to check if the queue is empty here for the first iteration, as we already checked earlier
327
+ const entity = queue.pop();
328
+
329
+ /**
330
+ *
331
+ * @type {PathDisplayContext|undefined}
332
+ */
333
+ const ctx = this.__getEntityContext(entity);
334
+
335
+ // check that entity still exists
336
+ if (ctx === undefined) {
337
+ continue;
338
+ }
339
+
340
+ ctx.rebuild();
341
+
342
+ } while ((performance.now() - t0) < UPDATE_PROCESSING_BUDGET_MS && !queue.isEmpty());
343
+ }
256
344
  }
@@ -4,7 +4,7 @@ import { BufferAttribute, BufferGeometry } from "three";
4
4
  /**
5
5
  * Helper class, allowing us to treat geometry build process as a stream without having to create intermediate arrays and performing unnecessary copying
6
6
  */
7
- export class GeometryOutput {
7
+ export class StreamGeometryBuilder {
8
8
  constructor() {
9
9
  /**
10
10
  *
@@ -1,4 +1,4 @@
1
- import { BufferGeometry, MeshBasicMaterial, MeshMatcapMaterial, MeshStandardMaterial, Vector3 } from "three";
1
+ import { BufferGeometry, MeshBasicMaterial, MeshMatcapMaterial, MeshStandardMaterial } from "three";
2
2
  import { InterpolationType } from "../../../../../navigation/ecs/components/InterpolationType.js";
3
3
  import EntityBuilder from "../../../../../ecs/EntityBuilder.js";
4
4
  import { Transform } from "../../../../../ecs/transform/Transform.js";
@@ -129,8 +129,6 @@ export class TubePathBuilder {
129
129
  // generate three.js curve from path
130
130
  const path_component = this.path;
131
131
 
132
- const three_points = [];
133
-
134
132
  const pointCount = path_component.getPointCount();
135
133
 
136
134
  if (pointCount < 2) {
@@ -138,15 +136,6 @@ export class TubePathBuilder {
138
136
  return;
139
137
  }
140
138
 
141
- for (let i = 0; i < pointCount; i++) {
142
- const v3 = new Vector3();
143
-
144
- path_component.getPosition(i, v3);
145
-
146
- three_points.push(v3);
147
- }
148
-
149
-
150
139
  const material_def = make_material(style, this.assetManager);
151
140
 
152
141
  material_def.color.set(style.color.toUint());
@@ -0,0 +1,32 @@
1
+ import Path from "../../../../../navigation/ecs/components/Path.js";
2
+ import { build_geometry_catmullrom } from "./build_geometry_catmullrom.js";
3
+ import { TubePathStyle } from "../TubePathStyle.js";
4
+ import { CapType } from "../CapType.js";
5
+
6
+ test('very tiny segment of a path', () => {
7
+ const path = Path.fromJSON({
8
+ points: [3, 1, 1, 3, 1, 3, 7, 1, 3, 7, 1, 7, 3, 1, 7, 3, 1, 8]
9
+ });
10
+
11
+ const style = new TubePathStyle();
12
+ style.cap_type = CapType.Flat;
13
+ style.width = 0.1;
14
+ style.path_mask = [0, 0.00005900000113712167];
15
+ style.resolution = 10;
16
+ style.shape = [1, -2.4492937051703357e-16, 0.9238795042037964, -0.3826834261417389, 0.7071067690849304, -0.7071067690849304, 0.3826834261417389, -0.9238795042037964, -1.8369701465288538e-16, -1, -0.3826834261417389, -0.9238795042037964, -0.7071067690849304, -0.7071067690849304, -0.9238795042037964, -0.3826834261417389, -1, 1.2246468525851679e-16, -0.9238795042037964, 0.3826834261417389, -0.7071067690849304, 0.7071067690849304, -0.3826834261417389, 0.9238795042037964, 6.123234262925839e-17, 1, 0.3826834261417389, 0.9238795042037964, 0.7071067690849304, 0.7071067690849304, 0.9238795042037964, 0.3826834261417389];
17
+
18
+ const result = build_geometry_catmullrom(path, style, style.shape, [
19
+ 1.131973639570565e-16, 1, 0.3826834559440613, 0.9238795042037964, 0.7071067690849304, 0.7071067690849304, 0.9238795042037964, 0.3826834559440613, 1, -8.489801965906992e-17, 0.9238795042037964, -0.3826834559440613, 0.7071067690849304, -0.7071067690849304, 0.3826834559440613, -0.9238795042037964, -5.659868197852824e-17, -1, -0.3826834559440613, -0.9238795042037964, -0.7071067690849304, -0.7071067690849304, -0.9238795042037964, -0.3826834559440613, -1, 4.244900982953496e-17, -0.9238795042037964, 0.3826834559440613, -0.7071067690849304, 0.7071067690849304, -0.3826834559440613, 0.9238795042037964
20
+ ], [
21
+ 0.10000000149011612, 0, 0, -0, 0.10000000149011612, 0, 0, 0, 1
22
+ ], 0, 0.00005900000113712167);
23
+
24
+
25
+ const attribute_position = result.getAttribute("position");
26
+ expect(attribute_position).toBeDefined();
27
+ expect(attribute_position).not.toBeNull();
28
+
29
+ for (let i = 0; i < attribute_position.array.length; i++) {
30
+ expect(attribute_position.array[i]).not.toBeNaN();
31
+ }
32
+ });
@@ -101,8 +101,13 @@ export function computeFrenetFrames(points, closed = false, normal_hint) {
101
101
  // no tangent, copy previous one
102
102
  if (i > 0) {
103
103
  tangents[i] = tangents[i - 1];
104
- continue;
104
+ } else {
105
+ // very first tangent is undefined, set something arbitrary
106
+ // TODO take normal_hint into account
107
+ tangents[i] = new Vector3(0, 0, 1);
105
108
  }
109
+ continue;
110
+
106
111
  }
107
112
 
108
113
  tangents[i] = new Vector3(
@@ -1,10 +1,10 @@
1
1
  import { BufferGeometry, Vector3 } from "three";
2
- import { GeometryOutput } from "./GeometryOutput.js";
2
+ import { StreamGeometryBuilder } from "./StreamGeometryBuilder.js";
3
3
  import { CapType } from "../CapType.js";
4
4
  import { make_ring_vertices } from "./make_ring_vertices.js";
5
5
  import { assert } from "../../../../../../core/assert.js";
6
6
  import { make_ring_faces } from "./make_ring_faces.js";
7
- import { compute_cap_geometry_size, make_cap } from "./make_cap.js";
7
+ import { append_compute_cap_geometry_size, make_cap } from "./make_cap.js";
8
8
  import { v3_angle_cos_between } from "../../../../../../core/geom/v3_angle_between.js";
9
9
 
10
10
 
@@ -12,6 +12,7 @@ const v4_array = new Float32Array(4);
12
12
 
13
13
  /**
14
14
  * @see https://github.com/mrdoob/three.js/blob/master/src/geometries/TubeGeometry.js
15
+ * @see https://github.com/hofk/THREEg.js/blob/488f1128a25321a76888aa1fa19db64750318444/THREEg.js#L3483
15
16
  * @param {Float32Array|number[]} in_positions
16
17
  * @param {Vector3[]} in_normals
17
18
  * @param {Vector3[]} in_binormals
@@ -34,7 +35,7 @@ export function makeTubeGeometry(
34
35
  assert.isNumber(shape_length, 'shape_length');
35
36
  assert.isArrayLike(shape, 'shape');
36
37
 
37
- const out = new GeometryOutput();
38
+ const out = new StreamGeometryBuilder();
38
39
 
39
40
  // helper variables
40
41
 
@@ -48,7 +49,7 @@ export function makeTubeGeometry(
48
49
  };
49
50
 
50
51
  if (!closed) {
51
- compute_cap_geometry_size(2, geometry_size, shape_length, cap_type);
52
+ append_compute_cap_geometry_size(2, geometry_size, shape_length, cap_type);
52
53
  }
53
54
 
54
55
  out.allocate(
@@ -57,6 +58,7 @@ export function makeTubeGeometry(
57
58
  );
58
59
 
59
60
  // create buffer data
61
+
60
62
  if (!closed) {
61
63
  // start cap
62
64
  make_cap(
@@ -50,7 +50,7 @@ function compute_shape_radius(shape, shape_length, cx = 0, cy = 0) {
50
50
  * @param {Vector3[]} in_normals
51
51
  * @param {Vector3[]} in_binormals
52
52
  * @param {Vector3[]} in_tangents
53
- * @param {GeometryOutput} out
53
+ * @param {StreamGeometryBuilder} out
54
54
  * @param {number[]} shape
55
55
  * @param {number[]|Float32Array} shape_normal
56
56
  * @param {number} shape_length
@@ -106,9 +106,9 @@ function make_cap_round(
106
106
 
107
107
  for (i = 0; i <= cap_segment_count; i++) {
108
108
 
109
- const j = direction>0? i:cap_segment_count-i;
109
+ const j = direction > 0 ? i : cap_segment_count - i;
110
110
 
111
- const angle_b = j * angular_step_i ;
111
+ const angle_b = j * angular_step_i;
112
112
 
113
113
 
114
114
  const cos_b = Math.cos(angle_b);
@@ -151,9 +151,9 @@ const v4_no_bend = new Float32Array([0, 1, 0, 0]);
151
151
  * @param {Vector3[]} in_normals
152
152
  * @param {Vector3[]} in_binormals
153
153
  * @param {Vector3[]} in_tangents
154
- * @param {GeometryOutput} out
154
+ * @param {StreamGeometryBuilder} out
155
155
  * @param {number[]} shape
156
- * @param {number[]|Float32Array} shape_normals
156
+ * @param {number[]|Float32Array} shape_normal
157
157
  * @param {number} shape_length
158
158
  * @param {number[]} shape_transform
159
159
  * @param {number} direction
@@ -211,7 +211,7 @@ function make_cap_flat(
211
211
  * @param {Float32Array|number[]} in_positions
212
212
  * @param {Vector3[]} in_normals
213
213
  * @param {Vector3[]} in_binormals
214
- * @param {GeometryOutput} out
214
+ * @param {StreamGeometryBuilder} out
215
215
  * @param {Vector3[]} in_tangents
216
216
  * @param {number[]} shape
217
217
  * @param {number[]|Float32Array} shape_normal
@@ -250,13 +250,13 @@ export function make_cap(
250
250
  }
251
251
 
252
252
  /**
253
- *
253
+ * Increases target geometry buffers to accommodate a given number of caps
254
254
  * @param {number} count how many caps
255
- * @param {{polygon_count:number, vertex_count:number}} out
256
- * @param {number} radial_segments
255
+ * @param {{polygon_count:number, vertex_count:number}} out where to increment
256
+ * @param {number} radial_segments number of lines that make up profile of the extruded shape
257
257
  * @param {CapType} type
258
258
  */
259
- export function compute_cap_geometry_size(count, out, radial_segments, type) {
259
+ export function append_compute_cap_geometry_size(count, out, radial_segments, type) {
260
260
 
261
261
  if (type === CapType.None) {
262
262
  // do nothing
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  *
3
- * @param {GeometryOutput} out
3
+ * @param {StreamGeometryBuilder} out
4
4
  * @param {number} index_offset
5
5
  * @param {number} path_segments
6
6
  * @param {number} profile_segments
@@ -4,7 +4,7 @@ import { v3_dot } from "../../../../../../core/geom/v3_dot.js";
4
4
 
5
5
  /**
6
6
  *
7
- * @param {GeometryOutput} out
7
+ * @param {StreamGeometryBuilder} out
8
8
  * @param {number} Px
9
9
  * @param {number} Py
10
10
  * @param {number} Pz
@@ -27,13 +27,28 @@ const shader_vx = `
27
27
  out vec3 local_ray_far;
28
28
 
29
29
  flat out vec2 frame1;
30
+ flat out vec3 frame1_normal;
30
31
  out vec2 uv_frame1;
31
32
  out vec2 xy_frame1;
32
33
 
34
+ flat out vec2 frame2;
35
+ flat out vec3 frame2_normal;
36
+ out vec2 uv_frame2;
37
+ out vec2 xy_frame2;
38
+
39
+ flat out vec2 frame3;
40
+ flat out vec3 frame3_normal;
41
+ out vec2 uv_frame3;
42
+ out vec2 xy_frame3;
43
+
44
+ out vec4 blend_weights;
45
+
33
46
  uniform mat4 modelViewMatrix;
34
47
  uniform mat4 projectionMatrix;
35
48
  uniform mat3 normalMatrix;
36
49
  uniform mat4 modelMatrix;
50
+ uniform mat4 viewMatrix;
51
+ uniform vec3 cameraPosition;
37
52
 
38
53
  uniform vec3 uOffset;
39
54
  uniform float uRadius;
@@ -143,6 +158,16 @@ const shader_vx = `
143
158
  return res;
144
159
  }
145
160
 
161
+ vec3 projectOnPlaneBasis(vec3 ray, vec3 plane_normal, vec3 plane_x, vec3 plane_y)
162
+ {
163
+ //reproject plane normal onto planeXY basos
164
+ return normalize(vec3(
165
+ dot(plane_x, ray),
166
+ dot(plane_y, ray),
167
+ dot(plane_normal, ray)
168
+ ));
169
+ }
170
+
146
171
  void calcuateXYbasis(vec3 plane_normal, out vec3 plane_x, out vec3 plane_y)
147
172
  {
148
173
  vec3 up = vec3(0,1,0);
@@ -155,14 +180,56 @@ const shader_vx = `
155
180
  plane_y = normalize(cross(plane_x, plane_normal));
156
181
  }
157
182
 
183
+ //this function works well in orthogonal projection. It works okeyish with further distances of perspective projection
184
+ vec2 virtualPlaneUV(vec3 plane_normal, vec3 plane_x, vec3 plane_y, vec3 pivotToCameraRay, vec3 vertexToCameraRay, float size)
185
+ {
186
+ plane_normal = normalize(plane_normal);
187
+ plane_x = normalize(plane_x);
188
+ plane_y = normalize(plane_y);
189
+
190
+ // plane_normal is normalized but pivotToCameraRay & vertexToCameraRay are NOT
191
+ // so what are we doing here ?
192
+ // We are calculating length of pivotToCameraRay projected onto plane_normal
193
+ // so pivotToCameraRay is vector to camera from CENTER OF object
194
+ // we are recalculting this distance taking into account new plane normal
195
+ float projectedNormalRayLength = dot(plane_normal, pivotToCameraRay);
196
+ // tihs is direction is almost the same as origin, but its to individual vertex
197
+ // not sure this is correct for perspective projection
198
+ float projectedVertexRayLength = dot(plane_normal, vertexToCameraRay);
199
+ // basically its length difference betwen center and vertex - "not precise"
200
+ // projectedVertexRayLength is bigger than projectedNormalRayLength when vertex is
201
+ // further than "main front facing billboard"
202
+ // so offsetLength is getting smaller, otherwise is getting bigger
203
+ float offsetLength = projectedNormalRayLength/projectedVertexRayLength;
204
+
205
+ // ok so offsetLength is just a length
206
+ // we want a vector so we multiply it by vertexToCameraRay to get this offset
207
+ // now what are we REALY doing is calculuating distance difference
208
+ // se are SUBSTRACTING pivotToCameraRay vector
209
+ // we would get difference between center of plane and vertex rotated
210
+ vec3 offsetVector = vertexToCameraRay * offsetLength - pivotToCameraRay;
211
+
212
+ // we got the offset of rotated vertex, but we need to offset it from correct plane axis
213
+ // so again we projecting length of intersection (offset of rotated vertex) onto plane_x
214
+ // and plane_y
215
+ vec2 duv = vec2(
216
+ dot(plane_x, offsetVector),
217
+ dot(plane_y, offsetVector)
218
+ );
219
+
220
+ //we are in space -1 to 1
221
+ duv /= 2.0 * size;
222
+ duv += 0.5;
223
+ return duv;
224
+ }
158
225
 
159
226
  void main() {
160
227
  vUv = uv;
161
228
 
162
229
  vec2 framesMinusOne = uFrames - vec2(1.0);
163
230
 
164
- vec3 cameraPos_WS = (projectionMatrix * vec4(vec3(0), 1.0)).xyz;
165
- vec3 cameraPos_OS = (inverse(modelMatrix) * vec4(cameraPos_WS, 1.0)).xyz;
231
+ vec3 cameraPos_WS = cameraPosition;
232
+ vec3 cameraPos_OS = (inverse(viewMatrix) * vec4(cameraPos_WS, 1.0)).xyz;
166
233
 
167
234
  //TODO: check if this is correct. We are using orho projected images, so
168
235
  // camera far away
@@ -206,7 +273,7 @@ const shader_vx = `
206
273
  //get 2D projection of this vertex in normalized device coordinates
207
274
  vec2 pos = gl_Position.xy/gl_Position.w;
208
275
 
209
- vec3 projected = position;
276
+ vec3 projected = object_scale*position;
210
277
  vec3 vertexToCameraRay = (pivotToCameraRay - (projected));
211
278
  vec3 vertexToCameraDir = normalize(vertexToCameraRay);
212
279
 
@@ -221,25 +288,44 @@ const shader_vx = `
221
288
  //
222
289
  vec3 view_direction = normalize(local_ray_near-local_ray_far);
223
290
 
224
- vec2 octahedral_uv = clamp(VectorToGrid(view_direction)*0.5 + 0.5, 0.0, 1.0);
225
- vec2 grid = octahedral_uv * vec2(uFrames - 1.0);
291
+ vec2 grid = VectorToGrid(-view_direction);
292
+ //bias and scale to 0 to 1
293
+ grid = clamp((grid + 1.0) * 0.5, vec2(0, 0), vec2(1, 1));
294
+ grid *= framesMinusOne;
295
+ grid = clamp(grid, vec2(0), vec2(framesMinusOne));
296
+ vec2 gridFloor = min(floor(grid), framesMinusOne);
297
+ vec2 gridFract = fract(grid);
226
298
 
227
- vec2 gridFrac = fract(grid);
228
- vec2 gridFloor = floor(grid);
299
+ blend_weights = TriangleInterpolate( gridFract );
229
300
 
230
- vec4 weights = TriangleInterpolate( gridFrac );
301
+ frame1 = gridFloor;
302
+ frame2 = gridFloor + mix(vec2(0,1),vec2(1,0),blend_weights.w);
303
+ frame3 = gridFloor + vec2(1.0,1.0);
231
304
 
232
- vec2 frame1 = gridFloor;
233
- vec2 frame2 = gridFloor + mix(vec2(0,1),vec2(1,0),weights.w);
234
- vec2 frame3 = gridFloor + vec2(1.0,1.0);
305
+ vec3 projectedQuadADir = FrameXYToRay(frame1, framesMinusOne);
306
+ vec3 projectedQuadBDir = FrameXYToRay(frame2, framesMinusOne);
307
+ vec3 projectedQuadCDir = FrameXYToRay(frame3, framesMinusOne);
308
+
309
+ frame1_normal = (modelViewMatrix *vec4(projectedQuadADir, 0)).xyz;
310
+ frame2_normal = (modelViewMatrix *vec4(projectedQuadBDir, 0)).xyz;
311
+ frame3_normal = (modelViewMatrix *vec4(projectedQuadCDir, 0)).xyz;
235
312
 
236
313
  vec3 plane_x1, plane_y1, plane_x2, plane_y2, plane_x3, plane_y3;
237
- vec3 projectedQuadADir = FrameXYToRay(frame1, framesMinusOne);
314
+
238
315
  calcuateXYbasis(projectedQuadADir, plane_x1, plane_y1);
239
316
 
240
- uv_frame1 = virtualPlaneUV(projectedQuadADir, plane_x1, plane_y1, pivotToCameraRay, vertexToCameraRay, scale);
317
+ uv_frame1 = virtualPlaneUV(projectedQuadADir, plane_x1, plane_y1, pivotToCameraRay, vertexToCameraRay, uRadius);
241
318
  xy_frame1 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadADir, plane_x1, plane_y1).xy;
319
+
320
+ calcuateXYbasis(projectedQuadBDir, plane_x2, plane_y2);
321
+ uv_frame2 = virtualPlaneUV(projectedQuadBDir, plane_x2, plane_y2, pivotToCameraRay, vertexToCameraRay, uRadius);
322
+ xy_frame2 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadBDir, plane_x2, plane_y2).xy;
242
323
 
324
+ calcuateXYbasis(projectedQuadCDir, plane_x3, plane_y3);
325
+ uv_frame3 = virtualPlaneUV(projectedQuadCDir, plane_x3, plane_y3, pivotToCameraRay, vertexToCameraRay, uRadius);
326
+ xy_frame3 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadCDir, plane_x3, plane_y3).xy;
327
+
328
+
243
329
  }
244
330
  `;
245
331
  const shader_fg = `
@@ -269,6 +355,23 @@ const shader_fg = `
269
355
  in vec3 local_ray_near;
270
356
  in vec3 local_ray_far;
271
357
 
358
+ flat in vec2 frame1;
359
+ flat in vec3 frame1_normal;
360
+ in vec2 uv_frame1;
361
+ in vec2 xy_frame1;
362
+
363
+ flat in vec2 frame2;
364
+ flat in vec3 frame2_normal;
365
+ in vec2 uv_frame2;
366
+ in vec2 xy_frame2;
367
+
368
+ flat in vec2 frame3;
369
+ flat in vec3 frame3_normal;
370
+ in vec2 uv_frame3;
371
+ in vec2 xy_frame3;
372
+
373
+ in vec4 blend_weights;
374
+
272
375
  struct Material{
273
376
  vec3 diffuse;
274
377
  vec3 normal;
@@ -394,14 +497,14 @@ const shader_fg = `
394
497
  ));
395
498
  }
396
499
 
397
- vec2 recompute_uv(vec2 frame, vec2 frame_uv, sampler2D gBuffer){
500
+ vec2 recompute_uv(vec2 frame, vec2 frame_uv, vec2 xy_f, sampler2D gBuffer){
398
501
  vec2 frame_size = vec2(1.0/ uFrames);
399
502
 
400
503
  vec2 source_uv = (frame + frame_uv)*frame_size;
401
504
 
402
505
  float n_depth = texture(gBuffer, source_uv).a;
403
506
 
404
- vec2 offset = clamp(length(frame_uv*2.0 - 1.0) * vec2(0.5-n_depth ) * depth_scale,0.0, 1.0);
507
+ vec2 offset = clamp(xy_f * vec2(0.5-n_depth ) * depth_scale,0.0, 1.0);
405
508
 
406
509
  vec2 uv_f = clamp(frame_uv+offset, 0.0, 1.0);
407
510
 
@@ -411,6 +514,21 @@ const shader_fg = `
411
514
  // return source_uv;
412
515
  }
413
516
 
517
+ vec2 recalculateUV(vec2 uv_f, vec2 frame, vec2 xy_f, vec2 frame_size, float d_scale, sampler2D depthTexture)
518
+ {
519
+ //clamp for parallax sampling
520
+ uv_f = clamp(uv_f, vec2(0.0), vec2(1.0));
521
+ vec2 uv_quad = frame_size * (frame + uv_f);
522
+ //paralax
523
+ vec4 n_depth = (texture( depthTexture, uv_quad));
524
+ uv_f = xy_f * (0.5-n_depth.a) * d_scale + uv_f;
525
+ //clamp parallax offset
526
+ uv_f = clamp(uv_f, vec2(0.0), vec2(1.0));
527
+ uv_f = frame_size * (frame + uv_f);
528
+ //clamped full UV
529
+ return clamp(uv_f, vec2(0.0), vec2(1.0));
530
+ }
531
+
414
532
  void main(){
415
533
  vec3 view_direction = normalize(local_ray_near-local_ray_far);
416
534
 
@@ -420,19 +538,17 @@ const shader_fg = `
420
538
  vec2 gridFrac = fract(grid);
421
539
  vec2 gridFloor = floor(grid);
422
540
 
423
- vec4 weights = TriangleInterpolate( gridFrac );
541
+ vec4 weights = blend_weights;
424
542
 
425
543
  vec2 frame_uv = vUv;
426
544
 
427
545
  //3 nearest frames
428
- vec2 frame0 = gridFloor;
429
- vec2 frame1 = gridFloor + mix(vec2(0,1),vec2(1,0),weights.w);
430
- vec2 frame2 = gridFloor + vec2(1.0,1.0);
431
-
432
- vec2 uv0 = recompute_uv(frame0, frame_uv, tGeometry);
433
- vec2 uv1 = recompute_uv(frame1, frame_uv, tGeometry);
434
- vec2 uv2 = recompute_uv(frame2, frame_uv, tGeometry);
435
-
546
+ vec2 quad_size = vec2(1.0) / uFrames;
547
+ vec2 uv_f1 = recalculateUV(uv_frame1, frame1, xy_frame1, quad_size, depth_scale, tGeometry);
548
+ vec2 uv_f2 = recalculateUV(uv_frame2, frame2, xy_frame2, quad_size, depth_scale, tGeometry);
549
+ vec2 uv_f3 = recalculateUV(uv_frame3, frame3, xy_frame3, quad_size, depth_scale, tGeometry);
550
+
551
+
436
552
  vec4 ddxy = vec4( dFdx(vUv.xy), dFdy(vUv.xy) );
437
553
 
438
554
  vec2 frame_size = vec2(1.0/ uFrames);
@@ -440,9 +556,9 @@ const shader_fg = `
440
556
  vec4 texel_color = ImposterBlendWeights(
441
557
  // vec4 texel_color = ImposterBlendWeightsNearest(
442
558
  tBase,
443
- uv0,
444
- uv1,
445
- uv2,
559
+ uv_f1,
560
+ uv_f2,
561
+ uv_f3,
446
562
  weights, ddxy
447
563
  );
448
564
 
@@ -139,6 +139,10 @@ export class MicronShadedGeometryRenderAdapter extends AbstractRenderAdapter {
139
139
 
140
140
  scratch_mvc.material = sg.material;
141
141
  scratch_mvc.spec = micron_geometry.vertex_spec;
142
+
143
+ // clear all flags
144
+ scratch_mvc.flags = 0;
145
+
142
146
  scratch_mvc.writeFlag(MaterialVertexSpecFlags.CastShadow, sg.getFlag(ShadedGeometryFlags.CastShadow));
143
147
  scratch_mvc.writeFlag(MaterialVertexSpecFlags.ReceiveShadow, sg.getFlag(ShadedGeometryFlags.ReceiveShadow));
144
148
 
@@ -175,6 +179,7 @@ export class MicronShadedGeometryRenderAdapter extends AbstractRenderAdapter {
175
179
  // clear context
176
180
  const material_contexts = this.ctx.contexts_array;
177
181
  const contexts_count = material_contexts.length;
182
+
178
183
  for (let i = 0; i < contexts_count; i++) {
179
184
  const mc = material_contexts[i];
180
185
 
@@ -32,6 +32,12 @@ const EXPOSED_ATTRIBUTES = [
32
32
  "blending"
33
33
  ];
34
34
 
35
+ /**
36
+ * Maximum allowed number of particles per emitter, used as a sanity constraint to prevent renderer from crashing
37
+ * @type {number}
38
+ */
39
+ const LIMIT_PARTICLE_COUNT = 1000000;
40
+
35
41
  export class ParticleVolume {
36
42
 
37
43
  constructor() {
@@ -436,8 +442,13 @@ export class ParticleVolume {
436
442
  const volume_value = this.__shape.volume;
437
443
 
438
444
  // total particle count
439
- const particle_count = Math.max(1, Math.ceil(volume_value * this.__density));
445
+ let particle_count = Math.max(1, Math.ceil(volume_value * this.__density));
440
446
 
447
+ if (particle_count > LIMIT_PARTICLE_COUNT) {
448
+ console.warn(`ParticleVolume exceeds maximum allowed particle count (limit=${LIMIT_PARTICLE_COUNT}, requested=${particle_count}), clamping to limit. Consider reducing density (current = ${this.__density} particles per unit of volume)`);
449
+
450
+ particle_count = LIMIT_PARTICLE_COUNT;
451
+ }
441
452
 
442
453
  this.__ensure_initialized();
443
454
 
@@ -158,3 +158,24 @@ test("getPosition on a path with 1 point", () => {
158
158
 
159
159
  expect(v.toJSON()).toEqual({ x: 1, y: 3, z: 7 });
160
160
  });
161
+
162
+
163
+ test("sample_catmull_rom with very small offsets",()=>{
164
+ const path = Path.fromJSON({
165
+ points:[3, 1, 1, 3, 1, 3, 7, 1, 3, 7, 1, 7, 3, 1, 7, 3, 1, 8]
166
+ });
167
+
168
+ const v = new Vector3();
169
+
170
+ path.sample_catmull_rom(v,0);
171
+
172
+ expect(v.x).not.toBeNaN();
173
+ expect(v.y).not.toBeNaN();
174
+ expect(v.z).not.toBeNaN();
175
+
176
+ path.sample_catmull_rom(v,0.00005900000113712167);
177
+
178
+ expect(v.x).not.toBeNaN();
179
+ expect(v.y).not.toBeNaN();
180
+ expect(v.z).not.toBeNaN();
181
+ });
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.43.9",
8
+ "version": "2.43.11",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",
@@ -15,6 +15,9 @@ import { TerrainLayer } from "../../engine/ecs/terrain/ecs/layers/TerrainLayer.j
15
15
  import Mesh from "../../engine/graphics/ecs/mesh/Mesh.js";
16
16
  import { Transform } from "../../engine/ecs/transform/Transform.js";
17
17
  import { MeshSystem } from "../../engine/graphics/ecs/mesh/MeshSystem.js";
18
+ import { downloadAsFile } from "../../core/binary/ByteArrayTools.js";
19
+ import { url_to_data_url } from "../../core/binary/url_to_data_url.js";
20
+ import ButtonView from "../../view/elements/button/ButtonView.js";
18
21
 
19
22
  const HEIGHT_RANGE = 64;
20
23
 
@@ -149,6 +152,36 @@ async function main(engine) {
149
152
  load_gltf("moicon/23_Nov_21_Skogplanter/02/model.gltf", engine, transform);
150
153
  // load_gltf("moicon/23_Nov_21_Skogplanter/03/model.gltf", engine, transform);
151
154
  // load_gltf("moicon/23_Nov_21_Skogplanter/04/model.gltf", engine, transform);
155
+
156
+ engine.gui.view.addChild(new ButtonView({
157
+ name: 'Download',
158
+ async action() {
159
+ for (const layer of terrain.layers.layers.data) {
160
+ // embed textures
161
+ layer.textureDiffuseURL = await url_to_data_url(layer.textureDiffuseURL);
162
+ }
163
+
164
+ // we wrap the terrain data into "component json" format that meep editor recognizes to make this compatible with the standalone terrain editor
165
+ const json_payload = {
166
+ type: "Terrain",
167
+ data: terrain.toJSON()
168
+ };
169
+
170
+ downloadAsFile(
171
+ JSON.stringify(json_payload),
172
+ 'terrain.json'
173
+ );
174
+ },
175
+ css: {
176
+ bottom: '4px',
177
+ left: '4px',
178
+ position: 'absolute',
179
+ background: 'white',
180
+ border: '1px solid black',
181
+ padding: '2px',
182
+ pointerEvents: 'auto'
183
+ }
184
+ }));
152
185
  }
153
186
 
154
187
  /**