@woosh/meep-engine 2.40.0 → 2.41.0

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.
Files changed (68) hide show
  1. package/core/binary/BinaryBuffer.js +6 -1
  2. package/core/collection/HashMap.d.ts +2 -0
  3. package/core/collection/HashMap.js +22 -0
  4. package/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -0
  5. package/core/geom/3d/normal/octahedron/decode_octahedron_to_unit.js +31 -0
  6. package/core/geom/3d/normal/octahedron/encode_unit_to_octahedron.js +33 -0
  7. package/core/geom/3d/normal/octahedron/encoding.spec.js +29 -0
  8. package/core/geom/3d/topology/struct/BinaryTopology.js +112 -0
  9. package/core/geom/Quaternion.js +8 -8
  10. package/core/geom/Quaternion.spec.js +13 -0
  11. package/core/geom/Vector1.d.ts +2 -0
  12. package/core/math/sign_not_zero.js +8 -0
  13. package/core/parser/simple/SimpleParser.js +3 -86
  14. package/core/parser/simple/readUnsignedInteger.js +66 -0
  15. package/core/parser/simple/skipWhitespace.js +21 -0
  16. package/editor/view/ecs/components/common/NumberController.js +24 -6
  17. package/engine/EngineHarness.js +6 -1
  18. package/engine/asset/AssetDescription.spec.js +27 -0
  19. package/engine/asset/loaders/GLTFAssetLoader.js +5 -3
  20. package/engine/asset/loaders/image/png/PNG.js +6 -0
  21. package/engine/asset/loaders/image/png/PNGReader.js +41 -0
  22. package/engine/ecs/foliage/ImpostorFoliage.js +4 -0
  23. package/engine/ecs/terrain/tiles/TerrainTile.js +2 -0
  24. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +22 -7
  25. package/engine/graphics/filter/ImageFilter.js +2 -1
  26. package/engine/graphics/geometry/MikkT/MikkTSpace.spec.js +7 -1
  27. package/engine/{asset/loaders/geometry/geometry → graphics/geometry/buffered}/computeBufferAttributeHash.js +1 -1
  28. package/engine/{asset/loaders/geometry/geometry → graphics/geometry/buffered}/computeGeometryEquality.js +2 -2
  29. package/engine/{asset/loaders/geometry/geometry → graphics/geometry/buffered}/computeGeometryHash.js +1 -1
  30. package/engine/graphics/geometry/optimization/merge/merge_geometry_hierarchy.js +2 -2
  31. package/engine/graphics/impostors/card_cluster/FacePlaneAssignment.js +30 -0
  32. package/engine/graphics/impostors/card_cluster/README.md +13 -0
  33. package/engine/graphics/impostors/octahedral/ImpostorBaker.js +332 -0
  34. package/engine/graphics/impostors/octahedral/ImpostorCaptureType.js +10 -0
  35. package/engine/graphics/impostors/octahedral/ImpostorDescription.js +63 -0
  36. package/engine/graphics/impostors/octahedral/README.md +29 -0
  37. package/engine/graphics/impostors/octahedral/bake/compute_bounding_sphere.js +42 -0
  38. package/engine/graphics/impostors/octahedral/bake/prepare_bake_material.js +88 -0
  39. package/engine/graphics/impostors/octahedral/grid/HemiOctahedralUvEncoder.js +20 -0
  40. package/engine/graphics/impostors/octahedral/grid/OctahedralUvEncoder.js +25 -0
  41. package/engine/graphics/impostors/octahedral/grid/UvEncoder.js +21 -0
  42. package/engine/graphics/impostors/octahedral/prototypeBaker.js +138 -0
  43. package/engine/graphics/impostors/octahedral/shader/BakeShaderStandard.js +157 -0
  44. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderStandard.js +306 -0
  45. package/engine/graphics/impostors/octahedral/shader/glsl/common.glsl +206 -0
  46. package/engine/graphics/micron/convert_three_object_to_micron.js +2 -2
  47. package/engine/graphics/micron/plugin/MicronRenderPlugin.js +2 -2
  48. package/engine/graphics/particles/node-based/codegen/CodeGenerator.js +1 -1
  49. package/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.js +1 -1
  50. package/engine/graphics/render/forward_plus/LightManager.js +25 -0
  51. package/engine/graphics/render/forward_plus/data/TextureBackedMemoryRegion.js +6 -2
  52. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_ACCUMULATION.js +6 -11
  53. package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +268 -2
  54. package/engine/graphics/render/visibility/hiz/buildCanvasViewFromTexture.js +32 -10
  55. package/engine/graphics/shaders/ScreenSpaceQuadShader.js +4 -14
  56. package/engine/graphics/shaders/glsl_gen_swizzled_read.js +39 -0
  57. package/engine/graphics/texture/atlas/AtlasPatch.js +12 -6
  58. package/engine/logging/ConsoleLoggerBackend.js +15 -0
  59. package/engine/logging/Logger.js +24 -1
  60. package/engine/logging/LoggerBackend.js +1 -0
  61. package/engine/physics/fluid/FluidField.js +9 -0
  62. package/engine/physics/fluid/effector/AbstractFluidEffector.js +6 -0
  63. package/engine/physics/fluid/effector/GlobalFluidEffector.js +12 -0
  64. package/engine/physics/fluid/effector/WakeFluidEffector.js +8 -0
  65. package/engine/ui/GUIEngine.js +8 -1
  66. package/package.json +1 -1
  67. package/view/tooltip/gml/parser/readReferenceToken.js +1 -1
  68. package/engine/asset/GameAssetManager.js +0 -137
@@ -959,14 +959,19 @@ export class BinaryBuffer {
959
959
  /**
960
960
  *
961
961
  * @param {number} length
962
+ * @param {boolean} [null_terminated]
962
963
  * @returns {string}
963
964
  */
964
- readASCIICharacters(length) {
965
+ readASCIICharacters(length, null_terminated = false) {
965
966
  let result = "";
966
967
 
967
968
  for (let i = 0; i < length; i++) {
968
969
  const code = this.readUint8();
969
970
 
971
+ if (null_terminated && code === 0) {
972
+ break;
973
+ }
974
+
970
975
  result += String.fromCharCode(code);
971
976
  }
972
977
 
@@ -19,6 +19,8 @@ export class HashMap<K, V> implements Iterable<[V, K]> {
19
19
 
20
20
  get(key: K): V | undefined
21
21
 
22
+ getOrCompute(key: K, compute: (k: K) => V, thisArg?: any): V
23
+
22
24
  has(key: K): boolean
23
25
 
24
26
  delete(key: K): boolean
@@ -273,6 +273,28 @@ export class HashMap {
273
273
  return undefined;
274
274
  }
275
275
 
276
+ /**
277
+ *
278
+ * @param {Key} key
279
+ * @param {function(Key):V} compute
280
+ * @param {*} [compute_context]
281
+ * @return {V}
282
+ */
283
+ getOrCompute(key, compute, compute_context) {
284
+ const existing = this.get(key);
285
+
286
+ if (existing !== undefined) {
287
+ return existing;
288
+ }
289
+
290
+ const value = compute.call(compute_context, key);
291
+
292
+ this.set(key, value);
293
+
294
+ return value;
295
+ }
296
+
297
+
276
298
  /**
277
299
  *
278
300
  * @param {K} key
@@ -2,6 +2,8 @@
2
2
  * Assume normalized input on +Z hemisphere.
3
3
  * Output is on [-1, 1].
4
4
  *
5
+ * Uses hemi-octahedron for sphere approximation
6
+ *
5
7
  * @see A Survey of Efficient Representations for Independent Unit Vectors (Journal of Computer Graphics Techniques Vol. 3, No. 2, 2014)
6
8
  * @param {number[]} output
7
9
  * @param {number} output_offset
@@ -27,6 +29,9 @@ export function encode_unit_to_hemioct(output, output_offset, x, y, z) {
27
29
 
28
30
  /**
29
31
  * @see A Survey of Efficient Representations for Independent Unit Vectors (Journal of Computer Graphics Techniques Vol. 3, No. 2, 2014)
32
+ *
33
+ * Uses hemi-octahedron for sphere approximation
34
+ *
30
35
  * @param {number[]} output
31
36
  * @param {number} output_offset
32
37
  * @param {number} x range [-1,1]
@@ -0,0 +1,31 @@
1
+ import { sign_not_zero } from "../../../../math/sign_not_zero.js";
2
+
3
+ /**
4
+ * @see A Survey of Efficient Representations for Independent Unit Vectors (Journal of Computer Graphics Techniques Vol. 3, No. 2, 2014) - page 13
5
+ * @see https://gamedev.stackexchange.com/questions/169508/octahedral-impostors-octahedral-mapping
6
+ * @param {number[]} output
7
+ * @param {number} output_offset
8
+ * @param {number} x range [0,1]
9
+ * @param {number} y range [0,1]
10
+ */
11
+ export function decode_octahedron_to_unit(output, output_offset, x, y) {
12
+ let v_x = x;
13
+ let v_y = y;
14
+
15
+ const abs_x = Math.abs(v_x);
16
+ const abs_y = Math.abs(v_y);
17
+
18
+ let v_z = 1 - abs_x - abs_y;
19
+
20
+ if (v_z < 0) {
21
+ v_x = (1 - abs_y) * sign_not_zero(v_x);
22
+ v_y = (1 - abs_x) * sign_not_zero(v_y);
23
+ }
24
+
25
+ // normalize
26
+ const m = 1 / Math.hypot(v_x, v_y, v_z);
27
+
28
+ output[output_offset] = v_x * m;
29
+ output[output_offset + 1] = v_y * m;
30
+ output[output_offset + 2] = v_z * m;
31
+ }
@@ -0,0 +1,33 @@
1
+ import { sign_not_zero } from "../../../../math/sign_not_zero.js";
2
+
3
+ /**
4
+ * Input vector is assumed to be normalized
5
+ *
6
+ * @see A Survey of Efficient Representations for Independent Unit Vectors (Journal of Computer Graphics Techniques Vol. 3, No. 2, 2014) - page 13
7
+ * @see https://gamedev.stackexchange.com/questions/169508/octahedral-impostors-octahedral-mapping
8
+ *
9
+ * @param {number[]} output
10
+ * @param {number} output_offset
11
+ * @param {number} x range [-1,1]
12
+ * @param {number} y range [-1,1]
13
+ * @param {number} z range [-1,1]
14
+ */
15
+ export function encode_unit_to_octahedron(output, output_offset, x, y, z) {
16
+ // Project the sphere onto the octahedron, and then onto the xy plane
17
+ const l2 = 1 / (Math.abs(x) + Math.abs(y) + Math.abs(z));
18
+
19
+ let p_x = x * l2;
20
+ let p_y = y * l2;
21
+
22
+ // Reflect the folds of the lower hemisphere over the diagonals
23
+ if (z <= 0) {
24
+ const abs_x = Math.abs(p_x);
25
+ const abs_y = Math.abs(p_y);
26
+
27
+ p_x = sign_not_zero(p_x) * (1 - abs_y);
28
+ p_y = sign_not_zero(p_y) * (1 - abs_x);
29
+ }
30
+
31
+ output[output_offset] = p_x;
32
+ output[output_offset + 1] = p_y;
33
+ }
@@ -0,0 +1,29 @@
1
+ import { encode_unit_to_octahedron } from "./encode_unit_to_octahedron.js";
2
+ import { decode_octahedron_to_unit } from "./decode_octahedron_to_unit.js";
3
+
4
+ /**
5
+ *
6
+ * @param {number} x
7
+ * @param {number} y
8
+ */
9
+ function try_one(x, y) {
10
+ const output_v2 = [];
11
+ const output_v3 = [];
12
+
13
+ decode_octahedron_to_unit(output_v3, 0, x, y);
14
+ encode_unit_to_octahedron(output_v2, 0, output_v3[0], output_v3[1], output_v3[2]);
15
+
16
+ expect(output_v2[0]).toBeCloseTo(x);
17
+ expect(output_v2[1]).toBeCloseTo(y);
18
+ }
19
+
20
+ test("encoding/decoding consistency", () => {
21
+ // corners
22
+ try_one(0, 0);
23
+ try_one(0, 1);
24
+ try_one(1, 0);
25
+ try_one(1, 1);
26
+
27
+ // center
28
+ try_one(0.5, 0.5);
29
+ });
@@ -0,0 +1,112 @@
1
+ import { max2 } from "../../../../math/max2.js";
2
+
3
+ const INITIAL_CAPACITY = 128;
4
+
5
+ /**
6
+ * @readonly
7
+ * @type {number}
8
+ */
9
+ const CAPACITY_GROW_MULTIPLIER = 1.2;
10
+
11
+ /**
12
+ * @readonly
13
+ * @type {number}
14
+ */
15
+ const CAPACITY_GROW_MIN_STEP = 32;
16
+
17
+ class BinaryPool {
18
+
19
+ constructor(item_size, initial_capacity = INITIAL_CAPACITY) {
20
+ /**
21
+ * Size of a single pool item in bytes
22
+ * @type {number}
23
+ * @private
24
+ */
25
+ this.__item_size = item_size;
26
+ /**
27
+ * Unused slots
28
+ * @type {number[]}
29
+ * @private
30
+ */
31
+ this.__free = [];
32
+
33
+ /**
34
+ *
35
+ * @type {number}
36
+ * @private
37
+ */
38
+ this.__free_pointer = 0;
39
+
40
+ this.__data_buffer = new ArrayBuffer(initial_capacity * item_size);
41
+ this.__data_uint8 = new Uint8Array(this.__data_buffer);
42
+
43
+ this.__capacity = initial_capacity;
44
+
45
+ this.__used_end = 0;
46
+ }
47
+
48
+ __grow_capacity() {
49
+ const new_capacity = Math.ceil(max2(
50
+ this.__capacity * CAPACITY_GROW_MULTIPLIER,
51
+ this.__capacity + CAPACITY_GROW_MIN_STEP
52
+ ));
53
+
54
+ const old_data_uint8 = this.__data_uint8;
55
+
56
+ const new_data_buffer = new ArrayBuffer(new_capacity * this.__item_size);
57
+
58
+ this.__data_buffer = new_data_buffer;
59
+ this.__data_uint8 = new Uint8Array(new_data_buffer);
60
+
61
+ // copy old data
62
+ this.__data_uint8.set(old_data_uint8);
63
+
64
+ this.__capacity = new_capacity;
65
+ }
66
+
67
+
68
+ /**
69
+ *
70
+ * @return {number}
71
+ */
72
+ allocate() {
73
+ if (this.__free_pointer > 0) {
74
+ // get unused slot
75
+ this.__free_pointer--;
76
+ return this.__free[this.__free_pointer];
77
+ }
78
+
79
+ // allocate new
80
+
81
+ let result = this.__used_end;
82
+
83
+ if (result >= this.__capacity) {
84
+ this.__grow_capacity();
85
+ }
86
+
87
+ this.__used_end++;
88
+
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ *
94
+ * @param {number} id
95
+ */
96
+ release(id) {
97
+ this.__free[this.__free_pointer++] = id;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Heavily influenced by blender's internal mesh structure
103
+ * @see https://github.com/blender/blender/blob/master/source/blender/bmesh/bmesh_class.h
104
+ */
105
+ export class BinaryTopology {
106
+ __vertex_pool = new BinaryPool();
107
+ __edge_pool = new BinaryPool();
108
+ __loop_pool = new BinaryPool();
109
+ __face_pool = new BinaryPool();
110
+
111
+
112
+ }
@@ -1470,7 +1470,7 @@ class Quaternion {
1470
1470
 
1471
1471
  /**
1472
1472
  * Based on GDC talk from Bungie on destiny, compressing quaternions for animation
1473
- * @param value
1473
+ * @param {number} value
1474
1474
  */
1475
1475
  decodeFromUint32(value) {
1476
1476
  //read components
@@ -1481,9 +1481,9 @@ class Quaternion {
1481
1481
  const iv2 = (value >> 22) & 0x3FF;
1482
1482
 
1483
1483
  //scale components back to quaternion range
1484
- const v0 = (iv0 / 511.5 - 1) * K_CONST;
1485
- const v1 = (iv1 / 511.5 - 1) * K_CONST;
1486
- const v2 = (iv2 / 511.5 - 1) * K_CONST;
1484
+ const v0 = (iv0 / 511 - 1) * K_CONST;
1485
+ const v1 = (iv1 / 511 - 1) * K_CONST;
1486
+ const v2 = (iv2 / 511 - 1) * K_CONST;
1487
1487
 
1488
1488
  //restore dropped component using quaternion identity: x^2 + y^2 + z^2 + w^2 = 1
1489
1489
  const dropped_2 = 1 - v0 * v0 - v1 * v1 - v2 * v2;
@@ -1501,7 +1501,7 @@ class Quaternion {
1501
1501
  }
1502
1502
 
1503
1503
  /**
1504
- *
1504
+ * Based on GDC talk from Bungie on destiny, compressing quaternions for animation
1505
1505
  * @returns {number}
1506
1506
  */
1507
1507
  encodeToUint32() {
@@ -1590,9 +1590,9 @@ class Quaternion {
1590
1590
  const m = 1 / (l * K_CONST);
1591
1591
 
1592
1592
  //re-normalize the remaining components to 10 bit UINT value
1593
- const oV0 = ((v0 * m + 1) * 511.5) | 0;
1594
- const oV1 = ((v1 * m + 1) * 511.5) | 0;
1595
- const oV2 = ((v2 * m + 1) * 511.5) | 0;
1593
+ const oV0 = Math.round((v0 * m + 1) * 511);
1594
+ const oV1 = Math.round((v1 * m + 1) * 511);
1595
+ const oV2 = Math.round((v2 * m + 1) * 511);
1596
1596
 
1597
1597
  assert.ok(oV0 <= 1023 && oV0 >= 0, `expected 0 <= ov0 <= 1023, instead was '${oV0}'`);
1598
1598
  assert.ok(oV1 <= 1023 && oV1 >= 0, `expected 0 <= ov1 <= 1023, instead was '${oV1}'`);
@@ -154,6 +154,19 @@ test('encoding to Uint32 consistency', () => {
154
154
  check(1, 0, 0, 0);
155
155
  });
156
156
 
157
+ test('encoding to Uint32 and decoding identity correctly', () => {
158
+ const a = new Quaternion();
159
+ a.copy(Quaternion.identity);
160
+
161
+ const b = new Quaternion();
162
+
163
+ const encoded = a.encodeToUint32();
164
+
165
+ b.decodeFromUint32(encoded);
166
+
167
+ expect(b.toJSON()).toEqual(a.toJSON());
168
+ });
169
+
157
170
 
158
171
  test('to/from euler YXZ consistency', () => {
159
172
  function check(x, y, z) {
@@ -10,4 +10,6 @@ export default class Vector1 {
10
10
  _sub(v: number): void
11
11
 
12
12
  isZero(): boolean
13
+
14
+ process(callback: (x) => any): void
13
15
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Same as C "sign" function, but in case of input value being 0, returns +1
3
+ * @param {number} v
4
+ * @return {number}
5
+ */
6
+ export function sign_not_zero(v) {
7
+ return v >= 0 ? 1 : -1;
8
+ }
@@ -2,6 +2,8 @@ import DataType from "./DataType.js";
2
2
  import ParserError from "./ParserError.js";
3
3
  import Token from "./Token.js";
4
4
  import TokenType from "./TokenType.js";
5
+ import { readUnsignedInteger } from "./readUnsignedInteger.js";
6
+ import { skipWhitespace } from "./skipWhitespace.js";
5
7
 
6
8
 
7
9
  const RX_IDENTIFIER_CHAR = /^[a-zA-Z0-9_]/;
@@ -272,90 +274,6 @@ function readHex(text, cursor, length) {
272
274
  return new Token(value, cursor, i, null, DataType.Number);
273
275
  }
274
276
 
275
- /**
276
- *
277
- * @param {string} text
278
- * @param {number} cursor
279
- * @param {number} length
280
- * @returns {Token}
281
- */
282
- function readUnsignedInteger(text, cursor, length) {
283
- let i = cursor;
284
-
285
- let value = 0;
286
-
287
- main_loop: for (; i < length;) {
288
- const char = text.charAt(i);
289
- let digit;
290
- switch (char) {
291
- case '0':
292
- digit = 0;
293
- break;
294
- case '1':
295
- digit = 1;
296
- break;
297
- case '2':
298
- digit = 2;
299
- break;
300
- case '3':
301
- digit = 3;
302
- break;
303
- case '4':
304
- digit = 4;
305
- break;
306
- case '5':
307
- digit = 5;
308
- break;
309
- case '6':
310
- digit = 6;
311
- break;
312
- case '7':
313
- digit = 7;
314
- break;
315
- case '8':
316
- digit = 8;
317
- break;
318
- case '9':
319
- digit = 9;
320
- break;
321
- default:
322
- if (i === cursor) {
323
- //first character is not a digit
324
- throw new ParserError(i, `Expected a digit [0,1,2,3,4,5,6,7,8,9] but got '${char}' instead`, text);
325
- }
326
- //not a digit
327
- break main_loop;
328
- }
329
- i++;
330
- value = value * 10 + digit;
331
- }
332
-
333
-
334
- return new Token(value, cursor, i, null, DataType.Number);
335
- }
336
-
337
-
338
- /**
339
- *
340
- * @param {string} text
341
- * @param {number} cursor
342
- * @param {number} length
343
- * @returns {number}
344
- */
345
- export function skipWhitespace(text, cursor, length) {
346
- let i = cursor;
347
- let char;
348
- while (i < length) {
349
- char = text.charAt(i);
350
-
351
- if (char === ' ' || char === '\n' || char === '\t') {
352
- i++;
353
- } else {
354
- break;
355
- }
356
- }
357
- return i;
358
- }
359
277
 
360
278
  /**
361
279
  *
@@ -542,6 +460,5 @@ export {
542
460
  readLiteral,
543
461
  readNumber,
544
462
  readStringToken,
545
- readBoolean,
546
- readUnsignedInteger
463
+ readBoolean
547
464
  };
@@ -0,0 +1,66 @@
1
+ import ParserError from "./ParserError.js";
2
+ import Token from "./Token.js";
3
+ import DataType from "./DataType.js";
4
+
5
+ /**
6
+ *
7
+ * @param {string} text
8
+ * @param {number} cursor
9
+ * @param {number} length
10
+ * @returns {Token}
11
+ */
12
+ export function readUnsignedInteger(text, cursor, length) {
13
+ let i = cursor;
14
+
15
+ let value = 0;
16
+
17
+ main_loop: for (; i < length;) {
18
+ const char = text.charAt(i);
19
+ let digit;
20
+ switch (char) {
21
+ case '0':
22
+ digit = 0;
23
+ break;
24
+ case '1':
25
+ digit = 1;
26
+ break;
27
+ case '2':
28
+ digit = 2;
29
+ break;
30
+ case '3':
31
+ digit = 3;
32
+ break;
33
+ case '4':
34
+ digit = 4;
35
+ break;
36
+ case '5':
37
+ digit = 5;
38
+ break;
39
+ case '6':
40
+ digit = 6;
41
+ break;
42
+ case '7':
43
+ digit = 7;
44
+ break;
45
+ case '8':
46
+ digit = 8;
47
+ break;
48
+ case '9':
49
+ digit = 9;
50
+ break;
51
+ default:
52
+ if (i === cursor) {
53
+ //first character is not a digit
54
+ throw new ParserError(i, `Expected a digit [0,1,2,3,4,5,6,7,8,9] but got '${char}' instead`, text);
55
+ }
56
+ //not a digit
57
+ break main_loop;
58
+ }
59
+ i++;
60
+ value = value * 10 + digit;
61
+ }
62
+
63
+
64
+ return new Token(value, cursor, i, null, DataType.Number);
65
+ }
66
+
@@ -0,0 +1,21 @@
1
+ /**
2
+ *
3
+ * @param {string} text
4
+ * @param {number} cursor
5
+ * @param {number} length
6
+ * @returns {number}
7
+ */
8
+ export function skipWhitespace(text, cursor, length) {
9
+ let i = cursor;
10
+ let char;
11
+ while (i < length) {
12
+ char = text.charAt(i);
13
+
14
+ if (char === ' ' || char === '\n' || char === '\t') {
15
+ i++;
16
+ } else {
17
+ break;
18
+ }
19
+ }
20
+ return i;
21
+ }
@@ -24,7 +24,7 @@ export class NumberController extends View {
24
24
  const el = document.createElement('input');
25
25
  this.el = el;
26
26
 
27
- classList.forEach(c => this.addClass(c));
27
+ this.addClasses(classList);
28
28
 
29
29
  el.setAttribute('type', 'text');
30
30
  el.setAttribute('spellcheck', false);
@@ -34,6 +34,7 @@ export class NumberController extends View {
34
34
 
35
35
  let lockForward = false;
36
36
 
37
+
37
38
  /**
38
39
  *
39
40
  * @param {number} num_value
@@ -44,7 +45,26 @@ export class NumberController extends View {
44
45
  let str = String(num_value);
45
46
 
46
47
  if (Math.abs(num_value) > 0 && Math.abs(num_value % 1).toString().length > (figures + 2)) {
47
- str = num_value.toFixed(figures);
48
+ const long_form = num_value.toFixed(figures);
49
+
50
+ const separator_index = long_form.indexOf('.');
51
+
52
+ if (separator_index !== -1) {
53
+ // has a decimal fraction, remove trailing zeroes
54
+ let i = long_form.length - 1;
55
+
56
+ while (i > 0 && long_form.charAt(i) === "0") {
57
+ i--;
58
+ }
59
+
60
+ if (long_form.charAt(i) === '.') {
61
+ i--;
62
+ }
63
+
64
+ str = long_form.slice(0, i + 1);
65
+ } else {
66
+ str = long_form;
67
+ }
48
68
  }
49
69
 
50
70
  return str;
@@ -80,10 +100,8 @@ export class NumberController extends View {
80
100
  * Input field stops being edited
81
101
  */
82
102
  function handle_blur() {
83
- if (Number.isNaN(parseFloat(el.value))) {
84
- // input in the field is invalid, replace with the value held in the data container
85
- el.value = format_value_string(_value.getValue());
86
- }
103
+ // reformat contents of the input field
104
+ el.value = format_value_string(_value.getValue());
87
105
  }
88
106
 
89
107
  _value.process(data2view);
@@ -81,7 +81,7 @@ export class EngineHarness {
81
81
 
82
82
  window.engine = this.engine;
83
83
 
84
- logger.addBackend(new ConsoleLoggerBackend());
84
+ logger.addBackend(ConsoleLoggerBackend.INSTANCE);
85
85
 
86
86
  this.p = null;
87
87
  }
@@ -122,6 +122,10 @@ export class EngineHarness {
122
122
  document.body.appendChild(engine.viewStack.el);
123
123
  engine.viewStack.link();
124
124
 
125
+ // set css
126
+ document.body.style.margin = "0";
127
+ document.body.style.overflow = "hidden";
128
+
125
129
  function handleInput() {
126
130
  window.removeEventListener(MouseEvents.Down, handleInput);
127
131
 
@@ -437,6 +441,7 @@ export class EngineHarness {
437
441
 
438
442
  const eb = new EntityBuilder();
439
443
 
444
+ eb.add(new Transform())
440
445
  eb.add(terrain);
441
446
 
442
447
  if (enableWater) {
@@ -0,0 +1,27 @@
1
+ import { AssetDescription } from "./AssetDescription.js";
2
+
3
+ test('equality', () => {
4
+ const a = new AssetDescription('a', 'b');
5
+ const b = new AssetDescription('a', 'b');
6
+ const c = new AssetDescription('b', 'a');
7
+ const d = new AssetDescription('b', 'b');
8
+ const e = new AssetDescription('a', 'a');
9
+ const f = new AssetDescription('x', 'y');
10
+
11
+ expect(b.equals(a)).toBe(true);
12
+ expect(b.equals(c)).toBe(false);
13
+ expect(b.equals(d)).toBe(false);
14
+ expect(b.equals(e)).toBe(false);
15
+ expect(b.equals(f)).toBe(false);
16
+ });
17
+
18
+
19
+ test('hash stability', () => {
20
+ const a = new AssetDescription('a', 'b');
21
+
22
+ expect(a.hash()).toEqual(a.hash());
23
+
24
+ const b = new AssetDescription('a', 'b');
25
+
26
+ expect(a.hash()).toEqual(b.hash());
27
+ });