@woosh/meep-engine 2.42.7 → 2.43.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 (139) 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.js +197 -53
  61. package/engine/asset/AssetRequest.js +32 -0
  62. package/engine/asset/loaders/ArrayBufferLoader.js +62 -50
  63. package/engine/asset/loaders/image/png/PNG.js +15 -1
  64. package/engine/asset/loaders/image/png/PNGReader.js +3 -2
  65. package/engine/ecs/foliage/ecs/InstancedMeshUtils.js +2 -1
  66. package/engine/ecs/storage/BinaryBufferDeSerializer.js +1 -1
  67. package/engine/ecs/storage/JSONDeSerializer.js +2 -1
  68. package/engine/ecs/terrain/ecs/splat/SplatMapOptimizer.js +2 -1
  69. package/engine/ecs/terrain/ecs/splat/SplatMapping.js +1 -1
  70. package/engine/graphics/camera/makeScreenScissorFrustum.js +1 -1
  71. package/engine/graphics/camera/testClippingPlaneComputation.js +4 -45
  72. package/engine/graphics/ecs/camera/FrustumProjector.js +6 -0
  73. package/engine/graphics/ecs/decal/v2/FPDecalSystem.js +5 -0
  74. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +23 -4
  75. package/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.js +1 -1
  76. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +11 -0
  77. package/engine/graphics/geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js +1 -2
  78. package/engine/graphics/impostors/octahedral/ImpostorBaker.js +5 -2
  79. package/engine/graphics/impostors/octahedral/ImpostorDescription.js +18 -0
  80. package/engine/graphics/impostors/octahedral/bake/prepare_bake_material.js +15 -0
  81. package/engine/graphics/impostors/octahedral/prototypeBaker.js +66 -79
  82. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.js +134 -0
  83. package/engine/graphics/impostors/octahedral/util/build_cutout_from_atlas_by_alpha.js +128 -0
  84. package/engine/graphics/impostors/octahedral/util/build_geometry_from_cutout_shape.js +32 -0
  85. package/engine/graphics/impostors/octahedral/util/load_mesh_for_bake.js +31 -0
  86. package/engine/graphics/impostors/octahedral/util/makeImpostorAtlasPreview.js +107 -0
  87. package/engine/graphics/material/manager/ManagedMaterial.js +4 -0
  88. package/engine/graphics/material/manager/MaterialManager.js +1 -0
  89. package/engine/graphics/material/optimization/MaterialOptimizationContext.js +7 -3
  90. package/engine/graphics/particles/particular/engine/renderers/billboard/ParticleBillboardMaterial.js +2 -2
  91. package/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.js +0 -19
  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/plugin/EnginePluginManager.js +28 -27
  115. package/engine/plugin/PluginReferenceContext.js +9 -0
  116. package/engine/scene/Scene.js +1 -1
  117. package/engine/scene/SerializedScene.js +1 -1
  118. package/engine/scene/transitionToScene.js +3 -1
  119. package/generation/example/main.js +1 -1
  120. package/generation/grid/generation/GridTaskApplyActionToCells.js +1 -1
  121. package/generation/grid/generation/GridTaskDensityMarkerDistribution.js +1 -1
  122. package/generation/grid/generation/GridTaskExecuteRuleTimes.js +1 -1
  123. package/generation/grid/generation/NoopGridTaskGenerator.js +1 -1
  124. package/generation/grid/generation/discrete/GridTaskCellularAutomata.js +2 -1
  125. package/generation/grid/generation/discrete/GridTaskConnectRooms.js +1 -1
  126. package/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +3 -2
  127. package/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.js +1 -1
  128. package/generation/grid/generation/grid/GridTaskAddNodesFixed.js +1 -1
  129. package/generation/grid/generation/road/GridTaskGenerateRoads.js +3 -2
  130. package/generation/grid/generation/util/buildDistanceMapToObjective.js +1 -1
  131. package/generation/markers/GridActionRuleSet.js +2 -1
  132. package/generation/placement/GridCellActionTransformNearbyMarkers.js +2 -4
  133. package/generation/theme/ThemeEngine.js +4 -1
  134. package/package.json +1 -1
  135. package/view/asset/AssetLoaderStatusView.js +5 -5
  136. package/view/minimap/gl/MinimapTerrainGL.js +1 -2
  137. package/view/renderModel.js +1 -1
  138. package/view/tooltip/TooltipView.js +5 -5
  139. package/core/process/task/TaskUtils.js +0 -352
@@ -1,13 +1,30 @@
1
- import { Cache } from "../../../../core/cache/Cache.js";
2
1
  import { AbstractTextureAtlas } from "./AbstractTextureAtlas.js";
3
- import { invokeObjectHash } from "../../../../core/model/object/invokeObjectHash.js";
2
+
3
+ class PatchRecord {
4
+ constructor() {
5
+ /**
6
+ *
7
+ * @type {AtlasPatch|null}
8
+ */
9
+ this.patch = null;
10
+
11
+ /**
12
+ * When was the patch last used
13
+ * @type {number}
14
+ */
15
+ this.last_use_time = 0;
16
+ }
17
+ }
4
18
 
5
19
  /**
6
- * 4Mb
7
- * @type {number}
20
+ * Adding and removing elements to atlas can be costly. It can trigger repack, it may require splatting or cleaning.
21
+ * This abstraction allows us to bypass that most of the time by caching unused patches and just reusing them in the future
22
+ * if they are requested again.
23
+ *
24
+ * The caching strategy here is a little unusual. The texture atlas has a certain size, and the cache will use as much of
25
+ * the atlas space as it can. Meaning that if there is not much unused space - the cache will be very small
26
+ * And if only a small part of the atlas is area is used by "live" patches, the cache can be quite large
8
27
  */
9
- const DEFAULT_CACHE_SIZE = 4194304;
10
-
11
28
  export class CachingTextureAtlas extends AbstractTextureAtlas {
12
29
  /**
13
30
  *
@@ -15,23 +32,16 @@ export class CachingTextureAtlas extends AbstractTextureAtlas {
15
32
  * @param {number} cache_size
16
33
  */
17
34
  constructor({
18
- atlas,
19
- cache_size = DEFAULT_CACHE_SIZE
35
+ atlas
20
36
  }) {
21
37
  super();
22
38
 
23
39
  /**
24
40
  *
25
- * @type {Cache<Sampler2D, AtlasPatch>}
41
+ * @type {PatchRecord[]}
26
42
  * @private
27
43
  */
28
- this.__cached_patches = new Cache({
29
- maxWeight: cache_size,
30
- keyHashFunction: invokeObjectHash,
31
- keyWeigher(sampler) {
32
- return sampler.computeByteSize();
33
- }
34
- });
44
+ this.__cached_patches = [];
35
45
 
36
46
  /**
37
47
  *
@@ -40,17 +50,156 @@ export class CachingTextureAtlas extends AbstractTextureAtlas {
40
50
  */
41
51
  this.__atals = atlas;
42
52
 
43
- this.__cached_patches.onEvicted.add(this.__handle_cache_eviction, this);
53
+ }
54
+
55
+ /**
56
+ * Finds an entry in the cache to evict
57
+ * @returns {number}
58
+ * @private
59
+ */
60
+ __find_eviction_target() {
61
+ let best_date = Infinity;
62
+ let best_node = -1;
63
+
64
+ const records = this.__cached_patches;
65
+ const n = records.length;
66
+
67
+ for (let i = 0; i < n; i++) {
68
+ const record = records[i];
69
+
70
+ const date = record.last_use_time;
71
+
72
+ if (date < best_date) {
73
+ best_node = i;
74
+ best_date = date;
75
+ }
76
+
77
+ }
78
+
79
+ return best_node;
80
+ }
81
+
82
+ /**
83
+ * Finds an entry in the cache that could be replaced with an incoming element
84
+ * @private
85
+ * @returns {number}
86
+ * @param {number} width
87
+ * @param {number} height
88
+ */
89
+ __find_replacement_target(width, height) {
90
+ // find the smallest element in area that is least recently used in cache
91
+ let best_area = Infinity;
92
+ let best_date = Infinity;
93
+
94
+ let best_node = -1;
95
+
96
+ const records = this.__cached_patches;
97
+ const n = records.length;
98
+ for (let i = 0; i < n; i++) {
99
+ const record = records[i];
100
+
101
+ const packing = record.patch.packing;
102
+
103
+ const w = packing.x1 - packing.x0;
104
+ const h = packing.y1 - packing.y0;
105
+
106
+ if (w < width || h < height) {
107
+ // too small
108
+ continue;
109
+ }
110
+
111
+ const area = w * h;
112
+
113
+ if (area > best_area) {
114
+ // prefer smaller patches to evict, as they are cheaper to pull back later on
115
+ continue;
116
+ }
117
+
118
+ const date = record.last_use_time;
119
+
120
+ if (date >= best_date) {
121
+ // prefer patches that have been in cache the longest (the oldest records)
122
+ continue;
123
+ }
124
+
125
+ // found a better candidate, update
126
+
127
+ best_area = area;
128
+ best_date = date;
129
+
130
+ best_node = i;
131
+ }
132
+
133
+ return best_node;
134
+ }
135
+
136
+ /**
137
+ * Will evict specified element from cache
138
+ * @private
139
+ * @param {number} index
140
+ */
141
+ __evict(index) {
142
+ const records = this.__cached_patches;
143
+ const record = records[index];
144
+
145
+ records.splice(index, 1);
146
+ this.__atals.remove(record.patch);
147
+ }
148
+
149
+ /**
150
+ * Evict all cached elements, purging the cache
151
+ * @private
152
+ */
153
+ __evict_all() {
154
+ for (let i = this.__cached_patches.length - 1; i > 0; i--) {
155
+ this.__evict(i);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Free up space in cache to accommodate certain area
161
+ * @private
162
+ * @param {number} width
163
+ * @param {number} height
164
+ * @returns {boolean} true if enough space was freed up to fit specified area, false otherwise
165
+ */
166
+ __evict_for(width, height) {
167
+ if (this.__cached_patches.length <= 0) {
168
+ // nothing to evict
169
+ return false;
170
+ }
171
+
172
+ const target = this.__find_replacement_target(width, height);
173
+
174
+ if (target) {
175
+ // evicted just one element
176
+ this.__evict(target);
177
+ return true;
178
+ }
179
+
180
+ while (!this.__atals.can_pack(width, height)) {
181
+ const victim = this.__find_eviction_target();
182
+
183
+ if (victim === -1) {
184
+ return false;
185
+ }
186
+
187
+ this.__evict(victim);
188
+ }
189
+
190
+ return true;
44
191
  }
45
192
 
46
193
  update() {
47
194
  try {
48
195
  this.__atals.update();
49
196
  } catch (e) {
50
- if (this.__cached_patches.size() > 0) {
197
+ // failed to perform update
198
+
199
+ if (this.__cached_patches.length > 0) {
51
200
 
52
201
  // has some caches patches, lets drop them
53
- this.__cached_patches.clear();
202
+ this.__evict_all();
54
203
 
55
204
  // try to update again
56
205
  this.__atals.update();
@@ -78,40 +227,56 @@ export class CachingTextureAtlas extends AbstractTextureAtlas {
78
227
  return this.__atals.sampler;
79
228
  }
80
229
 
81
- /**
82
- *
83
- * @param {Sampler2D} sampler
84
- * @param {AtlasPatch} patch
85
- * @private
86
- */
87
- __handle_cache_eviction(sampler, patch) {
88
- this.__atals.remove(patch);
89
- }
90
-
91
230
  reset() {
92
- this.__cached_patches.drop();
231
+ this.__cached_patches.splice(i, this.__cached_patches.length);
93
232
  this.__atals.reset();
94
233
  }
95
234
 
96
235
  /**
97
236
  *
98
237
  * @param {Sampler2D} sampler
99
- * @returns {AtlasPatch}
238
+ * @returns {number}
239
+ * @private
100
240
  */
101
- add(sampler) {
102
- const existing_patch = this.__cached_patches.get(sampler);
241
+ __find_cache_record_index(sampler) {
242
+ const records = this.__cached_patches;
243
+ const n = records.length;
244
+ for (let i = 0; i < n; i++) {
245
+ if (records[i].patch.sampler === sampler) {
246
+ return i;
247
+ }
248
+ }
249
+
250
+ return -1;
251
+ }
252
+
253
+ add(sampler, padding = 4) {
254
+ const existing_patch_index = this.__find_cache_record_index(sampler);
255
+
256
+ const records = this.__cached_patches;
103
257
 
104
- if (existing_patch !== null) {
258
+ if (existing_patch_index !== -1) {
105
259
  // cache hit
106
260
 
261
+ const record = records[existing_patch_index];
262
+
107
263
  // remove from cache
108
- this.__cached_patches.remove(sampler);
264
+ records.splice(existing_patch_index, 1);
109
265
 
110
266
  // return patch
111
- return existing_patch;
267
+ return record.patch;
268
+ }
269
+
270
+ const w = sampler.width + padding * 2;
271
+ const h = sampler.height + padding * 2;
272
+
273
+ if (records.length > 0 && !this.__atals.can_pack(w, h)) {
274
+ // new element will not fit, lets try to evict some cache to try and accommodate it
275
+ // note that eviction will purge the entire cache in case that this patch doesn't fit
276
+ this.__evict_for(w, h);
112
277
  }
113
278
 
114
- return this.__atals.add(sampler);
279
+ return this.__atals.add(sampler, padding);
115
280
  }
116
281
 
117
282
  /**
@@ -126,7 +291,12 @@ export class CachingTextureAtlas extends AbstractTextureAtlas {
126
291
  }
127
292
 
128
293
  // push to cache, let eviction logic handle the rest
129
- this.__cached_patches.put(patch.sampler, patch);
294
+ const record = new PatchRecord();
295
+
296
+ record.last_use_time = performance.now();
297
+ record.patch = patch;
298
+
299
+ this.__cached_patches.push(record);
130
300
 
131
301
  return true;
132
302
  }
@@ -1,6 +1,5 @@
1
- import { MaxRectanglesPacker } from "../../../../core/geom/packing/MaxRectangles.js";
1
+ import { MaxRectanglesPacker } from "../../../../core/geom/packing/max-rect/MaxRectangles.js";
2
2
  import { max2 } from "../../../../core/math/max2.js";
3
- import { min2 } from "../../../../core/math/min2.js";
4
3
  import { Sampler2D } from "../sampler/Sampler2D.js";
5
4
  import IdPool from "../../../../core/IdPool.js";
6
5
  import Vector2 from "../../../../core/geom/Vector2.js";
@@ -14,6 +13,7 @@ import {
14
13
  } from "../../../../core/collection/table/DataType2TypedArrayConstructorMapping.js";
15
14
  import { AbstractTextureAtlas } from "./AbstractTextureAtlas.js";
16
15
  import { invokeObjectClone } from "../../../../core/model/object/invokeObjectClone.js";
16
+ import { ceilPowerOfTwo } from "../../../../core/binary/operations/ceilPowerOfTwo.js";
17
17
 
18
18
  export class TextureAtlas extends AbstractTextureAtlas {
19
19
  /**
@@ -161,15 +161,12 @@ export class TextureAtlas extends AbstractTextureAtlas {
161
161
  if (patch.getFlag(AtlasPatchFlag.Packed)) {
162
162
  //only care about packed patches
163
163
 
164
- const pp = patch.position;
165
- const ps = patch.size;
164
+ const x1 = patch.packing.x1;
165
+ const y1 = patch.packing.y1;
166
166
 
167
- const x1 = pp.x + ps.x + patch.padding;
168
- const y1 = pp.y + ps.y + patch.padding;
169
-
170
- if (x1 >= x || y1 >= y) {
167
+ if (x1 > x || y1 > y) {
171
168
  // patch bounds would violate the desired atlas extends
172
- //Resizing atlas would result in some patches not fitting
169
+ // Resizing atlas would result in some patches not fitting
173
170
  return false;
174
171
  }
175
172
  }
@@ -278,6 +275,16 @@ export class TextureAtlas extends AbstractTextureAtlas {
278
275
  return patch;
279
276
  }
280
277
 
278
+ /**
279
+ * Whether a patch of certain size could be packed without triggering repack or growing the atlas
280
+ * @param {number} w
281
+ * @param {number} h
282
+ * @returns {boolean}
283
+ */
284
+ can_pack(w, h) {
285
+ return this.packer.canAdd(w, h);
286
+ }
287
+
281
288
  /**
282
289
  *
283
290
  * @param {AtlasPatch} patch
@@ -482,32 +489,32 @@ export class TextureAtlas extends AbstractTextureAtlas {
482
489
  // console.group("TextureAtlas.update");
483
490
  // console.time('TextureAtlas.update');
484
491
 
485
- const maxPower = 14; //4096
492
+ const maxPower = 14;
486
493
 
487
- const initialPower = Math.floor(
488
- max2(
489
- 0,
490
- Math.log2(
491
- min2(this.size.x, this.size.y)
492
- )
493
- )
494
- );
494
+ const initial_p_x = max2(0, Math.log2(ceilPowerOfTwo(this.size.x)));
495
+ const initial_p_y = max2(0, Math.log2(ceilPowerOfTwo(this.size.y)));
495
496
 
496
- let power = initialPower;
497
+ let power_x = initial_p_x;
498
+ let power_y = initial_p_y;
497
499
 
498
500
  while (!this.pack()) {
499
501
  //packing failed, grow canvas
500
502
 
501
503
  for (; ;) {
502
- power++;
504
+ if (power_x < power_y) {
505
+ power_x++;
506
+ } else {
507
+ power_y++;
508
+ }
503
509
 
504
- if (power > maxPower) {
505
- throw new Error(`Packing failed, could not pack ${this.patches.length} into ${Math.pow(2, maxPower)} resolution texture. Initial power: ${initialPower}`);
510
+ if (power_x > maxPower || power_y > maxPower) {
511
+ throw new Error(`Packing failed, could not pack ${this.patches.length} into ${Math.pow(2, maxPower)} resolution texture. Initial powers: ${initial_p_x}, ${initial_p_y}`);
506
512
  }
507
513
 
508
- const size = Math.pow(2, power);
514
+ const size_x = Math.pow(2, power_x);
515
+ const size_y = Math.pow(2, power_y);
509
516
 
510
- if (this.resize(size, size)) {
517
+ if (this.resize(size_x, size_y)) {
511
518
  break;
512
519
  }
513
520
  }
@@ -1,6 +1,6 @@
1
1
  import { AtlasPatch } from "../AtlasPatch.js";
2
2
  import IdPool from "../../../../../core/IdPool.js";
3
- import { MaxRectanglesPacker } from "../../../../../core/geom/packing/MaxRectangles.js";
3
+ import { MaxRectanglesPacker } from "../../../../../core/geom/packing/max-rect/MaxRectangles.js";
4
4
  import {
5
5
  ClampToEdgeWrapping,
6
6
  DataTexture,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @see https://github.com/terifan/ImageResampler/blob/dbc212ce6aaa769bf3c9623cb6ead58ffd51d76c/src/org/terifan/image_resampler/FilterFactory.java#L330
3
+ * @param {number} x
4
+ * @returns {number}
5
+ */
6
+ export function box(x) {
7
+ if (x < -0.5) {
8
+ return 0;
9
+ }
10
+ if (x <= 0.5) {
11
+ return 1;
12
+ }
13
+ return 0;
14
+ }
15
+
16
+ box.support = 0.5;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @see https://github.com/terifan/ImageResampler/blob/dbc212ce6aaa769bf3c9623cb6ead58ffd51d76c/src/org/terifan/image_resampler/FilterFactory.java#L431
3
+ * @see https://www.intel.com/content/www/us/en/develop/documentation/ipp-dev-reference/top/volume-2-image-processing/ipp-ref-interpolation-in-image-geometry-transform/interpolation-with-two-parameter-cubic-filters.html
4
+ * @param {number} x
5
+ * @returns {number}
6
+ */
7
+ export function cubic2(x){
8
+ const b = 0;
9
+ const c = 1;
10
+
11
+ if (x < -2.0)
12
+ {
13
+ return 0;
14
+ }
15
+ if (x < -1.0)
16
+ {
17
+ return Math.pow((12 - 9*b - 6*c) * Math.abs(x), 3) + Math.pow((-18 + 12*b + 6*c) * Math.abs(x), 2) + (6 - 2 * b) / 6;
18
+ }
19
+ if (x < 0.0)
20
+ {
21
+ return Math.pow((-b-6*c)*Math.abs(x), 3) + Math.pow((6*b+30*c)*Math.abs(x),2) + (-12*b-48*c)*Math.abs(x)+(8*b+24*c);
22
+ }
23
+ if (x < 1.0)
24
+ {
25
+ return Math.pow((-b-6*c)*Math.abs(x), 3) + Math.pow((6*b+30*c)*Math.abs(x),2) + (-12*b-48*c)*Math.abs(x)+(8*b+24*c);
26
+ }
27
+ if (x < 2.0)
28
+ {
29
+ return Math.pow((12 - 9*b - 6*c) * Math.abs(x), 3) + Math.pow((-18 + 12*b + 6*c) * Math.abs(x), 2) + (6 - 2 * b);
30
+ }
31
+ return 0;
32
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Precomputed constant term
3
+ * @type {number}
4
+ */
5
+ const a = Math.sqrt(2.0 / Math.PI);
6
+
7
+ /**
8
+ *
9
+ * @param {number} x
10
+ * @returns {number}
11
+ */
12
+ export function gaussian(x) {
13
+ return a * Math.exp(-2.0 * x * x);
14
+ }
15
+
16
+ gaussian.support = 1.25;
@@ -0,0 +1,19 @@
1
+ import { bessel_i0 } from "../../../../../core/math/bessel_i0.js";
2
+ import { clamp } from "../../../../../core/math/clamp.js";
3
+
4
+ const BASE_I0 = bessel_i0(6.5);
5
+
6
+ /**
7
+ * Support kernel size is 1
8
+ * @see https://github.com/terifan/ImageResampler/blob/dbc212ce6aaa769bf3c9623cb6ead58ffd51d76c/src/org/terifan/image_resampler/FilterFactory.java#L815
9
+ * @param {number} x
10
+ * @returns {number}
11
+ */
12
+ export function kaiser_1(x) {
13
+ const i = clamp(x * 0.6666666666666666, -1.0, 1.0);
14
+ const i0a = 1 / BASE_I0;
15
+
16
+ return bessel_i0(6.5 * Math.sqrt(1 - i * i)) * i0a;
17
+ }
18
+
19
+ kaiser_1.support = 1;
@@ -0,0 +1,19 @@
1
+ import { bessel_3 } from "../../../../../core/math/bessel_3.js";
2
+ import { clamp } from "../../../../../core/math/clamp.js";
3
+
4
+ const PI3 = 3.0 * Math.PI;
5
+ const BESSEL_3_PI_3 = bessel_3(PI3);
6
+
7
+ /**
8
+ *
9
+ * Kaiser Bessel window
10
+ * @see https://bgolus.medium.com/sharper-mipmapping-using-shader-based-supersampling-ed7aadb47bec#5bfb:~:text=Kaiser%2Dfiltered%20Mipmaps
11
+ * @see https://www.shadertoy.com/view/wlf3RH
12
+ */
13
+ export function kaiser_bessel_window(x) {
14
+ const i = clamp(x * 0.6666666666666666, -1.0, 1.0);
15
+ const t = Math.sqrt(1.0 - i * i);
16
+ return bessel_3(PI3 * t) / BESSEL_3_PI_3;
17
+ }
18
+
19
+ kaiser_bessel_window.support = 2;
@@ -0,0 +1,55 @@
1
+ const b = 1.0 / 3.0;
2
+ const c = 1.0 / 3.0;
3
+
4
+ /**
5
+ * @see Don P. Mitchell, Arun N. Netravali. Reconstruction Filters in Computer Graphics. Computer Graphics, Volume 22, Number 4, AT&T Bell Laboratories, Murray Hill, New Jersey, August 1988
6
+ * @see https://github.com/terifan/ImageResampler/blob/dbc212ce6aaa769bf3c9623cb6ead58ffd51d76c/src/org/terifan/image_resampler/FilterFactory.java#L540
7
+ * @param {number} x
8
+ * @returns {number}
9
+ */
10
+ export function mitchell(x) {
11
+ const p0 = (6.0 - 2.0 * b) / 6.0;
12
+ const p2 = (-18.0 + 12.0 * b + 6.0 * c) / 6.0;
13
+ const p3 = (12.0 - 9.0 * b - 6.0 * c) / 6.0;
14
+ const q0 = (8.0 * b + 24.0 * c) / 6.0;
15
+ const q1 = (-12.0 * b - 48.0 * c) / 6.0;
16
+ const q2 = (6.0 * b + 30.0 * c) / 6.0;
17
+ const q3 = (-b - 6.0 * c) / 6.0;
18
+
19
+ if (x < -2.0) {
20
+ return 0.0;
21
+ }
22
+ if (x < -1.0) {
23
+ return q0 - x * (q1 - x * (q2 - x * q3));
24
+ }
25
+ if (x < 0.0) {
26
+ return p0 + x * x * (p2 - x * p3);
27
+ }
28
+ if (x < 1.0) {
29
+ return p0 + x * x * (p2 + x * p3);
30
+ }
31
+ if (x < 2.0) {
32
+ return q0 + x * (q1 + x * (q2 + x * q3));
33
+ }
34
+ return 0.0;
35
+ }
36
+ mitchell.support = 2;
37
+
38
+ /**
39
+ * Hand-optimized mitchell function with c = 0.3333333 and b = 0.3333333
40
+ * NOTE: assumes non-negative values only
41
+ * @param {number} x
42
+ * @returns {number}
43
+ */
44
+ export function mitchell_v1(x) {
45
+
46
+ if (x < 1.0) {
47
+ return 0.8888888888888888 + x * x * (-2 + x * 1.1666666666666667);
48
+ }
49
+ if (x < 2.0) {
50
+ return 1.7777777777777777 + x * (-3.3333333333333335 + x * (2 + x * -0.3888888888888889));
51
+ }
52
+ return 0.0;
53
+ }
54
+
55
+ mitchell_v1.support = 2;
@@ -0,0 +1,109 @@
1
+ import { max2 } from "../../../../../core/math/max2.js";
2
+ import { min2 } from "../../../../../core/math/min2.js";
3
+
4
+ /**
5
+ *
6
+ * @param {Sampler2D} source
7
+ * @param {Sampler2D} destination
8
+ * @param {(number)=>number|{support:number}} weight_computer
9
+ * @param {number} support size of support kernel
10
+ */
11
+ export function sampler2d_scale_down_generic(source, destination, weight_computer, support = weight_computer.support) {
12
+
13
+ const d_w = destination.width;
14
+ const d_h = destination.height;
15
+
16
+ const item_size = destination.itemSize;
17
+
18
+ const source_width = source.width;
19
+ const source_height = source.height;
20
+
21
+ if (source_width < d_w || source_height < d_h) {
22
+ throw new Error('Invalid scale, source is larger than destination in at least 1 dimension');
23
+ }
24
+
25
+ const source_data = source.data;
26
+ const destination_data = destination.data;
27
+
28
+ const ratio_x = source_width / d_w;
29
+ const ratio_y = source_height / d_h;
30
+
31
+ const rcp_ratio_y = 2 / ratio_x;
32
+ const rcp_ratio_x = 2 / ratio_x;
33
+
34
+
35
+ const range2_x = Math.ceil(ratio_x * support / 2);
36
+ const range2_y = Math.ceil(ratio_y * support / 2);
37
+
38
+ const sample = new Array(item_size);
39
+
40
+ let v, u, i, j, k;
41
+
42
+ for (v = 0; v < d_h; v++) {
43
+
44
+ const center_y = (v + 0.5) * ratio_y;
45
+ const i_center_y = Math.floor(center_y);
46
+ //
47
+ const source_y0 = max2(0, i_center_y - range2_y);
48
+ const source_y1 = min2(i_center_y + range2_y, source_height - 1);
49
+
50
+ for (u = 0; u < d_w; u++) {
51
+ const center_x = (u + 0.5) * ratio_x;
52
+ const i_center_x = Math.floor(center_x);
53
+
54
+ const source_x0 = max2(i_center_x - range2_x, 0);
55
+ const source_x1 = min2(i_center_x + range2_x, source_width - 1);
56
+
57
+ let z = 0;
58
+ // reset sample
59
+ for (i = 0; i < item_size; i++) {
60
+ sample[i] = 0;
61
+ }
62
+
63
+ for (j = source_y0; j <= source_y1; j++) {
64
+
65
+ const f_y = (j - center_y);
66
+
67
+ const f_y_rcp = f_y * rcp_ratio_y;
68
+ const f_y_rcp2 = f_y_rcp * f_y_rcp;
69
+
70
+ for (i = source_x0; i <= source_x1; i++) {
71
+
72
+ const f_x = (i - center_x);
73
+
74
+ const f_x_rcp = f_x * rcp_ratio_x;
75
+ const f_x_rcp2 = f_x_rcp * f_x_rcp;
76
+
77
+ const distance_from_center_sqr = f_x_rcp2 + f_y_rcp2;
78
+ const distance_from_center = Math.sqrt(distance_from_center_sqr);
79
+
80
+ const weight = weight_computer(distance_from_center);
81
+
82
+ if (weight <= 0) {
83
+ continue;
84
+ }
85
+
86
+ z += weight;
87
+
88
+ const source_texel_address = (j * source_width + i) * item_size;
89
+
90
+ for (k = 0; k < item_size; k++) {
91
+ sample[k] += weight * source_data[source_texel_address + k];
92
+ }
93
+
94
+ }
95
+ }
96
+
97
+
98
+ // dilute sample and write to destination
99
+ const inv_z = 1 / z;
100
+
101
+ const texel_address = (v * d_w + u) * item_size;
102
+
103
+ for (i = 0; i < item_size; i++) {
104
+ destination_data[texel_address + i] = sample[i] * inv_z;
105
+ }
106
+ }
107
+ }
108
+
109
+ }