@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.
- package/core/binary/BitSet.js +4 -4
- package/core/binary/ctz32.js +40 -0
- package/core/collection/ObservedMap.js +61 -57
- package/core/collection/heap/FastBinaryHeap.js +7 -1
- package/core/collection/heap/Uint32Heap.js +19 -0
- package/core/collection/map/AsyncLoadingCache.js +3 -1
- package/core/geom/2d/compute_polygon_area_2d.js +32 -0
- package/core/geom/2d/compute_polygon_area_2d.spec.js +10 -0
- package/core/geom/2d/compute_triangle_area_2d.js +15 -0
- package/core/geom/2d/compute_triangle_area_2d.spec.js +9 -0
- package/core/geom/2d/convex-hull/convex_hull_jarvis_2d.js +64 -0
- package/core/geom/2d/convex-hull/convex_hull_jarvis_2d.spec.js +33 -0
- package/core/geom/2d/convex-hull/convex_hull_monotone_2d.js +82 -0
- package/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +135 -0
- package/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +282 -0
- package/core/geom/2d/convex-hull/orientation3.js +444 -0
- package/core/geom/2d/convex-hull/orientation3_array.js +22 -0
- package/core/geom/2d/convex-hull/orientation3_v2.js +12 -0
- package/core/geom/2d/intersect_ray_2d.js +56 -0
- package/core/geom/2d/quad-tree/QuadTreeNode.js +0 -81
- package/core/geom/2d/quad-tree/qt_match_data_by_circle.js +70 -0
- package/core/geom/3d/matrix/m4_multiply_alphatensor.js +131 -0
- package/core/geom/3d/plane/orient3d_fast.js +2 -6
- package/core/geom/3d/tetrahedra/README.md +7 -0
- package/core/geom/3d/tetrahedra/compute_bounding_simplex_3d.js +3 -1
- package/core/geom/3d/tetrahedra/delaunay/Cavity.js +48 -0
- package/core/geom/3d/tetrahedra/{compute_delaunay_tetrahedral_mesh.js → delaunay/compute_delaunay_tetrahedral_mesh.js} +15 -7
- package/core/geom/3d/tetrahedra/{compute_delaunay_tetrahedral_mesh.spec.js → delaunay/compute_delaunay_tetrahedral_mesh.spec.js} +0 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +73 -0
- package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_walk_toward_cavity.js +48 -0
- package/core/geom/3d/tetrahedra/hxt/a.js +524 -0
- package/core/geom/3d/tetrahedra/hxt/hxt.js +140 -0
- package/core/geom/3d/tetrahedra/hxt/hxt.wasm +0 -0
- package/core/geom/3d/tetrahedra/point_in_tetrahedron_circumsphere.js +35 -20
- package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +98 -0
- package/core/geom/3d/tetrahedra/tetrahedra_collection.js +60 -131
- package/core/geom/packing/{MaxRectangles.js → max-rect/MaxRectangles.js} +28 -124
- package/core/geom/packing/max-rect/removeRedundantBoxes.js +69 -0
- package/core/geom/packing/max-rect/removeRedundantBoxesArray.js +40 -0
- package/core/geom/v3_distance_above_plane.js +1 -1
- package/core/graph/layout/BoxLayouter.js +2 -88
- package/core/graph/layout/CircleLayout.js +2 -1
- package/core/graph/layout/box/forceIntoBox.js +45 -0
- package/core/graph/layout/box/pullBoxTowardsPoint.js +20 -0
- package/core/graph/layout/box/resolveAABB2Overlap.js +22 -0
- package/core/math/bessel_3.js +11 -0
- package/core/math/bessel_i0.js +26 -0
- package/core/process/executor/ConcurrentExecutor.spec.js +2 -1
- package/core/process/task/util/actionTask.js +19 -0
- package/core/process/task/util/countTask.js +62 -0
- package/core/process/task/util/delayTask.js +45 -0
- package/core/process/task/util/emptyTask.js +19 -0
- package/core/process/task/util/failingTask.js +17 -0
- package/core/process/task/util/futureTask.js +48 -0
- package/core/process/task/util/promiseTask.js +42 -0
- package/core/process/task/util/randomCountTask.js +64 -0
- package/core/process/task/util/wrapTaskIgnoreFailure.js +47 -0
- package/engine/Engine.js +8 -8
- package/engine/EngineBootstrapper.js +1 -1
- package/engine/asset/AssetManager.d.ts +2 -0
- package/engine/asset/AssetManager.js +197 -53
- package/engine/asset/AssetRequest.js +32 -0
- package/engine/asset/loaders/ArrayBufferLoader.js +62 -50
- package/engine/asset/loaders/image/png/PNG.js +15 -1
- package/engine/asset/loaders/image/png/PNGReader.js +3 -2
- package/engine/ecs/foliage/ecs/InstancedMeshUtils.js +2 -1
- package/engine/ecs/storage/BinaryBufferDeSerializer.js +1 -1
- package/engine/ecs/storage/JSONDeSerializer.js +2 -1
- package/engine/ecs/terrain/ecs/splat/SplatMapOptimizer.js +2 -1
- package/engine/ecs/terrain/ecs/splat/SplatMapping.js +1 -1
- package/engine/graphics/camera/makeScreenScissorFrustum.js +1 -1
- package/engine/graphics/camera/testClippingPlaneComputation.js +4 -45
- package/engine/graphics/ecs/camera/FrustumProjector.js +6 -0
- package/engine/graphics/ecs/decal/v2/FPDecalSystem.js +5 -0
- package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +23 -4
- package/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.js +1 -1
- package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +11 -0
- package/engine/graphics/geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js +1 -2
- package/engine/graphics/impostors/octahedral/ImpostorBaker.js +5 -2
- package/engine/graphics/impostors/octahedral/ImpostorDescription.js +18 -0
- package/engine/graphics/impostors/octahedral/bake/prepare_bake_material.js +15 -0
- package/engine/graphics/impostors/octahedral/prototypeBaker.js +66 -79
- package/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.js +134 -0
- package/engine/graphics/impostors/octahedral/util/build_cutout_from_atlas_by_alpha.js +128 -0
- package/engine/graphics/impostors/octahedral/util/build_geometry_from_cutout_shape.js +32 -0
- package/engine/graphics/impostors/octahedral/util/load_mesh_for_bake.js +31 -0
- package/engine/graphics/impostors/octahedral/util/makeImpostorAtlasPreview.js +107 -0
- package/engine/graphics/material/manager/ManagedMaterial.js +4 -0
- package/engine/graphics/material/manager/MaterialManager.js +1 -0
- package/engine/graphics/material/optimization/MaterialOptimizationContext.js +7 -3
- package/engine/graphics/particles/particular/engine/renderers/billboard/ParticleBillboardMaterial.js +2 -2
- package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +2 -2
- package/engine/graphics/render/visibility/hiz/buildCanvasViewFromTexture.js +83 -27
- package/engine/graphics/shadows/ShadowMapRenderer.js +11 -4
- package/engine/graphics/texture/atlas/AbstractTextureAtlas.js +2 -1
- package/engine/graphics/texture/atlas/CachingTextureAtlas.js +208 -38
- package/engine/graphics/texture/atlas/TextureAtlas.js +31 -24
- package/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.js +1 -1
- package/engine/graphics/texture/sampler/filter/box.js +16 -0
- package/engine/graphics/texture/sampler/filter/cubic2.js +32 -0
- package/engine/graphics/texture/sampler/filter/gaussian.js +16 -0
- package/engine/graphics/texture/sampler/filter/kaiser_1.js +19 -0
- package/engine/graphics/texture/sampler/filter/kaiser_bessel_window.js +19 -0
- package/engine/graphics/texture/sampler/filter/mitchell.js +55 -0
- package/engine/graphics/texture/sampler/filter/sampler2d_scale_down_generic.js +109 -0
- package/engine/graphics/texture/sampler/filter/triangle.js +19 -0
- package/engine/graphics/texture/sampler/prototypeSamplerFiltering.js +187 -86
- package/engine/graphics/texture/sampler/sampler2_d_scale_down_lanczos.js +77 -25
- package/engine/graphics/texture/sampler/search/make_edge_condition_channel_threshold.js +34 -0
- package/engine/graphics/texture/sampler/search/sampler2d_find_pixels.js +24 -0
- package/engine/graphics/texture/sprite/prototypeSpriteCutoutGeometry.js +212 -0
- package/engine/knowledge/database/StaticKnowledgeDataTable.js +1 -1
- package/engine/navigation/grid/AStar.js +1 -1
- package/engine/scene/Scene.js +1 -1
- package/engine/scene/SerializedScene.js +1 -1
- package/engine/scene/transitionToScene.js +3 -1
- package/generation/example/main.js +1 -1
- package/generation/grid/generation/GridTaskApplyActionToCells.js +1 -1
- package/generation/grid/generation/GridTaskDensityMarkerDistribution.js +1 -1
- package/generation/grid/generation/GridTaskExecuteRuleTimes.js +1 -1
- package/generation/grid/generation/NoopGridTaskGenerator.js +1 -1
- package/generation/grid/generation/discrete/GridTaskCellularAutomata.js +2 -1
- package/generation/grid/generation/discrete/GridTaskConnectRooms.js +1 -1
- package/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +3 -2
- package/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.js +1 -1
- package/generation/grid/generation/grid/GridTaskAddNodesFixed.js +1 -1
- package/generation/grid/generation/road/GridTaskGenerateRoads.js +3 -2
- package/generation/grid/generation/util/buildDistanceMapToObjective.js +1 -1
- package/generation/markers/GridActionRuleSet.js +2 -1
- package/generation/placement/GridCellActionTransformNearbyMarkers.js +2 -4
- package/generation/theme/ThemeEngine.js +4 -1
- package/package.json +1 -1
- package/view/asset/AssetLoaderStatusView.js +5 -5
- package/view/minimap/gl/MinimapTerrainGL.js +1 -2
- package/view/renderModel.js +1 -1
- package/view/tooltip/TooltipView.js +5 -5
- package/core/process/task/TaskUtils.js +0 -352
package/core/binary/BitSet.js
CHANGED
|
@@ -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 {
|
|
8
|
+
* @type {Uint8Array}
|
|
9
9
|
*/
|
|
10
|
-
const msb_lut = new
|
|
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
|
-
*
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
* @
|
|
24
|
+
* @param {K} key
|
|
25
|
+
* @returns {V|undefined}
|
|
17
26
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
get(key) {
|
|
28
|
+
return this.data.get(key);
|
|
29
|
+
}
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
this.on.deleted.dispatch(key);
|
|
56
|
+
return result;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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.
|
|
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,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,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
|
+
}
|