@woosh/meep-engine 2.42.8 → 2.43.1

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 (137) hide show
  1. package/core/binary/BitSet.js +4 -4
  2. package/core/binary/ctz32.js +40 -0
  3. package/core/collection/ObservedMap.js +61 -57
  4. package/core/collection/heap/FastBinaryHeap.js +7 -1
  5. package/core/collection/heap/Uint32Heap.js +19 -0
  6. package/core/collection/map/AsyncLoadingCache.js +3 -1
  7. package/core/geom/2d/compute_polygon_area_2d.js +32 -0
  8. package/core/geom/2d/compute_polygon_area_2d.spec.js +10 -0
  9. package/core/geom/2d/compute_triangle_area_2d.js +15 -0
  10. package/core/geom/2d/compute_triangle_area_2d.spec.js +9 -0
  11. package/core/geom/2d/convex-hull/convex_hull_jarvis_2d.js +64 -0
  12. package/core/geom/2d/convex-hull/convex_hull_jarvis_2d.spec.js +33 -0
  13. package/core/geom/2d/convex-hull/convex_hull_monotone_2d.js +82 -0
  14. package/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +135 -0
  15. package/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +282 -0
  16. package/core/geom/2d/convex-hull/orientation3.js +444 -0
  17. package/core/geom/2d/convex-hull/orientation3_array.js +22 -0
  18. package/core/geom/2d/convex-hull/orientation3_v2.js +12 -0
  19. package/core/geom/2d/intersect_ray_2d.js +56 -0
  20. package/core/geom/2d/quad-tree/QuadTreeNode.js +0 -81
  21. package/core/geom/2d/quad-tree/qt_match_data_by_circle.js +70 -0
  22. package/core/geom/3d/matrix/m4_multiply_alphatensor.js +131 -0
  23. package/core/geom/3d/plane/orient3d_fast.js +2 -6
  24. package/core/geom/3d/tetrahedra/README.md +7 -0
  25. package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.js +3 -1
  26. package/core/geom/3d/tetrahedra/delaunay/Cavity.js +48 -0
  27. package/core/geom/3d/tetrahedra/{compute_delaunay_tetrahedral_mesh.js → delaunay/compute_delaunay_tetrahedral_mesh.js} +15 -7
  28. package/core/geom/3d/tetrahedra/{compute_delaunay_tetrahedral_mesh.spec.js → delaunay/compute_delaunay_tetrahedral_mesh.spec.js} +0 -0
  29. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +73 -0
  30. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_walk_toward_cavity.js +48 -0
  31. package/core/geom/3d/tetrahedra/hxt/a.js +524 -0
  32. package/core/geom/3d/tetrahedra/hxt/hxt.js +140 -0
  33. package/core/geom/3d/tetrahedra/hxt/hxt.wasm +0 -0
  34. package/core/geom/3d/tetrahedra/point_in_tetrahedron_circumsphere.js +35 -20
  35. package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +98 -0
  36. package/core/geom/3d/tetrahedra/tetrahedra_collection.js +60 -131
  37. package/core/geom/packing/{MaxRectangles.js → max-rect/MaxRectangles.js} +28 -124
  38. package/core/geom/packing/max-rect/removeRedundantBoxes.js +69 -0
  39. package/core/geom/packing/max-rect/removeRedundantBoxesArray.js +40 -0
  40. package/core/geom/v3_distance_above_plane.js +1 -1
  41. package/core/graph/layout/BoxLayouter.js +2 -88
  42. package/core/graph/layout/CircleLayout.js +2 -1
  43. package/core/graph/layout/box/forceIntoBox.js +45 -0
  44. package/core/graph/layout/box/pullBoxTowardsPoint.js +20 -0
  45. package/core/graph/layout/box/resolveAABB2Overlap.js +22 -0
  46. package/core/math/bessel_3.js +11 -0
  47. package/core/math/bessel_i0.js +26 -0
  48. package/core/process/executor/ConcurrentExecutor.spec.js +2 -1
  49. package/core/process/task/util/actionTask.js +19 -0
  50. package/core/process/task/util/countTask.js +62 -0
  51. package/core/process/task/util/delayTask.js +45 -0
  52. package/core/process/task/util/emptyTask.js +19 -0
  53. package/core/process/task/util/failingTask.js +17 -0
  54. package/core/process/task/util/futureTask.js +48 -0
  55. package/core/process/task/util/promiseTask.js +42 -0
  56. package/core/process/task/util/randomCountTask.js +64 -0
  57. package/core/process/task/util/wrapTaskIgnoreFailure.js +47 -0
  58. package/engine/Engine.js +8 -8
  59. package/engine/EngineBootstrapper.js +1 -1
  60. package/engine/asset/AssetManager.d.ts +2 -0
  61. package/engine/asset/AssetManager.js +197 -53
  62. package/engine/asset/AssetRequest.js +32 -0
  63. package/engine/asset/loaders/ArrayBufferLoader.js +62 -50
  64. package/engine/asset/loaders/image/png/PNG.js +15 -1
  65. package/engine/asset/loaders/image/png/PNGReader.js +3 -2
  66. package/engine/ecs/foliage/ecs/InstancedMeshUtils.js +2 -1
  67. package/engine/ecs/storage/BinaryBufferDeSerializer.js +1 -1
  68. package/engine/ecs/storage/JSONDeSerializer.js +2 -1
  69. package/engine/ecs/terrain/ecs/splat/SplatMapOptimizer.js +2 -1
  70. package/engine/ecs/terrain/ecs/splat/SplatMapping.js +1 -1
  71. package/engine/graphics/camera/makeScreenScissorFrustum.js +1 -1
  72. package/engine/graphics/camera/testClippingPlaneComputation.js +4 -45
  73. package/engine/graphics/ecs/camera/FrustumProjector.js +6 -0
  74. package/engine/graphics/ecs/decal/v2/FPDecalSystem.js +5 -0
  75. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +23 -4
  76. package/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.js +1 -1
  77. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +11 -0
  78. package/engine/graphics/geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js +1 -2
  79. package/engine/graphics/impostors/octahedral/ImpostorBaker.js +5 -2
  80. package/engine/graphics/impostors/octahedral/ImpostorDescription.js +18 -0
  81. package/engine/graphics/impostors/octahedral/bake/prepare_bake_material.js +15 -0
  82. package/engine/graphics/impostors/octahedral/prototypeBaker.js +66 -79
  83. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.js +134 -0
  84. package/engine/graphics/impostors/octahedral/util/build_cutout_from_atlas_by_alpha.js +128 -0
  85. package/engine/graphics/impostors/octahedral/util/build_geometry_from_cutout_shape.js +32 -0
  86. package/engine/graphics/impostors/octahedral/util/load_mesh_for_bake.js +31 -0
  87. package/engine/graphics/impostors/octahedral/util/makeImpostorAtlasPreview.js +107 -0
  88. package/engine/graphics/material/manager/ManagedMaterial.js +4 -0
  89. package/engine/graphics/material/manager/MaterialManager.js +1 -0
  90. package/engine/graphics/material/optimization/MaterialOptimizationContext.js +7 -3
  91. package/engine/graphics/particles/particular/engine/renderers/billboard/ParticleBillboardMaterial.js +2 -2
  92. package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +2 -2
  93. package/engine/graphics/render/visibility/hiz/buildCanvasViewFromTexture.js +83 -27
  94. package/engine/graphics/shadows/ShadowMapRenderer.js +11 -4
  95. package/engine/graphics/texture/atlas/AbstractTextureAtlas.js +2 -1
  96. package/engine/graphics/texture/atlas/CachingTextureAtlas.js +208 -38
  97. package/engine/graphics/texture/atlas/TextureAtlas.js +31 -24
  98. package/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.js +1 -1
  99. package/engine/graphics/texture/sampler/filter/box.js +16 -0
  100. package/engine/graphics/texture/sampler/filter/cubic2.js +32 -0
  101. package/engine/graphics/texture/sampler/filter/gaussian.js +16 -0
  102. package/engine/graphics/texture/sampler/filter/kaiser_1.js +19 -0
  103. package/engine/graphics/texture/sampler/filter/kaiser_bessel_window.js +19 -0
  104. package/engine/graphics/texture/sampler/filter/mitchell.js +55 -0
  105. package/engine/graphics/texture/sampler/filter/sampler2d_scale_down_generic.js +109 -0
  106. package/engine/graphics/texture/sampler/filter/triangle.js +19 -0
  107. package/engine/graphics/texture/sampler/prototypeSamplerFiltering.js +187 -86
  108. package/engine/graphics/texture/sampler/sampler2_d_scale_down_lanczos.js +77 -25
  109. package/engine/graphics/texture/sampler/search/make_edge_condition_channel_threshold.js +34 -0
  110. package/engine/graphics/texture/sampler/search/sampler2d_find_pixels.js +24 -0
  111. package/engine/graphics/texture/sprite/prototypeSpriteCutoutGeometry.js +212 -0
  112. package/engine/knowledge/database/StaticKnowledgeDataTable.js +1 -1
  113. package/engine/navigation/grid/AStar.js +1 -1
  114. package/engine/scene/Scene.js +1 -1
  115. package/engine/scene/SerializedScene.js +1 -1
  116. package/engine/scene/transitionToScene.js +3 -1
  117. package/generation/example/main.js +1 -1
  118. package/generation/grid/generation/GridTaskApplyActionToCells.js +1 -1
  119. package/generation/grid/generation/GridTaskDensityMarkerDistribution.js +1 -1
  120. package/generation/grid/generation/GridTaskExecuteRuleTimes.js +1 -1
  121. package/generation/grid/generation/NoopGridTaskGenerator.js +1 -1
  122. package/generation/grid/generation/discrete/GridTaskCellularAutomata.js +2 -1
  123. package/generation/grid/generation/discrete/GridTaskConnectRooms.js +1 -1
  124. package/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +3 -2
  125. package/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.js +1 -1
  126. package/generation/grid/generation/grid/GridTaskAddNodesFixed.js +1 -1
  127. package/generation/grid/generation/road/GridTaskGenerateRoads.js +3 -2
  128. package/generation/grid/generation/util/buildDistanceMapToObjective.js +1 -1
  129. package/generation/markers/GridActionRuleSet.js +2 -1
  130. package/generation/placement/GridCellActionTransformNearbyMarkers.js +2 -4
  131. package/generation/theme/ThemeEngine.js +4 -1
  132. package/package.json +1 -1
  133. package/view/asset/AssetLoaderStatusView.js +5 -5
  134. package/view/minimap/gl/MinimapTerrainGL.js +1 -2
  135. package/view/renderModel.js +1 -1
  136. package/view/tooltip/TooltipView.js +5 -5
  137. package/core/process/task/TaskUtils.js +0 -352
@@ -5,9 +5,9 @@ import { min2 } from "../math/min2.js";
5
5
  /**
6
6
  * de Bruijn sequence
7
7
  * @see https://graphics.stanford.edu/~seander/bithacks.html
8
- * @type {Uint16Array}
8
+ * @type {Uint8Array}
9
9
  */
10
- const msb_lut = new Uint16Array([
10
+ const msb_lut = new Uint8Array([
11
11
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
12
12
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
13
13
  ]);
@@ -29,7 +29,8 @@ function msb_32(v) {
29
29
  }
30
30
 
31
31
  /**
32
- * get least significant set bit
32
+ * Get index of the least significant set bit
33
+ * Also known as ctz32 or "count trailing zeroes"
33
34
  * @see https://graphics.stanford.edu/~seander/bithacks.html
34
35
  * @param {number} v
35
36
  * @returns {number}
@@ -38,7 +39,6 @@ function lsb_32(v) {
38
39
  return msb_lut[((v & -v) * 0x077CB531) >>> 27];
39
40
  }
40
41
 
41
-
42
42
  /**
43
43
  * Used for overallocating space when bit set needs to grow
44
44
  * @constant
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Count trailing zeroes
3
+ * adapted from https://github.com/git/git/blob/bcd6bc478adc4951d57ec597c44b12ee74bc88fb/ewah/ewok.h#L44
4
+ * @param {number} x expected to be an integer
5
+ * @return {number}
6
+ */
7
+ function ctz32(x) {
8
+ let n = 0;
9
+
10
+ if ((x & 0xffff) === 0) {
11
+ x >>= 16;
12
+ n += 16;
13
+ }
14
+
15
+ if ((x & 0xff) === 0) {
16
+ x >>= 8;
17
+ n += 8;
18
+ }
19
+
20
+ if ((x & 0xf) === 0) {
21
+ x >>= 4;
22
+ n += 4;
23
+ }
24
+
25
+ if ((x & 0x3) === 0) {
26
+ x >>= 2;
27
+ n += 2;
28
+ }
29
+
30
+ if ((x & 0x1) === 0) {
31
+ x >>= 1;
32
+ n += 1;
33
+ }
34
+
35
+ if (x === 0) {
36
+ n += 1;
37
+ }
38
+
39
+ return n;
40
+ }
@@ -1,71 +1,75 @@
1
1
  import Signal from "../events/signal/Signal.js";
2
2
 
3
- /**
4
- * @template K,V
5
- * @constructor
6
- * @property {number} size
7
- */
8
- function ObservedMap(source = new Map()) {
9
- this.on = {
10
- set: new Signal(),
11
- deleted: new Signal()
12
- };
3
+ export class ObservedMap {
4
+ /**
5
+ * @template K,V
6
+ * @constructor
7
+ * @property {number} size
8
+ */
9
+ constructor(source = new Map()) {
10
+ this.on = {
11
+ set: new Signal(),
12
+ deleted: new Signal()
13
+ };
14
+
15
+ /**
16
+ *
17
+ * @type {Map<K, V>}
18
+ */
19
+ this.data = source;
20
+ }
13
21
 
14
22
  /**
15
23
  *
16
- * @type {Map<K, V>}
24
+ * @param {K} key
25
+ * @returns {V|undefined}
17
26
  */
18
- this.data = source;
19
- }
27
+ get(key) {
28
+ return this.data.get(key);
29
+ }
20
30
 
21
- Object.defineProperty(ObservedMap.prototype, "size", {
22
- get() {
23
- return this.data.size;
24
- },
25
- configurable: false
26
- });
31
+ /**
32
+ *
33
+ * @param {K} key
34
+ * @param {V} value
35
+ * @returns {ObservedMap}
36
+ */
37
+ set(key, value) {
38
+ this.data.set(key, value);
39
+ this.on.set.send2(key, value);
27
40
 
28
- /**
29
- *
30
- * @param {K} key
31
- * @returns {V|undefined}
32
- */
33
- ObservedMap.prototype.get = function (key) {
34
- return this.data.get(key);
35
- };
36
- /**
37
- *
38
- * @param {K} key
39
- * @param {V} value
40
- * @returns {ObservedMap}
41
- */
42
- ObservedMap.prototype.set = function (key, value) {
43
- this.data.set(key, value);
44
- this.on.set.dispatch(key, value);
41
+ return this;
42
+ }
45
43
 
46
- return this;
47
- };
44
+ /**
45
+ *
46
+ * @param {K} key
47
+ * @returns {boolean}
48
+ */
49
+ delete(key) {
50
+ const result = this.data.delete(key);
48
51
 
49
- /**
50
- *
51
- * @param {K} key
52
- * @returns {boolean}
53
- */
54
- ObservedMap.prototype.delete = function (key) {
55
- const result = this.data.delete(key);
52
+ if (result) {
53
+ this.on.deleted.send1(key);
54
+ }
56
55
 
57
- if (result) {
58
- this.on.deleted.dispatch(key);
56
+ return result;
59
57
  }
60
58
 
61
- return result;
62
- };
63
-
64
- ObservedMap.prototype.forEach = function (callback, thisArg) {
65
- this.data.forEach(callback, thisArg);
66
- };
67
-
59
+ /**
60
+ * Implements {@link Map#forEach} interface
61
+ * @param {function} callback
62
+ * @param {*} [thisArg]
63
+ */
64
+ forEach(callback, thisArg) {
65
+ this.data.forEach(callback, thisArg);
66
+ }
68
67
 
69
- export {
70
- ObservedMap
71
- };
68
+ /**
69
+ *
70
+ * @return {number}
71
+ */
72
+ get size() {
73
+ return this.data.size;
74
+ }
75
+ }
@@ -242,11 +242,17 @@ class BinaryHeap {
242
242
  /**
243
243
  *
244
244
  * @param {T} node
245
+ * @returns {boolean} false if element is not found, true otherwise
245
246
  */
246
- rescoreElement(node) {
247
+ updateElementScore(node) {
247
248
  const index = this.data.indexOf(node);
248
249
 
250
+ if (index === -1) {
251
+ return false;
252
+ }
253
+
249
254
  this.bubbleDown(index);
255
+ return true;
250
256
  }
251
257
 
252
258
  /**
@@ -229,6 +229,10 @@ export class Uint32Heap {
229
229
  return this.__size === 0;
230
230
  }
231
231
 
232
+ peek_min() {
233
+ return this.top_id;
234
+ }
235
+
232
236
  pop_min() {
233
237
  assert.greaterThan(this.__size, 0, 'heap is empty');
234
238
 
@@ -337,6 +341,21 @@ export class Uint32Heap {
337
341
  }
338
342
  }
339
343
 
344
+ /**
345
+ *
346
+ * @param {number} id
347
+ * @returns {number}
348
+ */
349
+ get_score(id) {
350
+ const index = this.find_index_by_id(id);
351
+
352
+ if (index === -1) {
353
+ return Number.NaN;
354
+ }
355
+
356
+ return this.__data_float32[index * 2];
357
+ }
358
+
340
359
 
341
360
  /**
342
361
  *
@@ -39,7 +39,9 @@ export class AsyncLoadingCache extends AbstractAsyncMap {
39
39
  const value = await promise;
40
40
 
41
41
  if (original_loader) {
42
- this.__cache.get(key, value);
42
+ this.__cache.set(key, value);
43
+ // remove from pending
44
+ this.__pending.delete(key);
43
45
  }
44
46
 
45
47
  return value;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Uses Green's Theorem to compute area of a 2d polygon
3
+ * @param {number[]|Float32Array|Float64Array} points
4
+ * @param {number} point_count
5
+ * @returns {number}
6
+ */
7
+ export function compute_polygon_area_2d(points, point_count) {
8
+ let sum = 0;
9
+
10
+ for (let i = 0; i < point_count - 1; i++) {
11
+ const x0 = points[(i) * 2];
12
+ const y0 = points[(i) * 2 + 1];
13
+
14
+ const x1 = points[(i + 1) * 2];
15
+ const y1 = points[(i + 1) * 2 + 1];
16
+
17
+ sum += x0 * y1 - y0 * x1;
18
+ }
19
+
20
+ const first_x = points[0];
21
+ const first_y = points[1];
22
+
23
+ const last_x = points[point_count * 2 - 2];
24
+ const last_y = points[point_count * 2 - 1];
25
+
26
+ if (first_x !== last_x || first_y !== last_y) {
27
+ // doesn't wrap around, let's close the loop ourselves
28
+ sum += last_x * first_y - last_y * first_x;
29
+ }
30
+
31
+ return sum * 0.5;
32
+ }
@@ -0,0 +1,10 @@
1
+ import { compute_polygon_area_2d } from "./compute_polygon_area_2d.js";
2
+
3
+ test("square", () => {
4
+ expect(compute_polygon_area_2d([
5
+ 0, 0,
6
+ 1, 0,
7
+ 1, 1,
8
+ 0, 1
9
+ ], 4)).toBeCloseTo(1);
10
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Uses Green's Theorem to compute area of a 2d triangle
3
+ * @param {number} x0
4
+ * @param {number} y0
5
+ * @param {number} x1
6
+ * @param {number} y1
7
+ * @param {number} x2
8
+ * @param {number} y2
9
+ * @returns {number}
10
+ */
11
+ export function compute_triangle_area_2d(x0, y0, x1, y1, x2, y2) {
12
+ return 0.5 * (
13
+ (x0 * y1 - y0 * x1) + (x1 * y2 - y1 * x2) + (x2 * y0 - y2 * x0)
14
+ );
15
+ }
@@ -0,0 +1,9 @@
1
+ import { compute_triangle_area_2d } from "./compute_triangle_area_2d.js";
2
+
3
+ test("half-square", () => {
4
+ expect(compute_triangle_area_2d(
5
+ 0, 0,
6
+ 1, 0,
7
+ 1, 1
8
+ )).toBeCloseTo(0.5);
9
+ });
@@ -0,0 +1,64 @@
1
+ import { assert } from "../../../assert.js";
2
+ import { orientation3_array } from "./orientation3_array.js";
3
+
4
+ /**
5
+ * Simple algorithm for computing convex hull, not the fastest, but it does the job and has very small implementation
6
+ * Please note that the result does not repeat the first vertex, meaning that the last point is not the same as the first point
7
+ * NOTE: complexity is O(nh) where h is the number of point on the hull and n is number of input points
8
+ * @see https://en.wikipedia.org/wiki/Gift_wrapping_algorithm
9
+ * @param {number[]|Float32Array|Float64Array} input 2d point set, of form [x0,y0,x1,y1, ... xn,yn]
10
+ * @param {number} point_count number of points in the input set
11
+ * @returns {number[]} indices of vertices from input that make up the hull, this result is ordered to form a closed loop around the hull if followed
12
+ */
13
+ export function convex_hull_jarvis_2d(input, point_count = input.length * 0.5) {
14
+ assert.isNumber(point_count, 'point_count');
15
+ assert.isNonNegativeInteger(point_count, 'point_count');
16
+
17
+ const jarvis_vertices = [];
18
+
19
+ if (point_count <= 2) {
20
+ // special case, very small input
21
+ for (let i = 0; i < point_count; i++) {
22
+ jarvis_vertices.push(i);
23
+ }
24
+
25
+ return jarvis_vertices;
26
+ }
27
+
28
+ // find left-most point
29
+ let l = 0;
30
+
31
+ for (let i = 0; i < point_count; i++) {
32
+ const i2 = i * 2;
33
+ const l2 = l * 2;
34
+
35
+ const x_i = input[i2];
36
+ const x_l = input[l2];
37
+
38
+ if (x_i < x_l) {
39
+ l = i;
40
+ }
41
+ }
42
+
43
+ let p = l, q;
44
+
45
+ do {
46
+ jarvis_vertices.push(p);
47
+
48
+ q = (p + 1) % point_count;
49
+
50
+ for (let i = 0; i < point_count; i++) {
51
+ if (orientation3_array(input, p, q, i) > 0) {
52
+ q = i;
53
+ }
54
+ }
55
+
56
+ p = q;
57
+
58
+ } while (
59
+ input[p * 2] !== input[l * 2]
60
+ || input[p * 2 + 1] !== input[l * 2 + 1]
61
+ );
62
+
63
+ return jarvis_vertices;
64
+ }
@@ -0,0 +1,33 @@
1
+ import { convex_hull_jarvis_2d } from "./convex_hull_jarvis_2d.js";
2
+
3
+ test("box with extra points", () => {
4
+ const hull = convex_hull_jarvis_2d([
5
+ 0, 1,
6
+ -1, 0,
7
+ 1, 0,
8
+ 0, -1,
9
+ // extra points inside
10
+ 0.3, 0.3,
11
+ -0.3, -0.3,
12
+ 0.3, -0.3,
13
+ -0.3, 0.3
14
+ ]);
15
+
16
+ expect(hull.length).toBe(4);
17
+
18
+ expect(hull).toContain(0);
19
+ expect(hull).toContain(1);
20
+ expect(hull).toContain(2);
21
+ expect(hull).toContain(3);
22
+ });
23
+
24
+
25
+ test("exact triangle", () => {
26
+ const hull = convex_hull_jarvis_2d([
27
+ 0, 0,
28
+ 1, 0,
29
+ 1, 1,
30
+ ]);
31
+
32
+ expect(hull.length).toBe(3);
33
+ });
@@ -0,0 +1,82 @@
1
+ import { orientation3_v2 } from "./orientation3_v2.js";
2
+
3
+ /**
4
+ * Ported from https://github.com/mikolalysenko/monotone-convex-hull-2d
5
+ * @param {number[][]} points
6
+ * @returns {number}
7
+ */
8
+ export function convex_hull_monotone_2d(points) {
9
+ var n = points.length
10
+
11
+ if (n < 3) {
12
+ var result = new Array(n)
13
+ for (var i = 0; i < n; ++i) {
14
+ result[i] = i
15
+ }
16
+
17
+ if (n === 2 &&
18
+ points[0][0] === points[1][0] &&
19
+ points[0][1] === points[1][1]) {
20
+ return [0]
21
+ }
22
+
23
+ return result
24
+ }
25
+
26
+ //Sort point indices along x-axis
27
+ var sorted = new Array(n)
28
+ for (var i = 0; i < n; ++i) {
29
+ sorted[i] = i
30
+ }
31
+ sorted.sort(function (a, b) {
32
+ var d = points[a][0] - points[b][0]
33
+ if (d) {
34
+ return d
35
+ }
36
+ return points[a][1] - points[b][1]
37
+ })
38
+
39
+ //Construct upper and lower hulls
40
+ var lower = [sorted[0], sorted[1]]
41
+ var upper = [sorted[0], sorted[1]]
42
+
43
+ for (var i = 2; i < n; ++i) {
44
+ var idx = sorted[i]
45
+ var p = points[idx]
46
+
47
+ //Insert into lower list
48
+ var m = lower.length
49
+ while (m > 1 && orientation3_v2(
50
+ points[lower[m - 2]],
51
+ points[lower[m - 1]],
52
+ p) <= 0) {
53
+ m -= 1
54
+ lower.pop()
55
+ }
56
+ lower.push(idx)
57
+
58
+ //Insert into upper list
59
+ m = upper.length
60
+ while (m > 1 && orient(
61
+ points[upper[m - 2]],
62
+ points[upper[m - 1]],
63
+ p) >= 0) {
64
+ m -= 1
65
+ upper.pop()
66
+ }
67
+ upper.push(idx)
68
+ }
69
+
70
+ //Merge lists together
71
+ var result = new Array(upper.length + lower.length - 2)
72
+ var ptr = 0
73
+ for (var i = 0, nl = lower.length; i < nl; ++i) {
74
+ result[ptr++] = lower[i]
75
+ }
76
+ for (var j = upper.length - 2; j > 0; --j) {
77
+ result[ptr++] = upper[j]
78
+ }
79
+
80
+ //Return result
81
+ return result
82
+ }
@@ -0,0 +1,135 @@
1
+ //
2
+
3
+ import { array_copy } from "../../../collection/array/copyArray.js";
4
+ import { compute_polygon_area_2d } from "../compute_polygon_area_2d.js";
5
+ import { intersect_ray_2d } from "../intersect_ray_2d.js";
6
+ import { UintArrayForCount } from "../../../collection/array/typed/uint_array_for_count.js";
7
+
8
+ /**
9
+ * Compute a bounding polygon with a fixed given number of vertices such that the area would be minimal
10
+ * Uses combinatorial sets of edges to find the subset, which when extending those edges produces poly with minimum area
11
+ * NOTE: very very slow as it's O(n^k) where n is number of edges and k is number of points on the desired bounding polygon
12
+ *
13
+ * @param {number[]|Float32Array} output result will be written here
14
+ * @param {number} output_offset offset into output array where to write the result
15
+ * @param {number} output_point_count number of points to comprise bounding polygon
16
+ * @param {number[]} input_points Points to be bounded, must be an ordered loop
17
+ * @param {number} input_point_count number of points in the input to consider
18
+ * @returns {number} actual number of points, this can be lower because input polygon has too few points
19
+ *
20
+ * @see 2014 "Implementation of linear minimum area enclosing triangle algorithm" Ovidiu Pârvu & David Gilbert
21
+ * @see https://github.com/MagnusTiberius/humus3/blob/9d43412d6e6be869e224817f0a966fe48a1af40f/Util/ConvexHull.cpp#L281
22
+ */
23
+ export function fixed_convex_hull_humus(
24
+ output, output_offset, output_point_count,
25
+ input_points, input_point_count
26
+ ) {
27
+ if (output_point_count >= input_point_count) {
28
+ // special case, input polygon is smaller or equal to in cardinality to what's desired
29
+ array_copy(input_points, 0, output, output_offset, input_point_count * 2);
30
+ return input_point_count;
31
+ }
32
+
33
+ /*
34
+ The following code iterated k-size sets of elements in the set of edges
35
+ @see https://stackoverflow.com/questions/29910312/algorithm-to-get-all-the-combinations-of-size-n-from-an-array-java
36
+ @see https://en.wikipedia.org/wiki/Combination
37
+ */
38
+
39
+ const edge_count = input_point_count;
40
+ const k = output_point_count;
41
+
42
+ const ArrayConstructor = UintArrayForCount(k);
43
+
44
+ const s = new ArrayConstructor(k);
45
+
46
+ let combos = 0;
47
+ let combos_valid = 0;
48
+
49
+ const intersections = new Float64Array(k * 2);
50
+ let best_area = Infinity;
51
+
52
+ /**
53
+ *
54
+ * @param {number[]|Uint16Array} subset
55
+ */
56
+ function process_combination(subset) {
57
+ combos++;
58
+
59
+ for (let i = 0; i < k; i++) {
60
+ const i0 = subset[i];
61
+ const i1 = subset[(i + 1) % k];
62
+
63
+ const a0x = input_points[i0 * 2];
64
+ const a0y = input_points[i0 * 2 + 1];
65
+
66
+ const a1 = (i0 + 1) % input_point_count;
67
+
68
+ const a1x = input_points[a1 * 2];
69
+ const a1y = input_points[a1 * 2 + 1];
70
+
71
+ const b0x = input_points[i1 * 2];
72
+ const b0y = input_points[i1 * 2 + 1];
73
+ const b1 = (i1 + 1) % input_point_count;
74
+ const b1x = input_points[b1 * 2];
75
+ const b1y = input_points[b1 * 2 + 1];
76
+
77
+ const a_dir_x = a1x - a0x;
78
+ const a_dir_y = a1y - a0y;
79
+
80
+ const b_dir_x = b1x - b0x;
81
+ const b_dir_y = b1y - b0y;
82
+
83
+ if (!intersect_ray_2d(
84
+ intersections, i * 2,
85
+ a0x, a0y, a_dir_x, a_dir_y,
86
+ b0x, b0y, b_dir_x, b_dir_y
87
+ )) {
88
+ // invalid combination, edges do not meet
89
+ return;
90
+ }
91
+ }
92
+
93
+ // we got intersection points, lets compute area
94
+ const area = compute_polygon_area_2d(intersections, k);
95
+
96
+ if (area < best_area) {
97
+ array_copy(intersections, 0, output, output_offset, k * 2);
98
+ best_area = area;
99
+ }
100
+
101
+ combos_valid++;
102
+ }
103
+
104
+
105
+ for (let i = 0; (s[i] = i) < k - 1; i++) {
106
+ // populate initial set
107
+ }
108
+
109
+ process_combination(s);
110
+
111
+ for (; ;) {
112
+ let i;
113
+
114
+ for (i = k - 1; i >= 0 && s[i] === edge_count - k + i; i--) {
115
+ // find position of item that can be incremented
116
+ }
117
+
118
+ if (i < 0) {
119
+ break;
120
+ }
121
+
122
+ s[i]++; // increment this item
123
+
124
+ for (++i; i < k; i++) {
125
+ // fill up remaining items
126
+ s[i] = s[i - 1] + 1;
127
+ }
128
+
129
+ process_combination(s);
130
+ }
131
+
132
+ console.log(`combinations: ${combos}, valid: ${combos_valid} (${(combos_valid * 100 / combos).toFixed(2)}%)`);
133
+
134
+ return output_point_count;
135
+ }