@woosh/meep-engine 2.39.2 → 2.39.3

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 (47) hide show
  1. package/core/bvh2/{traversal/__detailed_box_volume_intersection.js → aabb3/aabb3_detailed_volume_intersection.js} +47 -27
  2. package/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.js +13 -0
  3. package/core/bvh2/traversal/ThreeClippingPlaneComputingBVHVisitor.js +5 -3
  4. package/core/bvh2/traversal/__process_point_if_within_planes.js +1 -0
  5. package/core/bvh2/traversal/aabb3_detailed_volume_intersection_callback_based.js +60 -0
  6. package/core/geom/3d/plane/is_point_within_planes.js +46 -0
  7. package/editor/actions/concrete/ActionUpdateTexture.js +21 -0
  8. package/editor/actions/concrete/ArrayCopyAction.js +39 -0
  9. package/editor/actions/concrete/ModifyPatchTextureArray2DAction.js +182 -0
  10. package/editor/enableEditor.js +30 -2
  11. package/editor/tools/paint/TerrainPaintTool.js +19 -3
  12. package/editor/tools/paint/TerrainTexturePaintTool.js +19 -57
  13. package/editor/tools/paint/prototypeTerrainEditor.js +67 -0
  14. package/editor/view/ecs/ComponentControlView.js +105 -10
  15. package/editor/view/ecs/EntityEditor.js +1 -1
  16. package/engine/ecs/fow/FogOfWarSystem.js +6 -3
  17. package/engine/ecs/terrain/ecs/Terrain.js +57 -47
  18. package/engine/ecs/terrain/ecs/layers/TerrainLayer.js +16 -2
  19. package/engine/ecs/terrain/ecs/layers/TerrainLayers.js +17 -0
  20. package/engine/ecs/terrain/ecs/splat/SplatMapping.js +24 -78
  21. package/engine/ecs/terrain/ecs/splat/loadLegacyTerrainSplats.js +73 -0
  22. package/engine/graphics/camera/camera_compute_distance_to_fit_length.d.ts +1 -0
  23. package/engine/graphics/camera/camera_compute_distance_to_fit_length.js +16 -0
  24. package/engine/graphics/camera/testClippingPlaneComputation.js +7 -4
  25. package/engine/graphics/ecs/camera/Camera.js +2 -2
  26. package/engine/graphics/ecs/camera/CameraClippingPlaneComputer.js +5 -8
  27. package/engine/graphics/ecs/camera/CameraSystem.js +18 -184
  28. package/engine/graphics/ecs/camera/auto_set_camera_clipping_planes.js +32 -0
  29. package/engine/graphics/ecs/camera/build_three_camera_object.js +29 -0
  30. package/engine/graphics/ecs/camera/compute_perspective_camera_focal_position.js +27 -0
  31. package/engine/graphics/ecs/camera/frustum_from_camera.js +20 -0
  32. package/engine/graphics/ecs/camera/is_valid_distance_value.js +11 -0
  33. package/engine/graphics/ecs/camera/set_camera_aspect_ratio.js +46 -0
  34. package/engine/graphics/ecs/camera/update_camera_transform.js +17 -0
  35. package/engine/graphics/ecs/path/testPathDisplaySystem.js +19 -15
  36. package/engine/graphics/ecs/path/tube/TubePathStyle.js +1 -0
  37. package/engine/graphics/{Utils.js → makeModelView.js} +1 -10
  38. package/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +2 -2
  39. package/engine/graphics/render/forward_plus/LightManager.js +2 -2
  40. package/engine/graphics/render/view/CameraView.js +2 -2
  41. package/engine/graphics/texture/sampler/Sampler2D.js +1293 -1267
  42. package/engine/graphics/texture/texture_array_2d_copy.js +45 -0
  43. package/engine/graphics/util/makeMeshPreviewScene.js +2 -2
  44. package/package.json +1 -1
  45. package/view/renderModel.js +1 -1
  46. package/view/string_tag_to_css_class_name.js +12 -0
  47. package/engine/graphics/util/computeMeshPreviewCameraDistance.js +0 -10
@@ -1,11 +1,11 @@
1
1
  import { aabb3_build_corners } from "../../geom/3d/aabb/aabb3_build_corners.js";
2
2
  import { aabb_build_frustum } from "../../geom/3d/aabb/aabb3_build_frustum.js";
3
3
  import { computePointDistanceToPlane, plane3_computeLineSegmentIntersection } from "../../geom/Plane.js";
4
- import { aabb3_corner_edge_mapping } from "../aabb3/aabb3_corner_edge_mapping.js";
5
- import { aabb3_edge_corner_mapping } from "../aabb3/aabb3_edge_corner_mapping.js";
6
- import { __process_point_if_within_planes } from "./__process_point_if_within_planes.js";
4
+ import { aabb3_corner_edge_mapping } from "./aabb3_corner_edge_mapping.js";
5
+ import { aabb3_edge_corner_mapping } from "./aabb3_edge_corner_mapping.js";
7
6
  import { plane_computeConvex3PlaneIntersection } from "../../geom/3d/plane/plane_computeConvex3PlaneIntersection.js";
8
7
  import { EPSILON } from "../../math/MathUtils.js";
8
+ import { is_point_within_planes } from "../../geom/3d/plane/is_point_within_planes.js";
9
9
 
10
10
  /**
11
11
  * Common piece of continuous memory for better cache coherence
@@ -50,28 +50,26 @@ const scratch_v3_array = new Float32Array(__scratch_buffer, 224, 3);
50
50
  * where where traditionally only AABB corners would be considered,
51
51
  * we're instead able to compute minimum bounds of the overlap of the two
52
52
  *
53
- * @param {AABB3} box
53
+ * @param {number[]} destination
54
+ * @param {number} destination_offset
55
+ * @param {number} x0
56
+ * @param {number} y0
57
+ * @param {number} z0
58
+ * @param {number} x1
59
+ * @param {number} y1
60
+ * @param {number} z1
54
61
  * @param {number[]} planes
55
- * @param {number} plane_count
56
62
  * @param {number} plane_mask
57
- * @param {function(x:number,y:number,z:number)} callback
58
- * @param {*} [thisArg]
59
- * @private
63
+ * @param {number} plane_count
64
+ * @returns {number}
60
65
  */
61
- export function __detailed_box_volume_intersection(
62
- box,
66
+ export function aabb3_detailed_volume_intersection(
67
+ destination, destination_offset,
68
+ x0, y0, z0, x1, y1, z1,
63
69
  plane_mask,
64
70
  planes,
65
- plane_count,
66
- callback,
67
- thisArg
71
+ plane_count
68
72
  ) {
69
- const x0 = box.x0;
70
- const y0 = box.y0;
71
- const z0 = box.z0;
72
- const x1 = box.x1;
73
- const y1 = box.y1;
74
- const z1 = box.z1;
75
73
 
76
74
  aabb3_build_corners(scratch_aabb_corners, 0, x0, y0, z0, x1, y1, z1);
77
75
  aabb_build_frustum(scratch_aabb_planes, x0, y0, z0, x1, y1, z1);
@@ -94,7 +92,9 @@ export function __detailed_box_volume_intersection(
94
92
  */
95
93
  let corner_plane_out_bitfield = 0;
96
94
 
97
- // find out which corners are outside of the frustum
95
+ let destination_cursor = destination_offset;
96
+
97
+ // find out which corners are outside the frustum
98
98
  for (let corner_index = 0; corner_index < 8; corner_index++) {
99
99
  const corner_offset = corner_index * 3;
100
100
 
@@ -148,7 +148,9 @@ export function __detailed_box_volume_intersection(
148
148
 
149
149
  if (corner_plane_out_bitfield === 0) {
150
150
  // corner is fully inside the frustum
151
- callback.call(thisArg, x, y, z);
151
+ destination[destination_cursor++] = x;
152
+ destination[destination_cursor++] = y;
153
+ destination[destination_cursor++] = z;
152
154
  }
153
155
  }
154
156
 
@@ -211,14 +213,18 @@ export function __detailed_box_volume_intersection(
211
213
 
212
214
  if (edge_intersects) {
213
215
  // a possible candidate, let's see if this point is within rest of the planes
214
- __process_point_if_within_planes(
216
+ const inside_planes = is_point_within_planes(
215
217
  scratch_v3_array[0], scratch_v3_array[1], scratch_v3_array[2],
216
218
  (plane_mask ^ plane_bit_mask),
217
219
  planes,
218
- plane_count,
219
- callback,
220
- thisArg
220
+ plane_count
221
221
  );
222
+
223
+ if (inside_planes) {
224
+ destination[destination_cursor++] = scratch_v3_array[0];
225
+ destination[destination_cursor++] = scratch_v3_array[1];
226
+ destination[destination_cursor++] = scratch_v3_array[2];
227
+ }
222
228
  }
223
229
 
224
230
 
@@ -258,16 +264,30 @@ export function __detailed_box_volume_intersection(
258
264
 
259
265
  if (intersection_exists) {
260
266
 
261
- const point_within_box = box.containsPointWithTolerance(scratch_v3_array[0], scratch_v3_array[1], scratch_v3_array[2], EPSILON);
267
+ const intersection_point_x = scratch_v3_array[0];
268
+ const intersection_point_y = scratch_v3_array[1];
269
+ const intersection_point_z = scratch_v3_array[2];
270
+
271
+ const point_within_box = !(
272
+ (intersection_point_x + EPSILON) < x0 || (intersection_point_x - EPSILON) > x1
273
+ || (intersection_point_y + EPSILON) < y0 || (intersection_point_y - EPSILON) > y1
274
+ || (intersection_point_z + EPSILON) < z0 || (intersection_point_z - EPSILON) > z1
275
+ );
262
276
 
263
277
  if (point_within_box) {
264
278
  // point is within the AABB bounds
265
279
 
266
280
  // ThreeClippingPlaneComputingBVHVisitor.DEBUG_POINTS_2.send3(scratch_v3.x, scratch_v3.y, scratch_v3.z);
267
281
 
268
- callback.call(thisArg, scratch_v3_array[0], scratch_v3_array[1], scratch_v3_array[2]);
282
+ destination[destination_cursor++] = intersection_point_x;
283
+ destination[destination_cursor++] = intersection_point_y;
284
+ destination[destination_cursor++] = intersection_point_z;
269
285
  }
270
286
  }
271
287
  }
272
288
  }
289
+
290
+ const address_change = destination_cursor - destination_offset;
291
+
292
+ return address_change / 3;
273
293
  }
@@ -0,0 +1,13 @@
1
+ //
2
+
3
+
4
+ /**
5
+ *
6
+ * @param result
7
+ * @param result_offset
8
+ * @param bvh
9
+ * @param frustum
10
+ */
11
+ export function compute_tight_near_far_clipping_planes(result, result_offset, bvh, frustum) {
12
+
13
+ }
@@ -1,7 +1,9 @@
1
1
  import { computePointDistanceToPlane } from "../../geom/Plane.js";
2
2
  import { BVHVisitor } from "./BVHVisitor.js";
3
3
  import { read_frustum_planes_to_array } from "../../geom/3d/frustum/read_frustum_planes_to_array.js";
4
- import { __detailed_box_volume_intersection } from "./__detailed_box_volume_intersection.js";
4
+ import {
5
+ aabb3_detailed_volume_intersection_callback_based
6
+ } from "./aabb3_detailed_volume_intersection_callback_based.js";
5
7
 
6
8
  export class ThreeClippingPlaneComputingBVHVisitor extends BVHVisitor {
7
9
  constructor() {
@@ -131,8 +133,8 @@ export class ThreeClippingPlaneComputingBVHVisitor extends BVHVisitor {
131
133
  this.__traverseBoxNearCorner(box);
132
134
 
133
135
 
134
- __detailed_box_volume_intersection(
135
- box,
136
+ aabb3_detailed_volume_intersection_callback_based(
137
+ box.x0, box.y0, box.z0, box.x1, box.y1, box.z1,
136
138
  plane_mask,
137
139
  this.__planes_f32,
138
140
  this.__planeCount,
@@ -53,3 +53,4 @@ export function __process_point_if_within_planes(
53
53
 
54
54
  return true;
55
55
  }
56
+
@@ -0,0 +1,60 @@
1
+ import { aabb3_detailed_volume_intersection } from "../aabb3/aabb3_detailed_volume_intersection.js";
2
+
3
+
4
+ /**
5
+ *
6
+ * @type {number[]}
7
+ */
8
+ const scratch_points_data = [];
9
+
10
+ /**
11
+ * Figures out intersection volume between an AABB and a frustum for much tighter bounds
12
+ *
13
+ * Especially beneficial when trying to intersect a large AABB against a small frustum,
14
+ * where where traditionally only AABB corners would be considered,
15
+ * we're instead able to compute minimum bounds of the overlap of the two
16
+ *
17
+ * @param {number} x0
18
+ * @param {number} y0
19
+ * @param {number} z0
20
+ * @param {number} x1
21
+ * @param {number} y1
22
+ * @param {number} z1
23
+ * @param {number[]} planes
24
+ * @param {number} plane_count
25
+ * @param {number} plane_mask
26
+ * @param {function(x:number,y:number,z:number)} callback
27
+ * @param {*} [thisArg]
28
+ */
29
+ export function aabb3_detailed_volume_intersection_callback_based(
30
+ x0, y0, z0, x1, y1, z1,
31
+ plane_mask,
32
+ planes,
33
+ plane_count,
34
+ callback,
35
+ thisArg
36
+ ) {
37
+ const point_count = aabb3_detailed_volume_intersection(
38
+ scratch_points_data, 0,
39
+ x0, y0, z0, x1, y1, z1,
40
+ plane_mask,
41
+ planes,
42
+ plane_count
43
+ );
44
+
45
+ for (let i = 0; i < point_count; i++) {
46
+ const address = i * 3;
47
+ const x = scratch_points_data[address];
48
+ const y = scratch_points_data[address + 1];
49
+ const z = scratch_points_data[address + 2];
50
+ callback.call(thisArg,
51
+ x,
52
+ y,
53
+ z
54
+ );
55
+
56
+ // Gizmo.color= [1,1,1,1];
57
+ // Gizmo.draw_solid_sphere([x, y, z], 0.1);
58
+ }
59
+ }
60
+
@@ -0,0 +1,46 @@
1
+ import { computePointDistanceToPlane } from "../../Plane.js";
2
+ import { EPSILON } from "../../../math/MathUtils.js";
3
+
4
+ /**
5
+ *
6
+ * @param {number} x
7
+ * @param {number} y
8
+ * @param {number} z
9
+ * @param {number} plane_mask
10
+ * @param {number[]} planes
11
+ * @param {number} plane_count
12
+ * @return {boolean}
13
+ * @private
14
+ */
15
+ export function is_point_within_planes(
16
+ x, y, z,
17
+ plane_mask,
18
+ planes,
19
+ plane_count
20
+ ) {
21
+
22
+ for (let plane_index = 0; plane_index < plane_count; plane_index++) {
23
+ const plane_bit_mask = 1 << plane_index;
24
+
25
+ if ((plane_mask & plane_bit_mask) === 0) {
26
+ // non-intersecting plane, ignore
27
+ continue;
28
+ }
29
+
30
+ const plane_address = plane_index * 4;
31
+
32
+ const nx = planes[plane_address];
33
+ const ny = planes[plane_address + 1];
34
+ const nz = planes[plane_address + 2];
35
+ const c = planes[plane_address + 3];
36
+
37
+ const distance_to_plane = computePointDistanceToPlane(x, y, z, nx, ny, nz, c);
38
+
39
+ if (distance_to_plane < -EPSILON) {
40
+ // point is outside of one of the planes
41
+ return false;
42
+ }
43
+ }
44
+
45
+ return true;
46
+ }
@@ -0,0 +1,21 @@
1
+ import { Action } from "../../../core/process/undo/Action.js";
2
+
3
+ export class ActionUpdateTexture extends Action {
4
+ /**
5
+ *
6
+ * @param {THREE.Texture} texture
7
+ */
8
+ constructor(texture) {
9
+ super();
10
+
11
+ this.__texture = texture;
12
+ }
13
+
14
+ async apply(context) {
15
+ this.__texture.needsUpdate = true;
16
+ }
17
+
18
+ async revert(context) {
19
+ this.__texture.needsUpdate = true;
20
+ }
21
+ }
@@ -0,0 +1,39 @@
1
+ import { Action } from "../../../core/process/undo/Action.js";
2
+ import { array_copy } from "../../../core/collection/array/copyArray.js";
3
+
4
+ export class ArrayCopyAction extends Action {
5
+ /**
6
+ * @template T
7
+ * @param {ArrayLike<T>|T[]|Uint8Array|Float32Array} source
8
+ * @param {ArrayLike<T>|T[]|Uint8Array|Float32Array} destination
9
+ */
10
+ constructor(source, destination) {
11
+ super();
12
+
13
+ this.__source = source;
14
+ this.__destination = destination;
15
+
16
+ /**
17
+ *
18
+ * @type {ArrayLike<T>|null}
19
+ * @private
20
+ */
21
+ this.__restore_value = null;
22
+ }
23
+
24
+ async apply(context) {
25
+
26
+ const CTOR = Object.getPrototypeOf(this.__destination).constructor;
27
+ this.__restore_value = new CTOR(this.__destination.length);
28
+
29
+ // remember old value
30
+ array_copy(this.__destination, 0, this.__restore_value, 0, this.__destination.length);
31
+
32
+ // copy data across
33
+ array_copy(this.__source, 0, this.__destination, 0, this.__source.length);
34
+ }
35
+
36
+ async revert(context) {
37
+ array_copy(this.__restore_value, 0, this.__destination, 0, this.__restore_value.length);
38
+ }
39
+ }
@@ -0,0 +1,182 @@
1
+ import { Action } from "../../../core/process/undo/Action.js";
2
+ import { assert } from "../../../core/assert.js";
3
+ import { texture_array_2d_copy } from "../../../engine/graphics/texture/texture_array_2d_copy.js";
4
+ import { clamp } from "../../../core/math/clamp.js";
5
+
6
+ export class ModifyPatchTextureArray2DAction extends Action {
7
+ /**
8
+ *
9
+ * @param {number[]|Uint8Array|Uint32Array|Float32Array} data
10
+ * @param {number[]} data_resolution
11
+ * @param {number[]} bounds
12
+ */
13
+ constructor(data, data_resolution, bounds) {
14
+ super();
15
+
16
+ assert.isArrayLike(data, 'data');
17
+
18
+ this.__data = data;
19
+ this.__data_resolution = data_resolution;
20
+ this.__bounds = bounds;
21
+
22
+
23
+ const width = this.__bounds[2] - this.__bounds[0];
24
+ const height = this.__bounds[3] - this.__bounds[1];
25
+
26
+ const depth = data_resolution[2];
27
+
28
+ const CTOR = Object.getPrototypeOf(data).constructor;
29
+
30
+ /**
31
+ * @type {ArrayLike<number>}
32
+ * @private
33
+ */
34
+ this.__patch_new = new CTOR(height * width * depth);
35
+ /**
36
+ * @type {ArrayLike<number>}
37
+ * @private
38
+ */
39
+ this.__patch_old = new CTOR(height * width * depth);
40
+ }
41
+
42
+ /**
43
+ *
44
+ * @param {number} center_x
45
+ * @param {number} center_y
46
+ * @param {Sampler2D} stamp
47
+ * @param {number} layer_index
48
+ * @param {number} weight
49
+ * @param {number} clamp_min
50
+ * @param {number} clamp_max
51
+ */
52
+ applyWeightStampToLayer(
53
+ center_x, center_y,
54
+ stamp, layer_index, weight,
55
+ clamp_min = Number.NEGATIVE_INFINITY, clamp_max = Number.POSITIVE_INFINITY
56
+ ) {
57
+
58
+ const bounds_x0 = this.__bounds[0];
59
+ const bounds_y0 = this.__bounds[1];
60
+
61
+ const bounds_x1 = this.__bounds[2];
62
+ const bounds_y1 = this.__bounds[3];
63
+
64
+ const width = bounds_x1 - bounds_x0;
65
+ const height = bounds_y1 - bounds_y0;
66
+
67
+ const center_offset_x = (bounds_x0 + bounds_x1) * 0.5 - center_x;
68
+ const center_offset_y = (bounds_y0 + bounds_y1) * 0.5 - center_y;
69
+
70
+ const source_data = this.__data;
71
+
72
+ const source_width = this.__data_resolution[0];
73
+ const source_height = this.__data_resolution[1];
74
+ const depth = this.__data_resolution[2];
75
+
76
+ for (let y = 0; y < height; y++) {
77
+ const marker_v = (y + center_offset_y) / (height - 1);
78
+
79
+ for (let x = 0; x < width; x++) {
80
+ const marker_u = (x + center_offset_x) / (width - 1);
81
+
82
+ const stamp_value = stamp.sampleChannelBilinearUV(marker_u, marker_v, 3);
83
+
84
+ if (Number.isNaN(stamp_value)) {
85
+ continue;
86
+ }
87
+
88
+ const weighed_stamp_value = stamp_value * weight;
89
+
90
+ const source_x = (x + bounds_x0);
91
+ const source_y = (y + bounds_y0);
92
+
93
+ const weight_reciprocal = weighed_stamp_value / (depth - 1);
94
+
95
+ for (let i = 0; i < depth; i++) {
96
+ const source_layer_address = source_width * source_height * i;
97
+ const source_texel_address = source_layer_address + source_y * source_width + source_x;
98
+
99
+ const destination_texel_address = width * height * i + y * width + x;
100
+
101
+ const source_value = source_data[source_texel_address];
102
+
103
+ let destination_value;
104
+ if (i === layer_index) {
105
+ // the channel that we are stamping
106
+ destination_value = source_value + weighed_stamp_value;
107
+ } else {
108
+ destination_value = source_value - weight_reciprocal;
109
+ }
110
+
111
+ this.__patch_new[destination_texel_address] = clamp(destination_value, clamp_min, clamp_max);
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ get patch_data() {
118
+ return this.__patch_new;
119
+ }
120
+
121
+ computeByteSize() {
122
+ // TODO take size of array elements into account
123
+ return this.__patch_new.length + this.__patch_old.length + 280;
124
+ }
125
+
126
+ async apply(context) {
127
+ const data = this.__data;
128
+
129
+ const depth = this.__data_resolution[2];
130
+
131
+ const bounds_x0 = this.__bounds[0];
132
+ const bounds_y0 = this.__bounds[1];
133
+
134
+ const bounds_x1 = this.__bounds[2];
135
+ const bounds_y1 = this.__bounds[3];
136
+
137
+ const width = bounds_x1 - bounds_x0;
138
+ const height = bounds_y1 - bounds_y0;
139
+
140
+ const source_resolution_x = this.__data_resolution[0];
141
+ const source_resolution_y = this.__data_resolution[1];
142
+
143
+ // store old data
144
+ texture_array_2d_copy(
145
+ data, bounds_x0, bounds_y0, source_resolution_x, source_resolution_y,
146
+ this.__patch_old, 0, 0, width, height,
147
+ width, height, depth
148
+ );
149
+
150
+ texture_array_2d_copy(
151
+ this.__patch_new, 0, 0, width, height,
152
+ data, bounds_x0, bounds_y0, source_resolution_x, source_resolution_y,
153
+ width, height, depth
154
+ );
155
+
156
+ }
157
+
158
+ async revert(context) {
159
+
160
+ const data = this.__data;
161
+
162
+ const depth = this.__data_resolution[2];
163
+
164
+ const bounds_x0 = this.__bounds[0];
165
+ const bounds_y0 = this.__bounds[1];
166
+
167
+ const bounds_x1 = this.__bounds[2];
168
+ const bounds_y1 = this.__bounds[3];
169
+
170
+ const width = bounds_x1 - bounds_x0;
171
+ const height = bounds_y1 - bounds_y0;
172
+
173
+ const source_resolution_x = this.__data_resolution[0];
174
+ const source_resolution_y = this.__data_resolution[1];
175
+
176
+ texture_array_2d_copy(
177
+ this.__patch_old, 0, 0, width, height,
178
+ data, bounds_x0, bounds_y0, source_resolution_x, source_resolution_y,
179
+ width, height, depth
180
+ );
181
+ }
182
+ }
@@ -5,14 +5,14 @@ import { noop } from "../core/function/Functions.js";
5
5
  *
6
6
  * @param {Engine} engine
7
7
  * @param {function(Editor):*} [initialization]
8
+ * @returns {{enable:function, disable: function, toggle: function, editor: Editor}}
8
9
  */
9
10
  export function enableEditor(engine, initialization = noop) {
10
11
  let editor = null;
11
12
 
12
13
  let enabled = false;
13
14
 
14
- function attachEditor() {
15
- console.log('Enabling editor');
15
+ function ensureEditor() {
16
16
  if (editor === null) {
17
17
  editor = new Editor();
18
18
  editor.engine = engine;
@@ -21,11 +21,28 @@ export function enableEditor(engine, initialization = noop) {
21
21
 
22
22
  initialization(editor);
23
23
  }
24
+
25
+ return editor;
26
+ }
27
+
28
+ function attachEditor() {
29
+ if (enabled) {
30
+ return;
31
+ }
32
+
33
+ console.log('Enabling editor');
34
+
35
+ ensureEditor();
36
+
24
37
  editor.attach(engine);
25
38
  enabled = true;
26
39
  }
27
40
 
28
41
  function detachEditor() {
42
+ if (!enabled) {
43
+ return;
44
+ }
45
+
29
46
  console.log('Disabling editor');
30
47
  if (editor !== null) {
31
48
  editor.detach();
@@ -54,4 +71,15 @@ export function enableEditor(engine, initialization = noop) {
54
71
  });
55
72
 
56
73
  console.warn('Editor mode enabled, use NumLock key to toggle editor mode');
74
+
75
+ return {
76
+ toggle: toggleEditor,
77
+ enable: attachEditor,
78
+ disable: detachEditor,
79
+ get editor() {
80
+ ensureEditor();
81
+
82
+ return editor;
83
+ }
84
+ }
57
85
  }
@@ -31,13 +31,16 @@ export class TerrainPaintTool extends Tool {
31
31
  this.__brushPosition = new Vector2();
32
32
 
33
33
  this.__brushImage = document.createElement('canvas');
34
+
35
+ this.__paint_debt = 0;
36
+ this.__paint_pending = false;
34
37
  }
35
38
 
36
39
  /**
37
40
  *
38
41
  * @param {number} timeDelta
39
42
  */
40
- paint(timeDelta) {
43
+ async paint(timeDelta) {
41
44
 
42
45
  }
43
46
 
@@ -46,14 +49,25 @@ export class TerrainPaintTool extends Tool {
46
49
  * @param {number} timeDelta
47
50
  */
48
51
  update(timeDelta) {
49
-
50
52
  this.updateBrushPosition();
51
53
  this.updateOverlay();
52
54
 
53
55
  if (this.isRunning()) {
54
56
 
55
- this.paint(timeDelta);
57
+ this.__paint_debt += timeDelta;
58
+
59
+ if (this.__paint_pending) {
60
+ return;
61
+ }
62
+
63
+ this.__paint_pending = true;
64
+
65
+ const paint_promise = this.paint(timeDelta);
66
+ this.__paint_debt = 0;
56
67
 
68
+ paint_promise.finally(() => {
69
+ this.__paint_pending = false;
70
+ });
57
71
  }
58
72
 
59
73
  }
@@ -156,6 +170,8 @@ export class TerrainPaintTool extends Tool {
156
170
  const engine = this.engine;
157
171
  const editor = this.editor;
158
172
 
173
+ this.__paint_debt = 0;
174
+
159
175
  this.terrain = obtainTerrain(engine.entityManager.dataset, (t, entity) => {
160
176
  this.terrainEntity = entity;
161
177
  });