@woosh/meep-engine 2.39.0 → 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 (55) 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 +26 -14
  36. package/engine/graphics/ecs/path/tube/BasicMaterialDefinition.d.ts +3 -0
  37. package/engine/graphics/ecs/path/tube/BasicMaterialDefinition.js +8 -0
  38. package/engine/graphics/ecs/path/tube/StandardMaterialDefinition.js +9 -0
  39. package/engine/graphics/ecs/path/tube/TubeMaterialType.d.ts +1 -0
  40. package/engine/graphics/ecs/path/tube/TubeMaterialType.js +1 -0
  41. package/engine/graphics/ecs/path/tube/TubePathStyle.js +10 -1
  42. package/engine/graphics/ecs/path/tube/build/TubePathBuilder.js +39 -107
  43. package/engine/graphics/ecs/path/tube/build/compute_smooth_profile_normals.js +41 -0
  44. package/engine/graphics/ecs/path/tube/build/fix_shape_normal_order.js +45 -0
  45. package/engine/graphics/{Utils.js → makeModelView.js} +1 -10
  46. package/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +2 -2
  47. package/engine/graphics/render/forward_plus/LightManager.js +2 -2
  48. package/engine/graphics/render/view/CameraView.js +2 -2
  49. package/engine/graphics/texture/sampler/Sampler2D.js +1293 -1267
  50. package/engine/graphics/texture/texture_array_2d_copy.js +45 -0
  51. package/engine/graphics/util/makeMeshPreviewScene.js +2 -2
  52. package/package.json +1 -1
  53. package/view/renderModel.js +1 -1
  54. package/view/string_tag_to_css_class_name.js +12 -0
  55. package/engine/graphics/util/computeMeshPreviewCameraDistance.js +0 -10
@@ -10,1651 +10,1677 @@ import { mix } from "../../../../core/math/mix.js";
10
10
  import { BlendingType } from "./BlendingType.js";
11
11
  import { assert } from "../../../../core/assert.js";
12
12
  import { typedArrayConstructorByInstance } from "./typedArrayConstructorByInstance.js";
13
-
13
+ import { typedArrayToDataType } from "../../../../core/collection/array/typedArrayToDataType.js";
14
+ import {
15
+ compute_typed_array_constructor_from_data_type
16
+ } from "../../../../core/collection/table/DataType2TypedArrayConstructorMapping.js";
14
17
 
15
18
  /**
16
- *
17
- * @param {ArrayLike<number>|number[]|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array|Int8Array|Int16Array|Int32Array|Float32Array|Float64Array} data
18
- * @param {int} itemSize
19
- * @param {int} width
20
- * @param {int} height
21
- * @constructor
19
+ * @class
22
20
  */
23
- export function Sampler2D(data = [], itemSize = 1, width = 0, height = 0) {
24
- if (!Number.isInteger(itemSize)) {
25
- throw new Error(`itemSize must be integer, instead was ${itemSize}`);
26
- }
27
- if (!Number.isInteger(width)) {
28
- throw new Error(`width must be integer, instead was ${width}`);
29
- }
30
- if (!Number.isInteger(height)) {
31
- throw new Error(`height must be integer, instead was ${height}`);
32
- }
33
-
34
- if (data === undefined) {
35
- throw new Error("data was undefined");
36
- }
37
-
38
- /**
39
- *
40
- * @type {Number}
41
- */
42
- this.width = width;
43
-
21
+ export class Sampler2D {
44
22
  /**
45
23
  *
46
- * @type {Number}
24
+ * @param {ArrayLike<number>|number[]|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array|Int8Array|Int16Array|Int32Array|Float32Array|Float64Array} data
25
+ * @param {int} itemSize
26
+ * @param {int} width
27
+ * @param {int} height
28
+ * @constructor
47
29
  */
48
- this.height = height;
30
+ constructor(data = [], itemSize = 1, width = 0, height = 0) {
31
+ if (!Number.isInteger(itemSize)) {
32
+ throw new Error(`itemSize must be integer, instead was ${itemSize}`);
33
+ }
34
+ if (!Number.isInteger(width)) {
35
+ throw new Error(`width must be integer, instead was ${width}`);
36
+ }
37
+ if (!Number.isInteger(height)) {
38
+ throw new Error(`height must be integer, instead was ${height}`);
39
+ }
49
40
 
50
- /**
51
- * Number of channels
52
- * @type {Number}
53
- */
54
- this.itemSize = itemSize;
41
+ if (data === undefined) {
42
+ throw new Error("data was undefined");
43
+ }
55
44
 
56
- /**
57
- *
58
- * @type {number[]|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array|Int8Array|Int16Array|Int32Array|Float32Array|Float64Array}
59
- */
60
- this.data = data;
45
+ /**
46
+ *
47
+ * @type {Number}
48
+ */
49
+ this.width = width;
50
+
51
+ /**
52
+ *
53
+ * @type {Number}
54
+ */
55
+ this.height = height;
56
+
57
+ /**
58
+ * Number of channels
59
+ * @type {Number}
60
+ */
61
+ this.itemSize = itemSize;
62
+
63
+ /**
64
+ *
65
+ * @type {number[]|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array|Int8Array|Int16Array|Int32Array|Float32Array|Float64Array}
66
+ */
67
+ this.data = data;
68
+
69
+ /**
70
+ * Used to tracking changes
71
+ * @type {number}
72
+ */
73
+ this.version = 0;
74
+ /**
75
+ * @readonly
76
+ * @type {boolean}
77
+ */
78
+ this.isSampler2D = true;
79
+ }
61
80
 
62
81
  /**
63
- * Used to tracking changes
64
- * @type {number}
82
+ * @param {number} [channel=0]
83
+ * @returns {{x: number, index: number, y: number, value: number}}
65
84
  */
66
- this.version = 0;
67
- }
68
-
69
- /**
70
- *
71
- * @param {int} itemSize
72
- * @param {int} width
73
- * @param {int} height
74
- * @return {Sampler2D}
75
- */
76
- Sampler2D.uint8clamped = function (itemSize, width, height) {
77
- const data = new Uint8ClampedArray(width * height * itemSize);
78
- const sampler = new Sampler2D(data, itemSize, width, height);
79
- return sampler;
80
- };
81
-
82
- /**
83
- *
84
- * @param {int} itemSize
85
- * @param {int} width
86
- * @param {int} height
87
- * @return {Sampler2D}
88
- */
89
- Sampler2D.uint8 = function (itemSize, width, height) {
90
- const data = new Uint8Array(width * height * itemSize);
91
- const sampler = new Sampler2D(data, itemSize, width, height);
92
- return sampler;
93
- };
94
-
95
- /**
96
- *
97
- * @param {int} itemSize
98
- * @param {int} width
99
- * @param {int} height
100
- * @return {Sampler2D}
101
- */
102
- Sampler2D.uint16 = function (itemSize, width, height) {
103
- const data = new Uint16Array(width * height * itemSize);
104
- const sampler = new Sampler2D(data, itemSize, width, height);
105
- return sampler;
106
- };
107
-
108
- /**
109
- *
110
- * @param {int} itemSize
111
- * @param {int} width
112
- * @param {int} height
113
- * @return {Sampler2D}
114
- */
115
- Sampler2D.uint32 = function (itemSize, width, height) {
116
- const data = new Uint32Array(width * height * itemSize);
117
- const sampler = new Sampler2D(data, itemSize, width, height);
118
- return sampler;
119
- };
85
+ computeMax(channel = 0) {
86
+ const itemSize = this.itemSize;
120
87
 
121
- /**
122
- *
123
- * @param {int} itemSize
124
- * @param {int} width
125
- * @param {int} height
126
- * @return {Sampler2D}
127
- */
128
- Sampler2D.int8 = function (itemSize, width, height) {
129
- const data = new Int8Array(width * height * itemSize);
130
- const sampler = new Sampler2D(data, itemSize, width, height);
131
- return sampler;
132
- };
133
-
134
- /**
135
- *
136
- * @param {int} itemSize
137
- * @param {int} width
138
- * @param {int} height
139
- * @return {Sampler2D}
140
- */
141
- Sampler2D.int16 = function (itemSize, width, height) {
142
- const data = new Int16Array(width * height * itemSize);
143
- const sampler = new Sampler2D(data, itemSize, width, height);
144
- return sampler;
145
- };
146
- /**
147
- *
148
- * @param {int} itemSize
149
- * @param {int} width
150
- * @param {int} height
151
- * @return {Sampler2D}
152
- */
153
- Sampler2D.int32 = function (itemSize, width, height) {
154
- const data = new Int32Array(width * height * itemSize);
155
- const sampler = new Sampler2D(data, itemSize, width, height);
156
- return sampler;
157
- };
88
+ assert.typeOf(channel, "number", "channel");
89
+ assert.ok(channel >= 0, `channel must be >= 0, was ${channel}`);
90
+ assert.ok(channel < itemSize, `channel must be less than itemSize(=${itemSize}), was ${channel}`);
158
91
 
159
- /**
160
- *
161
- * @param {int} itemSize
162
- * @param {int} width
163
- * @param {int} height
164
- * @return {Sampler2D}
165
- */
166
- Sampler2D.float32 = function (itemSize, width, height) {
167
- const data = new Float32Array(width * height * itemSize);
168
- const sampler = new Sampler2D(data, itemSize, width, height);
169
- return sampler;
170
- };
171
- /**
172
- *
173
- * @param {int} itemSize
174
- * @param {int} width
175
- * @param {int} height
176
- * @return {Sampler2D}
177
- */
178
- Sampler2D.float64 = function (itemSize, width, height) {
179
- const data = new Float64Array(width * height * itemSize);
180
- const sampler = new Sampler2D(data, itemSize, width, height);
181
- return sampler;
182
- };
92
+ const data = this.data;
183
93
 
184
- /**
185
- *
186
- * @param {Sampler2D} input0
187
- * @param {Sampler2D} input1
188
- * @param {Sampler2D} result
189
- * @param {function( value0 : number[], value1 : number[], result : number[], index : number) : void} operation
190
- */
191
- Sampler2D.combine = function (input0, input1, result, operation) {
192
- assert.notEqual(input0, undefined, "input0 is undefined");
193
- assert.notEqual(input1, undefined, "input1 is undefined");
194
- assert.notEqual(result, undefined, "result is undefined");
94
+ const l = data.length;
195
95
 
196
- assert.typeOf(operation, "function", "operation");
96
+ if (l === 0) {
97
+ //no data
98
+ return undefined;
99
+ }
197
100
 
198
- assert.equal(input0.width, input1.width, `input0.width(=${input0.width}) is not equal to input1.width(=${input1.width})`);
199
- assert.equal(input0.height, input1.height, `input0.height(=${input0.height}) is not equal to input1.height(=${input1.height})`);
101
+ let bestValue = data[channel];
102
+ let bestIndex = channel;
200
103
 
201
- assert.equal(input0.width, result.width, `input width(=${input0.width}) is not equal to result.width(=${result.width})`);
202
- assert.equal(input0.height, result.height, `input height(=${input0.height}) is not equal to result.height(=${result.height})`);
104
+ for (let i = channel + itemSize; i < l; i += itemSize) {
105
+ const value = data[i];
203
106
 
204
- const width = input0.width;
205
- const height = input0.height;
107
+ if (bestValue < value) {
108
+ bestValue = value;
109
+ bestIndex = i;
110
+ }
206
111
 
207
- const length = width * height;
112
+ }
208
113
 
209
- const arg0 = [];
210
- const arg1 = [];
211
- const res = [];
114
+ const width = this.width;
212
115
 
213
- const itemSize0 = input0.itemSize;
214
- const itemSize1 = input1.itemSize;
215
- const itemSizeR = result.itemSize;
116
+ const itemIndex = (bestIndex / this.itemSize) | 0;
216
117
 
217
- const data0 = input0.data;
218
- const data1 = input1.data;
219
- const dataR = result.data;
118
+ const x = itemIndex % width;
119
+ const y = (itemIndex / width) | 0;
220
120
 
121
+ return {
122
+ index: bestIndex,
123
+ value: bestValue,
124
+ x,
125
+ y
126
+ };
127
+ }
221
128
 
222
- let i, j;
129
+ /**
130
+ * @param {number[]} result
131
+ * @param {number} [channel=0]
132
+ */
133
+ computeMinIndices(result, channel = 0) {
134
+ const itemSize = this.itemSize;
223
135
 
224
- for (i = 0; i < length; i++) {
136
+ assert.typeOf(channel, "number", "channel");
137
+ assert.ok(channel >= 0, `channel must be >= 0, was ${channel}`);
138
+ assert.ok(channel < itemSize, `channel must be less than itemSize(=${itemSize}), was ${channel}`);
225
139
 
226
- // read input 0
227
- for (j = 0; j < itemSize0; j++) {
228
- arg0[j] = data0[j + i * itemSize0];
229
- }
140
+ assert.ok(Array.isArray(result), "result is not an array");
230
141
 
231
- // read input 1
232
- for (j = 0; j < itemSize0; j++) {
233
- arg1[j] = data1[j + i * itemSize1];
234
- }
142
+ const data = this.data;
235
143
 
236
- //perform operation
237
- operation(arg0, arg1, res, i);
144
+ const l = data.length;
238
145
 
239
- //write result
240
- for (j = 0; j < itemSizeR; j++) {
241
- dataR[j + i * itemSizeR] = res[j];
146
+ if (l === 0) {
147
+ //no data
148
+ return undefined;
242
149
  }
243
150
 
244
- }
151
+ let bestValue = data[channel];
245
152
 
246
- result.version++;
247
- };
153
+ let resultCount = 0;
248
154
 
155
+ for (let i = channel + itemSize; i < l; i += itemSize) {
156
+ const value = data[i];
249
157
 
250
- /**
251
- * @param {number} [channel=0]
252
- * @returns {{x: number, index: number, y: number, value: number}}
253
- */
254
- Sampler2D.prototype.computeMax = function (channel = 0) {
255
- const itemSize = this.itemSize;
158
+ if (bestValue > value) {
159
+ bestValue = value;
160
+ //drop result
161
+ resultCount = 1;
256
162
 
257
- assert.typeOf(channel, "number", "channel");
258
- assert.ok(channel >= 0, `channel must be >= 0, was ${channel}`);
259
- assert.ok(channel < itemSize, `channel must be less than itemSize(=${itemSize}), was ${channel}`);
163
+ result[0] = i;
164
+ } else if (value === bestValue) {
165
+ result[resultCount++] = i;
166
+ }
260
167
 
261
- const data = this.data;
168
+ }
262
169
 
263
- const l = data.length;
170
+ //crop results
171
+ if (resultCount < result.length) {
172
+ result.splice(resultCount, result.length - resultCount);
173
+ }
264
174
 
265
- if (l === 0) {
266
- //no data
267
- return undefined;
175
+ return;
268
176
  }
269
177
 
270
- let bestValue = data[channel];
271
- let bestIndex = channel;
178
+ /**
179
+ * @param {number} [channel=0]
180
+ * @returns {{x: number, index: number, y: number, value: number}}
181
+ */
182
+ computeMin(channel = 0) {
183
+ const itemSize = this.itemSize;
272
184
 
273
- for (let i = channel + itemSize; i < l; i += itemSize) {
274
- const value = data[i];
185
+ assert.typeOf(channel, "number", "channel");
186
+ assert.ok(channel >= 0, `channel must be >= 0, was ${channel}`);
187
+ assert.ok(channel < itemSize, `channel must be less than itemSize(=${itemSize}), was ${channel}`);
275
188
 
276
- if (bestValue < value) {
277
- bestValue = value;
278
- bestIndex = i;
279
- }
189
+ const data = this.data;
280
190
 
281
- }
191
+ const l = data.length;
282
192
 
283
- const width = this.width;
284
-
285
- const itemIndex = (bestIndex / this.itemSize) | 0;
193
+ if (l === 0) {
194
+ //no data
195
+ return undefined;
196
+ }
286
197
 
287
- const x = itemIndex % width;
288
- const y = (itemIndex / width) | 0;
198
+ let bestValue = data[channel];
199
+ let bestIndex = channel;
289
200
 
290
- return {
291
- index: bestIndex,
292
- value: bestValue,
293
- x,
294
- y
295
- };
296
- };
201
+ for (let i = channel + itemSize; i < l; i += itemSize) {
202
+ const value = data[i];
297
203
 
298
- /**
299
- * @param {number[]} result
300
- * @param {number} [channel=0]
301
- */
302
- Sampler2D.prototype.computeMinIndices = function (result, channel = 0) {
303
- const itemSize = this.itemSize;
204
+ if (bestValue > value) {
205
+ bestValue = value;
206
+ bestIndex = i;
207
+ }
304
208
 
305
- assert.typeOf(channel, "number", "channel");
306
- assert.ok(channel >= 0, `channel must be >= 0, was ${channel}`);
307
- assert.ok(channel < itemSize, `channel must be less than itemSize(=${itemSize}), was ${channel}`);
209
+ }
308
210
 
309
- assert.ok(Array.isArray(result), "result is not an array");
211
+ const width = this.width;
310
212
 
311
- const data = this.data;
213
+ const itemIndex = (bestIndex / this.itemSize) | 0;
312
214
 
313
- const l = data.length;
215
+ const x = itemIndex % width;
216
+ const y = (itemIndex / width) | 0;
314
217
 
315
- if (l === 0) {
316
- //no data
317
- return undefined;
218
+ return {
219
+ index: bestIndex,
220
+ value: bestValue,
221
+ x,
222
+ y
223
+ };
318
224
  }
319
225
 
320
- let bestValue = data[channel];
321
-
322
- let resultCount = 0;
226
+ /**
227
+ *
228
+ * @deprecated
229
+ * @param {number} x
230
+ * @param {number}y
231
+ * @param {Vector1|Vector2|Vector3|Vector4} result
232
+ * @returns {number}
233
+ */
234
+ get(x, y, result) {
235
+ console.warn("Deprecated method, use sampleBilinear instead");
323
236
 
324
- for (let i = channel + itemSize; i < l; i += itemSize) {
325
- const value = data[i];
237
+ const t = [];
326
238
 
327
- if (bestValue > value) {
328
- bestValue = value;
329
- //drop result
330
- resultCount = 1;
239
+ this.sampleBilinear(x, y, t, 0);
331
240
 
332
- result[0] = i;
333
- } else if (value === bestValue) {
334
- result[resultCount++] = i;
241
+ if (result !== undefined) {
242
+ result.readFromArray(t, 0);
243
+ return result;
244
+ } else {
245
+ return t[0];
335
246
  }
336
-
337
- }
338
-
339
- //crop results
340
- if (resultCount < result.length) {
341
- result.splice(resultCount, result.length - resultCount);
342
247
  }
343
248
 
344
- return;
345
- };
249
+ /**
250
+ *
251
+ * @param {number} u
252
+ * @param {number} v
253
+ * @param {number[]} result
254
+ */
255
+ sampleCatmullRomUV(u, v, result) {
256
+ const itemSize = this.itemSize;
346
257
 
347
- /**
348
- * @param {number} [channel=0]
349
- * @returns {{x: number, index: number, y: number, value: number}}
350
- */
351
- Sampler2D.prototype.computeMin = function (channel = 0) {
352
- const itemSize = this.itemSize;
258
+ for (let i = 0; i < itemSize; i++) {
259
+ result[i] = this.sampleChannelCatmullRomUV(u, v, i);
260
+ }
261
+ }
353
262
 
354
- assert.typeOf(channel, "number", "channel");
355
- assert.ok(channel >= 0, `channel must be >= 0, was ${channel}`);
356
- assert.ok(channel < itemSize, `channel must be less than itemSize(=${itemSize}), was ${channel}`);
263
+ /**
264
+ *
265
+ * @param {number} u
266
+ * @param {number} v
267
+ * @param {number} channel
268
+ * @returns {number}
269
+ */
270
+ sampleChannelCatmullRomUV(u, v, channel) {
271
+ const x = u * (this.width);
272
+ const y = v * (this.height);
357
273
 
358
- const data = this.data;
274
+ return this.sampleChannelCatmullRom(x - 0.5, y - 0.5, channel);
275
+ }
359
276
 
360
- const l = data.length;
277
+ /**
278
+ *
279
+ * @see https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
280
+ * @param {number} x
281
+ * @param {number} y
282
+ * @param {number} channel
283
+ * @returns {number}
284
+ */
285
+ sampleChannelCatmullRom(x, y, channel) {
286
+ // We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding
287
+ // down the sample location to get the exact center of our "starting" texel. The starting texel will be at
288
+ // location [1, 1] in the grid, where [0, 0] is the top-left corner.
289
+ const texPos1_x = Math.floor(x - 0.5) + 0.5;
290
+ const texPos1_y = Math.floor(y - 0.5) + 0.5;
291
+
292
+ // Compute the fractional offset from our starting texel to our original sample location, which we'll
293
+ // feed into the Catmull-Rom spline function to get our filter weights.
294
+ const f_x = x - texPos1_x;
295
+ const f_y = y - texPos1_y;
296
+
297
+ // Compute the Catmull-Rom weights using the fractional offset that we calculated earlier.
298
+ // These equations are pre-expanded based on our knowledge of where the texels will be located,
299
+ // which lets us avoid having to evaluate a piece-wise function.
300
+ const w0_x = f_x * (-0.5 + f_x * (1.0 - 0.5 * f_x));
301
+ const w0_y = f_y * (-0.5 + f_y * (1.0 - 0.5 * f_y));
302
+
303
+ const w1_x = 1.0 + f_x * f_x * (-2.5 + 1.5 * f_x);
304
+ const w1_y = 1.0 + f_y * f_y * (-2.5 + 1.5 * f_y);
305
+
306
+ const w2_x = f_x * (0.5 + f_x * (2.0 - 1.5 * f_x));
307
+ const w2_y = f_y * (0.5 + f_y * (2.0 - 1.5 * f_y));
308
+
309
+ const w3_x = f_x * f_x * (-0.5 + 0.5 * f_x);
310
+ const w3_y = f_y * f_y * (-0.5 + 0.5 * f_y);
311
+
312
+ // Work out weighting factors and sampling offsets that will let us use bilinear filtering to
313
+ // simultaneously evaluate the middle 2 samples from the 4x4 grid.
314
+ const w12_x = w1_x + w2_x;
315
+ const w12_y = w1_y + w2_y;
316
+ const offset12_x = w2_x / w12_x;
317
+ const offset12_y = w2_y / w12_y;
318
+
319
+ // Compute the final coordinates we'll use for sampling the texture
320
+ const texPos0_x = texPos1_x - 1;
321
+ const texPos0_y = texPos1_y - 1;
322
+ const texPos3_x = texPos1_x + 2;
323
+ const texPos3_y = texPos1_y + 2;
324
+ const texPos12_x = texPos1_x + offset12_x;
325
+ const texPos12_y = texPos1_y + offset12_y;
326
+
327
+
328
+ let result = 0.0;
329
+ result += this.sampleChannelBilinear(texPos0_x, texPos0_y, channel) * w0_x * w0_y;
330
+ result += this.sampleChannelBilinear(texPos12_x, texPos0_y, channel) * w12_x * w0_y;
331
+ result += this.sampleChannelBilinear(texPos3_x, texPos0_y, channel) * w3_x * w0_y;
332
+
333
+ result += this.sampleChannelBilinear(texPos0_x, texPos12_y, channel) * w0_x * w12_y;
334
+ result += this.sampleChannelBilinear(texPos12_x, texPos12_y, channel) * w12_x * w12_y;
335
+ result += this.sampleChannelBilinear(texPos3_x, texPos12_y, channel) * w3_x * w12_y;
336
+
337
+ result += this.sampleChannelBilinear(texPos0_x, texPos3_y, channel) * w0_x * w3_y;
338
+ result += this.sampleChannelBilinear(texPos12_x, texPos3_y, channel) * w12_x * w3_y;
339
+ result += this.sampleChannelBilinear(texPos3_x, texPos3_y, channel) * w3_x * w3_y;
361
340
 
362
- if (l === 0) {
363
- //no data
364
- return undefined;
341
+ return result;
365
342
  }
366
343
 
367
- let bestValue = data[channel];
368
- let bestIndex = channel;
369
-
370
- for (let i = channel + itemSize; i < l; i += itemSize) {
371
- const value = data[i];
344
+ /**
345
+ *
346
+ * @param {number} u
347
+ * @param {number} v
348
+ * @param {number[]} result
349
+ */
350
+ sampleBicubicUV(u, v, result) {
351
+ const itemSize = this.itemSize;
372
352
 
373
- if (bestValue > value) {
374
- bestValue = value;
375
- bestIndex = i;
353
+ for (let i = 0; i < itemSize; i++) {
354
+ result[i] = this.sampleChannelBicubicUV(u, v, i);
376
355
  }
377
-
378
356
  }
379
357
 
380
- const width = this.width;
381
-
382
- const itemIndex = (bestIndex / this.itemSize) | 0;
358
+ /**
359
+ *
360
+ * @param {number} u
361
+ * @param {number} v
362
+ * @param {number} channel
363
+ * @returns {number}
364
+ */
365
+ sampleChannelBicubicUV(u, v, channel) {
366
+ const x = u * (this.width);
367
+ const y = v * (this.height);
383
368
 
384
- const x = itemIndex % width;
385
- const y = (itemIndex / width) | 0;
369
+ return this.sampleChannelBicubic(x - 0.5, y - 0.5, channel);
370
+ }
386
371
 
387
- return {
388
- index: bestIndex,
389
- value: bestValue,
390
- x,
391
- y
392
- };
393
- };
372
+ /**
373
+ * Bicubic-filtered sampling
374
+ * @param {number} x
375
+ * @param {number} y
376
+ * @param {number} channel
377
+ * @returns {number}
378
+ */
379
+ sampleChannelBicubic(x, y, channel) {
394
380
 
395
- /**
396
- *
397
- * @deprecated
398
- * @param {number} x
399
- * @param {number}y
400
- * @param {Vector1|Vector2|Vector3|Vector4} result
401
- * @returns {number}
402
- */
403
- Sampler2D.prototype.get = function (x, y, result) {
404
- console.warn("Deprecated method, use sampleBilinear instead");
381
+ const itemSize = this.itemSize;
405
382
 
406
- const t = [];
383
+ const width = this.width;
384
+ const height = this.height;
407
385
 
408
- this.sampleBilinear(x, y, t, 0);
386
+ const data = this.data;
409
387
 
410
- if (result !== undefined) {
411
- result.readFromArray(t, 0);
412
- return result;
413
- } else {
414
- return t[0];
415
- }
416
- };
388
+ const rowSize = width * itemSize;
417
389
 
418
- /**
419
- *
420
- * @param {number} u
421
- * @param {number} v
422
- * @param {number[]} result
423
- */
424
- Sampler2D.prototype.sampleCatmullRomUV = function (u, v, result) {
425
- const itemSize = this.itemSize;
390
+ const x_max = width - 1;
391
+ const y_max = height - 1;
426
392
 
427
- for (let i = 0; i < itemSize; i++) {
428
- result[i] = this.sampleChannelCatmullRomUV(u, v, i);
429
- }
430
- };
393
+ const clamped_x = clamp(x, 0, x_max)
394
+ const clamped_y = clamp(y, 0, y_max)
431
395
 
432
- /**
433
- *
434
- * @param {number} u
435
- * @param {number} v
436
- * @param {number} channel
437
- * @returns {number}
438
- */
439
- Sampler2D.prototype.sampleChannelCatmullRomUV = function (u, v, channel) {
440
- const x = u * (this.width);
441
- const y = v * (this.height);
396
+ // fractional texel position (from the nearest low texel)
397
+ const x1 = clamped_x | 0;
398
+ const y1 = clamped_y | 0;
442
399
 
443
- return this.sampleChannelCatmullRom(x - 0.5, y - 0.5, channel);
444
- };
400
+ const xd = clamped_x - x1;
401
+ const yd = clamped_y - y1;
445
402
 
446
- /**
447
- *
448
- * @see https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
449
- * @param {number} x
450
- * @param {number} y
451
- * @param {number} channel
452
- * @returns {number}
453
- */
454
- Sampler2D.prototype.sampleChannelCatmullRom = function (x, y, channel) {
455
- // We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding
456
- // down the sample location to get the exact center of our "starting" texel. The starting texel will be at
457
- // location [1, 1] in the grid, where [0, 0] is the top-left corner.
458
- const texPos1_x = Math.floor(x - 0.5) + 0.5;
459
- const texPos1_y = Math.floor(y - 0.5) + 0.5;
460
-
461
- // Compute the fractional offset from our starting texel to our original sample location, which we'll
462
- // feed into the Catmull-Rom spline function to get our filter weights.
463
- const f_x = x - texPos1_x;
464
- const f_y = y - texPos1_y;
465
-
466
- // Compute the Catmull-Rom weights using the fractional offset that we calculated earlier.
467
- // These equations are pre-expanded based on our knowledge of where the texels will be located,
468
- // which lets us avoid having to evaluate a piece-wise function.
469
- const w0_x = f_x * (-0.5 + f_x * (1.0 - 0.5 * f_x));
470
- const w0_y = f_y * (-0.5 + f_y * (1.0 - 0.5 * f_y));
471
-
472
- const w1_x = 1.0 + f_x * f_x * (-2.5 + 1.5 * f_x);
473
- const w1_y = 1.0 + f_y * f_y * (-2.5 + 1.5 * f_y);
474
-
475
- const w2_x = f_x * (0.5 + f_x * (2.0 - 1.5 * f_x));
476
- const w2_y = f_y * (0.5 + f_y * (2.0 - 1.5 * f_y));
477
-
478
- const w3_x = f_x * f_x * (-0.5 + 0.5 * f_x);
479
- const w3_y = f_y * f_y * (-0.5 + 0.5 * f_y);
480
-
481
- // Work out weighting factors and sampling offsets that will let us use bilinear filtering to
482
- // simultaneously evaluate the middle 2 samples from the 4x4 grid.
483
- const w12_x = w1_x + w2_x;
484
- const w12_y = w1_y + w2_y;
485
- const offset12_x = w2_x / w12_x;
486
- const offset12_y = w2_y / w12_y;
487
-
488
- // Compute the final coordinates we'll use for sampling the texture
489
- const texPos0_x = texPos1_x - 1;
490
- const texPos0_y = texPos1_y - 1;
491
- const texPos3_x = texPos1_x + 2;
492
- const texPos3_y = texPos1_y + 2;
493
- const texPos12_x = texPos1_x + offset12_x;
494
- const texPos12_y = texPos1_y + offset12_y;
495
-
496
-
497
- let result = 0.0;
498
- result += this.sampleChannelBilinear(texPos0_x, texPos0_y, channel) * w0_x * w0_y;
499
- result += this.sampleChannelBilinear(texPos12_x, texPos0_y, channel) * w12_x * w0_y;
500
- result += this.sampleChannelBilinear(texPos3_x, texPos0_y, channel) * w3_x * w0_y;
501
-
502
- result += this.sampleChannelBilinear(texPos0_x, texPos12_y, channel) * w0_x * w12_y;
503
- result += this.sampleChannelBilinear(texPos12_x, texPos12_y, channel) * w12_x * w12_y;
504
- result += this.sampleChannelBilinear(texPos3_x, texPos12_y, channel) * w3_x * w12_y;
505
-
506
- result += this.sampleChannelBilinear(texPos0_x, texPos3_y, channel) * w0_x * w3_y;
507
- result += this.sampleChannelBilinear(texPos12_x, texPos3_y, channel) * w12_x * w3_y;
508
- result += this.sampleChannelBilinear(texPos3_x, texPos3_y, channel) * w3_x * w3_y;
509
-
510
- return result;
511
- }
403
+ const x0 = max2(0, x1 - 1);
404
+ const y0 = max2(0, y1 - 1);
512
405
 
513
- /**
514
- * Based on code from reddit https://www.reddit.com/r/javascript/comments/jxa8x/bicubic_interpolation/
515
- * @param {number} t
516
- * @param {number} a
517
- * @param {number} b
518
- * @param {number} c
519
- * @param {number} d
520
- * @returns {number}
521
- */
522
- function bicubic_terp(t, a, b, c, d) {
523
- return 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * t) * t) * t + b;
524
- }
406
+ const x2 = min2(x_max, x1 + 1);
407
+ const y2 = min2(y_max, y1 + 1);
525
408
 
526
- /**
527
- *
528
- * @param {number} u
529
- * @param {number} v
530
- * @param {number[]} result
531
- */
532
- Sampler2D.prototype.sampleBicubicUV = function (u, v, result) {
533
- const itemSize = this.itemSize;
409
+ const x3 = min2(x_max, x2 + 1);
410
+ const y3 = min2(y_max, y2 + 1);
534
411
 
535
- for (let i = 0; i < itemSize; i++) {
536
- result[i] = this.sampleChannelBicubicUV(u, v, i);
537
- }
538
- };
412
+ // compute row offsets
413
+ const row0 = y0 * rowSize;
414
+ const row1 = y1 * rowSize;
415
+ const row2 = y2 * rowSize;
416
+ const row3 = y3 * rowSize;
539
417
 
540
- /**
541
- *
542
- * @param {number} u
543
- * @param {number} v
544
- * @param {number} channel
545
- * @returns {number}
546
- */
547
- Sampler2D.prototype.sampleChannelBicubicUV = function (u, v, channel) {
548
- const x = u * (this.width);
549
- const y = v * (this.height);
418
+ const row0_address = row0 + channel;
419
+ const row1_address = row1 + channel;
420
+ const row2_address = row2 + channel;
421
+ const row3_address = row3 + channel;
550
422
 
551
- return this.sampleChannelBicubic(x - 0.5, y - 0.5, channel);
552
- };
423
+ const col0_offset = x0 * itemSize;
424
+ const col1_offset = x1 * itemSize;
425
+ const col2_offset = x2 * itemSize;
426
+ const col3_offset = x3 * itemSize;
553
427
 
554
- /**
555
- * Bicubic-filtered sampling
556
- * @param {number} x
557
- * @param {number} y
558
- * @param {number} channel
559
- * @returns {number}
560
- */
561
- Sampler2D.prototype.sampleChannelBicubic = function (x, y, channel) {
428
+ // read samples
429
+ const vi0 = data[row0_address + col0_offset];
430
+ const vi1 = data[row0_address + col1_offset];
431
+ const vi2 = data[row0_address + col2_offset];
432
+ const vi3 = data[row0_address + col3_offset];
562
433
 
563
- const itemSize = this.itemSize;
434
+ const vj0 = data[row1_address + col0_offset];
435
+ const vj1 = data[row1_address + col1_offset];
436
+ const vj2 = data[row1_address + col2_offset];
437
+ const vj3 = data[row1_address + col3_offset];
564
438
 
565
- const width = this.width;
566
- const height = this.height;
439
+ const vk0 = data[row2_address + col0_offset];
440
+ const vk1 = data[row2_address + col1_offset];
441
+ const vk2 = data[row2_address + col2_offset];
442
+ const vk3 = data[row2_address + col3_offset];
567
443
 
568
- const data = this.data;
444
+ const vl0 = data[row3_address + col0_offset];
445
+ const vl1 = data[row3_address + col1_offset];
446
+ const vl2 = data[row3_address + col2_offset];
447
+ const vl3 = data[row3_address + col3_offset];
569
448
 
570
- const rowSize = width * itemSize;
571
449
 
572
- const x_max = width - 1;
573
- const y_max = height - 1;
450
+ // return bicubic(xd, yd,
451
+ // vi0, vi1, vi2, vi3,
452
+ // vj0, vj1, vj2, vj3,
453
+ // vk0, vk1, vk2, vk3,
454
+ // vl0, vl1, vl2, vl3,
455
+ // );
574
456
 
575
- const clamped_x = clamp(x, 0, x_max)
576
- const clamped_y = clamp(y, 0, y_max)
577
457
 
578
- // fractional texel position (from the nearest low texel)
579
- const x1 = clamped_x | 0;
580
- const y1 = clamped_y | 0;
458
+ // perform filtering in X (rows)
459
+ const s0 = bicubic_terp(xd, vi0, vi1, vi2, vi3);
460
+ const s1 = bicubic_terp(xd, vj0, vj1, vj2, vj3);
461
+ const s2 = bicubic_terp(xd, vk0, vk1, vk2, vk3);
462
+ const s3 = bicubic_terp(xd, vl0, vl1, vl2, vl3);
581
463
 
582
- const xd = clamped_x - x1;
583
- const yd = clamped_y - y1;
464
+ // filter in Y (columns)
465
+ return bicubic_terp(yd, s0, s1, s2, s3);
466
+ }
584
467
 
585
- const x0 = max2(0, x1 - 1);
586
- const y0 = max2(0, y1 - 1);
468
+ /**
469
+ *
470
+ * @param {number} u
471
+ * @param {number} v
472
+ * @param {number[]} result
473
+ * @param {number} result_offset
474
+ */
475
+ sampleBilinearUV(u, v, result, result_offset = 0) {
587
476
 
588
- const x2 = min2(x_max, x1 + 1);
589
- const y2 = min2(y_max, y1 + 1);
477
+ const itemSize = this.itemSize;
590
478
 
591
- const x3 = min2(x_max, x2 + 1);
592
- const y3 = min2(y_max, y2 + 1);
479
+ for (let i = 0; i < itemSize; i++) {
480
+ result[i + result_offset] = this.sampleChannelBilinearUV(u, v, i);
593
481
 
594
- // compute row offsets
595
- const row0 = y0 * rowSize;
596
- const row1 = y1 * rowSize;
597
- const row2 = y2 * rowSize;
598
- const row3 = y3 * rowSize;
482
+ }
483
+ }
599
484
 
600
- const row0_address = row0 + channel;
601
- const row1_address = row1 + channel;
602
- const row2_address = row2 + channel;
603
- const row3_address = row3 + channel;
485
+ /**
486
+ *
487
+ * @param {number} x
488
+ * @param {number} y
489
+ * @param {number[]} result
490
+ * @param {number} result_offset
491
+ */
492
+ sampleBilinear(x, y, result, result_offset = 0) {
604
493
 
605
- const col0_offset = x0 * itemSize;
606
- const col1_offset = x1 * itemSize;
607
- const col2_offset = x2 * itemSize;
608
- const col3_offset = x3 * itemSize;
494
+ const itemSize = this.itemSize;
609
495
 
610
- // read samples
611
- const vi0 = data[row0_address + col0_offset];
612
- const vi1 = data[row0_address + col1_offset];
613
- const vi2 = data[row0_address + col2_offset];
614
- const vi3 = data[row0_address + col3_offset];
496
+ for (let i = 0; i < itemSize; i++) {
497
+ //TODO this can be optimized greatly
498
+ result[i + result_offset] = this.sampleChannelBilinear(x, y, i);
615
499
 
616
- const vj0 = data[row1_address + col0_offset];
617
- const vj1 = data[row1_address + col1_offset];
618
- const vj2 = data[row1_address + col2_offset];
619
- const vj3 = data[row1_address + col3_offset];
500
+ }
501
+ }
620
502
 
621
- const vk0 = data[row2_address + col0_offset];
622
- const vk1 = data[row2_address + col1_offset];
623
- const vk2 = data[row2_address + col2_offset];
624
- const vk3 = data[row2_address + col3_offset];
503
+ /**
504
+ *
505
+ * @param {number} u
506
+ * @param {number} v
507
+ * @param {number} channel
508
+ * @return {number}
509
+ */
510
+ sampleChannelBilinearUV(u, v, channel) {
511
+ const x = u * (this.width);
512
+ const y = v * (this.height);
625
513
 
626
- const vl0 = data[row3_address + col0_offset];
627
- const vl1 = data[row3_address + col1_offset];
628
- const vl2 = data[row3_address + col2_offset];
629
- const vl3 = data[row3_address + col3_offset];
514
+ return this.sampleChannelBilinear(x - 0.5, y - 0.5, channel);
515
+ }
630
516
 
517
+ /**
518
+ *
519
+ * @param {number} x
520
+ * @param {number} y
521
+ * @param {number} channel
522
+ * @returns {number}
523
+ */
524
+ sampleChannelBilinear(x, y, channel) {
631
525
 
632
- // return bicubic(xd, yd,
633
- // vi0, vi1, vi2, vi3,
634
- // vj0, vj1, vj2, vj3,
635
- // vk0, vk1, vk2, vk3,
636
- // vl0, vl1, vl2, vl3,
637
- // );
526
+ const itemSize = this.itemSize;
638
527
 
528
+ const width = this.width;
529
+ const height = this.height;
639
530
 
640
- // perform filtering in X (rows)
641
- const s0 = bicubic_terp(xd, vi0, vi1, vi2, vi3);
642
- const s1 = bicubic_terp(xd, vj0, vj1, vj2, vj3);
643
- const s2 = bicubic_terp(xd, vk0, vk1, vk2, vk3);
644
- const s3 = bicubic_terp(xd, vl0, vl1, vl2, vl3);
531
+ const rowSize = width * itemSize;
645
532
 
646
- // filter in Y (columns)
647
- return bicubic_terp(yd, s0, s1, s2, s3);
648
- };
533
+ //sample 4 points
534
+ const x_max = width - 1;
535
+ const y_max = height - 1;
649
536
 
650
- /**
651
- *
652
- * @param {number} u
653
- * @param {number} v
654
- * @param {number[]} result
655
- * @param {number} result_offset
656
- */
657
- Sampler2D.prototype.sampleBilinearUV = function (u, v, result, result_offset = 0) {
537
+ const clamped_x = clamp(x, 0, x_max);
538
+ const clamped_y = clamp(y, 0, y_max);
658
539
 
659
- const itemSize = this.itemSize;
540
+ const x0 = clamped_x | 0;
541
+ const y0 = clamped_y | 0;
660
542
 
661
- for (let i = 0; i < itemSize; i++) {
662
- result[i + result_offset] = this.sampleChannelBilinearUV(u, v, i);
543
+ //
544
+ const row0 = y0 * rowSize;
545
+ const col0_offset = x0 * itemSize + channel;
663
546
 
664
- }
665
- }
547
+ const i0 = row0 + col0_offset;
666
548
 
667
- /**
668
- *
669
- * @param {number} x
670
- * @param {number} y
671
- * @param {number[]} result
672
- * @param {number} result_offset
673
- */
674
- Sampler2D.prototype.sampleBilinear = function (x, y, result, result_offset = 0) {
549
+ //
550
+ let x1, y1;
675
551
 
676
- const itemSize = this.itemSize;
552
+ if (clamped_x === x0 || x0 >= x_max) {
553
+ x1 = x0;
554
+ } else {
555
+ x1 = x0 + 1;
556
+ }
677
557
 
678
- for (let i = 0; i < itemSize; i++) {
679
- //TODO this can be optimized greatly
680
- result[i + result_offset] = this.sampleChannelBilinear(x, y, i);
681
558
 
682
- }
683
- };
559
+ if (clamped_y === y0 || y0 >= y_max) {
560
+ y1 = y0;
561
+ } else {
562
+ y1 = y0 + 1;
563
+ }
684
564
 
685
- /**
686
- *
687
- * @param {number} u
688
- * @param {number} v
689
- * @param {number} channel
690
- * @return {number}
691
- */
692
- Sampler2D.prototype.sampleChannelBilinearUV = function (u, v, channel) {
693
- const x = u * (this.width);
694
- const y = v * (this.height);
565
+ const data = this.data;
695
566
 
696
- return this.sampleChannelBilinear(x - 0.5, y - 0.5, channel);
697
- };
567
+ const q0 = data[i0];
698
568
 
699
- /**
700
- *
701
- * @param {number} x
702
- * @param {number} y
703
- * @param {number} channel
704
- * @returns {number}
705
- */
706
- Sampler2D.prototype.sampleChannelBilinear = function (x, y, channel) {
569
+ if (x0 === x1 && y0 === y1) {
570
+ return q0;
571
+ }
707
572
 
708
- const itemSize = this.itemSize;
573
+ //
574
+ const xd = clamped_x - x0;
575
+ const yd = clamped_y - y0;
709
576
 
710
- const width = this.width;
711
- const height = this.height;
577
+ const col1_offset = x1 * itemSize + channel;
712
578
 
713
- const rowSize = width * itemSize;
579
+ const i1 = row0 + col1_offset;
714
580
 
715
- //sample 4 points
716
- const x_max = width - 1;
717
- const y_max = height - 1;
581
+ const row1 = y1 * rowSize;
718
582
 
719
- const clamped_x = clamp(x, 0, x_max);
720
- const clamped_y = clamp(y, 0, y_max);
583
+ const j0 = row1 + col0_offset;
584
+ const j1 = row1 + col1_offset;
721
585
 
722
- const x0 = clamped_x | 0;
723
- const y0 = clamped_y | 0;
586
+ const q1 = data[i1];
587
+ const p0 = data[j0];
588
+ const p1 = data[j1];
724
589
 
725
- //
726
- const row0 = y0 * rowSize;
727
- const col0_offset = x0 * itemSize + channel;
590
+ // perform Bi-Linear interpolation
591
+ const s0 = mix(q0, q1, xd);
592
+ const s1 = mix(p0, p1, xd);
728
593
 
729
- const i0 = row0 + col0_offset;
594
+ return mix(s0, s1, yd);
595
+ }
730
596
 
731
- //
732
- let x1, y1;
597
+ sampleNearestUV(u, v, result) {
598
+ const x = Math.round(u * (this.width) - 0.5);
599
+ const y = Math.round(v * (this.height) - 0.5);
733
600
 
734
- if (clamped_x === x0 || x0 >= x_max) {
735
- x1 = x0;
736
- } else {
737
- x1 = x0 + 1;
601
+ this.read(min2(x, this.width - 1), min2(y, this.height - 1), result);
738
602
  }
739
603
 
604
+ /**
605
+ *
606
+ * @param {number} x
607
+ * @param {number} y
608
+ * @param {number} channel
609
+ * @returns {number}
610
+ */
611
+ readChannel(x, y, channel) {
612
+ assert.isNumber(x, "x");
613
+ assert.isNumber(y, "y");
614
+ assert.isNumber(channel, "channel");
740
615
 
741
- if (clamped_y === y0 || y0 >= y_max) {
742
- y1 = y0;
743
- } else {
744
- y1 = y0 + 1;
616
+ assert.isNonNegativeInteger(channel, 'channel');
617
+
618
+ const index = (y * this.width + x) * this.itemSize + channel;
619
+
620
+ return this.data[index];
745
621
  }
746
622
 
747
- const data = this.data;
623
+ /**
624
+ *
625
+ * @param {number} x
626
+ * @param {number} y
627
+ * @param {number[]} result
628
+ */
629
+ read(x, y, result) {
748
630
 
749
- const q0 = data[i0];
631
+ const width = this.width;
750
632
 
751
- if (x0 === x1 && y0 === y1) {
752
- return q0;
753
- }
633
+ const itemSize = this.itemSize;
754
634
 
755
- //
756
- const xd = clamped_x - x0;
757
- const yd = clamped_y - y0;
635
+ const i0 = (y * width + x) * itemSize;
758
636
 
759
- const col1_offset = x1 * itemSize + channel;
637
+ for (let i = 0; i < itemSize; i++) {
638
+ const v = this.data[i0 + i];
760
639
 
761
- const i1 = row0 + col1_offset;
640
+ result[i] = v;
641
+ }
642
+ }
762
643
 
763
- const row1 = y1 * rowSize;
644
+ /**
645
+ *
646
+ * @param {number} u
647
+ * @param {number} v
648
+ * @param {Vector4|Vector3|Vector2} [result]
649
+ * @deprecated
650
+ */
651
+ sample(u, v, result) {
652
+ console.warn("Deprecated method, use sampleBilinear instead");
764
653
 
765
- const j0 = row1 + col0_offset;
766
- const j1 = row1 + col1_offset;
654
+ const temp = [];
767
655
 
768
- const q1 = data[i1];
769
- const p0 = data[j0];
770
- const p1 = data[j1];
656
+ this.sampleBilinear(u * (this.width - 1), v * (this.height - 1), temp, 0);
771
657
 
772
- // perform Bi-Linear interpolation
773
- const s0 = mix(q0, q1, xd);
774
- const s1 = mix(p0, p1, xd);
658
+ result.readFromArray(temp);
775
659
 
776
- return mix(s0, s1, yd);
777
- };
660
+ return temp[0];
661
+ }
778
662
 
779
- Sampler2D.prototype.sampleNearestUV = function (u, v, result) {
780
- const x = Math.round(u * (this.width) - 0.5);
781
- const y = Math.round(v * (this.height) - 0.5);
663
+ /**
664
+ *
665
+ * @param {number} index
666
+ * @param {number[]} result
667
+ */
668
+ computeNeighbors(index, result) {
669
+ const width = this.width;
670
+ const height = this.height;
671
+
672
+ const x = index % width;
673
+ const y = (index / width) | 0;
674
+ if (x > 0) {
675
+ result.push(index - 1);
676
+ }
677
+ if (x < width - 1) {
678
+ result.push(index + 1);
679
+ }
680
+ if (y > 0) {
681
+ result.push(index - width);
682
+ }
683
+ if (y < height - 1) {
684
+ result.push(index + width);
685
+ }
686
+ }
782
687
 
783
- this.read(min2(x, this.width - 1), min2(y, this.height - 1), result);
784
- }
688
+ /**
689
+ *
690
+ * @param {number} x
691
+ * @param {number} y
692
+ * @returns {number}
693
+ */
694
+ point2index(x, y) {
695
+ return x + y * this.width;
696
+ }
785
697
 
786
- /**
787
- *
788
- * @param {number} x
789
- * @param {number} y
790
- * @param {number} channel
791
- * @returns {number}
792
- */
793
- Sampler2D.prototype.readChannel = function (x, y, channel) {
794
- assert.isNumber(x, "x");
795
- assert.isNumber(y, "y");
796
- assert.isNumber(channel, "channel");
698
+ /**
699
+ *
700
+ * @param {number} index
701
+ * @param {Vector2} result
702
+ */
703
+ index2point(index, result) {
704
+ const width = this.width;
797
705
 
798
- assert.isNonNegativeInteger(channel, 'channel');
706
+ const x = index % width;
707
+ const y = (index / width) | 0;
799
708
 
800
- const index = (y * this.width + x) * this.itemSize + channel;
709
+ result.set(x, y);
710
+ }
801
711
 
802
- return this.data[index];
803
- };
712
+ /**
713
+ *
714
+ * @param {number} scale
715
+ * @param {number} offset
716
+ * @return {function(index:int, array:ArrayLike, x:int, y:int)}
717
+ */
718
+ makeArrayFiller(scale, offset) {
719
+ scale = scale || 255;
720
+ offset = offset || 0;
721
+
722
+ const sampler = this;
723
+ const v4 = [1 / scale, 1 / scale, 1 / scale, 1 / scale];
724
+
725
+ //
726
+ function fillDD1(index, array, x, y) {
727
+ const val = (sampler.sampleChannelBilinear(x, y, 0) + offset) * scale | 0;
728
+ array[index] = val;
729
+ array[index + 1] = val;
730
+ array[index + 2] = val;
731
+ array[index + 3] = 255;
732
+ }
804
733
 
805
- /**
806
- *
807
- * @param {number} x
808
- * @param {number} y
809
- * @param {number[]} result
810
- */
811
- Sampler2D.prototype.read = function (x, y, result) {
734
+ function fillDD2(index, array, x, y) {
735
+ sampler.sampleBilinear(x, y, v4, 0);
736
+ const val = (v4[0] + offset) * scale | 0;
737
+ array.fill(val, index, index + 3);
738
+ array[index + 3] = (v4[1] + offset) * scale | 0;
739
+ }
812
740
 
813
- const width = this.width;
741
+ function fillDD3(index, array, x, y) {
814
742
 
815
- const itemSize = this.itemSize;
743
+ sampler.sampleBilinear(x, y, v4, 0);
816
744
 
817
- const i0 = (y * width + x) * itemSize;
745
+ array[index] = (v4[0] + offset) * scale | 0;
746
+ array[index + 1] = (v4[1] + offset) * scale | 0;
747
+ array[index + 2] = (v4[2] + offset) * scale | 0;
748
+ array[index + 3] = 255;
749
+ }
818
750
 
819
- for (let i = 0; i < itemSize; i++) {
820
- const v = this.data[i0 + i];
751
+ function fillDD4(index, array, x, y) {
752
+ sampler.sampleBilinear(x, y, v4, 0);
753
+ array[index] = (v4[0] + offset) * scale | 0;
754
+ array[index + 1] = (v4[1] + offset) * scale | 0;
755
+ array[index + 2] = (v4[2] + offset) * scale | 0;
756
+ array[index + 3] = (v4[3] + offset) * scale | 0;
757
+ }
821
758
 
822
- result[i] = v;
759
+ let fillDD;
760
+ switch (sampler.itemSize) {
761
+ case 1:
762
+ fillDD = fillDD1;
763
+ break;
764
+ case 2:
765
+ fillDD = fillDD2;
766
+ break;
767
+ case 3:
768
+ fillDD = fillDD3;
769
+ break;
770
+ case 4:
771
+ fillDD = fillDD4;
772
+ break;
773
+ default :
774
+ throw new Error("unsupported item size");
775
+ break;
776
+ }
777
+ return fillDD;
823
778
  }
824
- };
825
779
 
780
+ /**
781
+ * Copy a patch from another sampler with a margin.
782
+ * This is useful for texture rendering where filtering can cause bleeding along the edges of the patch.
783
+ * @param {Sampler2D} source where to copy from
784
+ * @param {Number} sourceX where to start reading from, X coordinate
785
+ * @param {Number} sourceY where to start reading from, X coordinate
786
+ * @param {Number} destinationX where to start writing to, X coordinate
787
+ * @param {Number} destinationY where to start writing to, X coordinate
788
+ * @param {Number} width size of the patch that is to be copied
789
+ * @param {Number} height size of the patch that is to be copied
790
+ * @param {Number} marginLeft
791
+ * @param {Number} marginRight
792
+ * @param {Number} marginTop
793
+ * @param {Number} marginBottom
794
+ */
795
+ copyWithMargin(source, sourceX, sourceY, destinationX, destinationY, width, height, marginLeft, marginRight, marginTop, marginBottom) {
796
+ const dItemSize = this.itemSize;
797
+ const sItemSize = source.itemSize;
798
+ const _itemSize = Math.min(dItemSize, sItemSize);
826
799
 
827
- /**
828
- *
829
- * @param {number} u
830
- * @param {number} v
831
- * @param {Vector4|Vector3|Vector2} [result]
832
- * @deprecated
833
- */
834
- Sampler2D.prototype.sample = function (u, v, result) {
835
- console.warn("Deprecated method, use sampleBilinear instead");
836
800
 
837
- const temp = [];
801
+ const dRowSize = dItemSize * this.width;
802
+ const sRowSize = sItemSize * source.width;
838
803
 
839
- this.sampleBilinear(u * (this.width - 1), v * (this.height - 1), temp, 0);
804
+ const sData = source.data;
805
+ const dData = this.data;
840
806
 
841
- result.readFromArray(temp);
807
+ let x, y, i, j;
842
808
 
843
- return temp[0];
844
- };
809
+ let xMax, yMax;
845
810
 
846
- /**
847
- *
848
- * @param {number} index
849
- * @param {number[]} result
850
- */
851
- Sampler2D.prototype.computeNeighbors = function (index, result) {
852
- const width = this.width;
853
- const height = this.height;
854
-
855
- const x = index % width;
856
- const y = (index / width) | 0;
857
- if (x > 0) {
858
- result.push(index - 1);
859
- }
860
- if (x < width - 1) {
861
- result.push(index + 1);
862
- }
863
- if (y > 0) {
864
- result.push(index - width);
865
- }
866
- if (y < height - 1) {
867
- result.push(index + width);
868
- }
869
- };
811
+ let dA, sA, dOffset, sOffset;
812
+ //Write top-left corner
813
+ sOffset = sourceY * sRowSize + sourceX * dItemSize;
814
+ for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
815
+ dA = y * dRowSize;
870
816
 
871
- /**
872
- *
873
- * @param {number} x
874
- * @param {number} y
875
- * @returns {number}
876
- */
877
- Sampler2D.prototype.point2index = function (x, y) {
878
- return x + y * this.width;
879
- };
817
+ for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {
880
818
 
881
- /**
882
- *
883
- * @param {number} index
884
- * @param {Vector2} result
885
- */
886
- Sampler2D.prototype.index2point = function (index, result) {
887
- const width = this.width;
819
+ dOffset = dA + x * dItemSize;
888
820
 
889
- const x = index % width;
890
- const y = (index / width) | 0;
821
+ for (i = 0; i < _itemSize; i++) {
822
+ dData[dOffset + i] = sData[sOffset + i];
823
+ }
824
+ }
825
+ }
826
+ //Write top margin
827
+ sA = sourceY * sRowSize;
828
+ for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
829
+ dA = y * dRowSize;
891
830
 
892
- result.set(x, y);
893
- };
831
+ for (x = 0; x < width; x++) {
894
832
 
895
- /**
896
- *
897
- * @param {number} scale
898
- * @param {number} offset
899
- * @return {function(index:int, array:ArrayLike, x:int, y:int)}
900
- */
901
- Sampler2D.prototype.makeArrayFiller = function (scale, offset) {
902
- scale = scale || 255;
903
- offset = offset || 0;
904
-
905
- const sampler = this;
906
- const v4 = [1 / scale, 1 / scale, 1 / scale, 1 / scale];
907
-
908
- //
909
- function fillDD1(index, array, x, y) {
910
- const val = (sampler.sampleChannelBilinear(x, y, 0) + offset) * scale | 0;
911
- array[index] = val;
912
- array[index + 1] = val;
913
- array[index + 2] = val;
914
- array[index + 3] = 255;
915
- }
833
+ dOffset = dA + (x + destinationX) * dItemSize;
834
+ sOffset = sA + (x + sourceX) * dItemSize;
835
+ for (i = 0; i < _itemSize; i++) {
836
+ dData[dOffset + i] = sData[sOffset + i];
837
+ }
838
+ }
839
+ }
840
+ //Write top-right corner
841
+ sOffset = sourceY * sRowSize + (sourceX + width - 1) * dItemSize;
842
+ for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
843
+ dA = y * dRowSize;
916
844
 
917
- function fillDD2(index, array, x, y) {
918
- sampler.sampleBilinear(x, y, v4, 0);
919
- const val = (v4[0] + offset) * scale | 0;
920
- array.fill(val, index, index + 3);
921
- array[index + 3] = (v4[1] + offset) * scale | 0;
922
- }
845
+ for (x = destinationX + width, xMax = Math.min(this.width, x + marginRight); x < xMax; x++) {
923
846
 
924
- function fillDD3(index, array, x, y) {
847
+ dOffset = dA + x * dItemSize;
925
848
 
926
- sampler.sampleBilinear(x, y, v4, 0);
849
+ for (i = 0; i < _itemSize; i++) {
850
+ dData[dOffset + i] = sData[sOffset + i];
851
+ }
852
+ }
853
+ }
854
+ //Write left margin
855
+ for (y = 0; y < height; y++) {
856
+ dA = (y + destinationY) * dRowSize;
857
+ sA = (y + sourceY) * sRowSize;
927
858
 
928
- array[index] = (v4[0] + offset) * scale | 0;
929
- array[index + 1] = (v4[1] + offset) * scale | 0;
930
- array[index + 2] = (v4[2] + offset) * scale | 0;
931
- array[index + 3] = 255;
932
- }
859
+ sOffset = sA + (sourceX) * dItemSize;
933
860
 
934
- function fillDD4(index, array, x, y) {
935
- sampler.sampleBilinear(x, y, v4, 0);
936
- array[index] = (v4[0] + offset) * scale | 0;
937
- array[index + 1] = (v4[1] + offset) * scale | 0;
938
- array[index + 2] = (v4[2] + offset) * scale | 0;
939
- array[index + 3] = (v4[3] + offset) * scale | 0;
940
- }
861
+ for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {
941
862
 
942
- let fillDD;
943
- switch (sampler.itemSize) {
944
- case 1:
945
- fillDD = fillDD1;
946
- break;
947
- case 2:
948
- fillDD = fillDD2;
949
- break;
950
- case 3:
951
- fillDD = fillDD3;
952
- break;
953
- case 4:
954
- fillDD = fillDD4;
955
- break;
956
- default :
957
- throw new Error("unsupported item size");
958
- break;
959
- }
960
- return fillDD;
961
- };
863
+ dOffset = dA + x * dItemSize;
962
864
 
963
- /**
964
- * Copy a patch from another sampler with a margin.
965
- * This is useful for texture rendering where filtering can cause bleeding along the edges of the patch.
966
- * @param {Sampler2D} source where to copy from
967
- * @param {Number} sourceX where to start reading from, X coordinate
968
- * @param {Number} sourceY where to start reading from, X coordinate
969
- * @param {Number} destinationX where to start writing to, X coordinate
970
- * @param {Number} destinationY where to start writing to, X coordinate
971
- * @param {Number} width size of the patch that is to be copied
972
- * @param {Number} height size of the patch that is to be copied
973
- * @param {Number} marginLeft
974
- * @param {Number} marginRight
975
- * @param {Number} marginTop
976
- * @param {Number} marginBottom
977
- */
978
- Sampler2D.prototype.copyWithMargin = function (source, sourceX, sourceY, destinationX, destinationY, width, height, marginLeft, marginRight, marginTop, marginBottom) {
979
- const dItemSize = this.itemSize;
980
- const sItemSize = source.itemSize;
981
- const _itemSize = Math.min(dItemSize, sItemSize);
865
+ for (i = 0; i < _itemSize; i++) {
866
+ dData[dOffset + i] = sData[sOffset + i];
867
+ }
868
+ }
869
+ }
870
+ //write actual patch
871
+ this.copy(source, sourceX, sourceY, destinationX, destinationY, width, height);
982
872
 
873
+ //Write right margin
874
+ for (y = 0; y < height; y++) {
875
+ dA = (y + destinationY) * dRowSize;
876
+ sA = (y + sourceY) * sRowSize;
983
877
 
984
- const dRowSize = dItemSize * this.width;
985
- const sRowSize = sItemSize * source.width;
878
+ sOffset = sA + (sourceX + width - 1) * dItemSize;
986
879
 
987
- const sData = source.data;
988
- const dData = this.data;
880
+ for (x = destinationX + width, xMax = Math.min(this.width, x + marginRight); x < xMax; x++) {
989
881
 
990
- let x, y, i, j;
882
+ dOffset = dA + x * dItemSize;
991
883
 
992
- let xMax, yMax;
884
+ for (i = 0; i < _itemSize; i++) {
885
+ dData[dOffset + i] = sData[sOffset + i];
886
+ }
887
+ }
888
+ }
993
889
 
994
- let dA, sA, dOffset, sOffset;
995
- //Write top-left corner
996
- sOffset = sourceY * sRowSize + sourceX * dItemSize;
997
- for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
998
- dA = y * dRowSize;
890
+ //Write Bottom-left margin
891
+ sOffset = (sourceY + height - 1) * sRowSize + sourceX * dItemSize;
892
+ for (y = destinationY + width, yMax = Math.min(this.height, y + marginBottom); y < yMax; y++) {
893
+ dA = y * dRowSize;
999
894
 
1000
- for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {
895
+ for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {
1001
896
 
1002
- dOffset = dA + x * dItemSize;
897
+ dOffset = dA + x * dItemSize;
1003
898
 
1004
- for (i = 0; i < _itemSize; i++) {
1005
- dData[dOffset + i] = sData[sOffset + i];
899
+ for (i = 0; i < _itemSize; i++) {
900
+ dData[dOffset + i] = sData[sOffset + i];
901
+ }
1006
902
  }
1007
903
  }
1008
- }
1009
- //Write top margin
1010
- sA = sourceY * sRowSize;
1011
- for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
1012
- dA = y * dRowSize;
904
+ //Write Bottom margin
905
+ sA = (sourceY + height - 1) * sRowSize;
906
+ for (y = destinationY + width, yMax = Math.min(this.height, y + marginBottom); y < yMax; y++) {
907
+ dA = y * dRowSize;
1013
908
 
1014
- for (x = 0; x < width; x++) {
909
+ for (x = 0; x < width; x++) {
1015
910
 
1016
- dOffset = dA + (x + destinationX) * dItemSize;
1017
- sOffset = sA + (x + sourceX) * dItemSize;
1018
- for (i = 0; i < _itemSize; i++) {
1019
- dData[dOffset + i] = sData[sOffset + i];
911
+ dOffset = dA + (x + destinationX) * dItemSize;
912
+ sOffset = sA + (x + sourceX) * dItemSize;
913
+ for (i = 0; i < _itemSize; i++) {
914
+ dData[dOffset + i] = sData[sOffset + i];
915
+ }
1020
916
  }
1021
917
  }
1022
- }
1023
- //Write top-right corner
1024
- sOffset = sourceY * sRowSize + (sourceX + width - 1) * dItemSize;
1025
- for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
1026
- dA = y * dRowSize;
918
+ //Write Bottom-right margin
919
+ sOffset = (sourceY + height - 1) * sRowSize + (sourceX + width - 1) * dItemSize;
920
+ for (y = destinationY + width, yMax = Math.min(this.height, y + marginBottom); y < yMax; y++) {
921
+ dA = y * dRowSize;
1027
922
 
1028
- for (x = destinationX + width, xMax = Math.min(this.width, x + marginRight); x < xMax; x++) {
923
+ for (x = destinationX + width, xMax = Math.min(this.width, x + marginRight); x < xMax; x++) {
1029
924
 
1030
- dOffset = dA + x * dItemSize;
925
+ dOffset = dA + x * dItemSize;
1031
926
 
1032
- for (i = 0; i < _itemSize; i++) {
1033
- dData[dOffset + i] = sData[sOffset + i];
927
+ for (i = 0; i < _itemSize; i++) {
928
+ dData[dOffset + i] = sData[sOffset + i];
929
+ }
1034
930
  }
1035
931
  }
932
+
933
+ this.version++;
1036
934
  }
1037
- //Write left margin
1038
- for (y = 0; y < height; y++) {
1039
- dA = (y + destinationY) * dRowSize;
1040
- sA = (y + sourceY) * sRowSize;
1041
935
 
1042
- sOffset = sA + (sourceX) * dItemSize;
936
+ /**
937
+ * Copy a patch from another sampler
938
+ * @param {Sampler2D} source where to copy from
939
+ * @param {Number} sourceX where to start reading from, X coordinate
940
+ * @param {Number} sourceY where to start reading from, X coordinate
941
+ * @param {Number} destinationX where to start writing to, X coordinate
942
+ * @param {Number} destinationY where to start writing to, X coordinate
943
+ * @param {Number} width size of the patch that is to be copied
944
+ * @param {Number} height size of the patch that is to be copied
945
+ */
946
+ copy(
947
+ source, sourceX, sourceY,
948
+ destinationX, destinationY, width, height
949
+ ) {
1043
950
 
1044
- for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {
951
+ assert.isNumber(sourceX, 'sourceX');
952
+ assert.isNumber(sourceY, 'sourceY');
1045
953
 
1046
- dOffset = dA + x * dItemSize;
954
+ assert.isNumber(destinationX, 'destinationX');
955
+ assert.isNumber(destinationY, 'destinationY');
1047
956
 
1048
- for (i = 0; i < _itemSize; i++) {
1049
- dData[dOffset + i] = sData[sOffset + i];
1050
- }
1051
- }
1052
- }
1053
- //write actual patch
1054
- this.copy(source, sourceX, sourceY, destinationX, destinationY, width, height);
957
+ assert.isNumber(width, 'width');
958
+ assert.isNumber(height, 'height');
1055
959
 
1056
- //Write right margin
1057
- for (y = 0; y < height; y++) {
1058
- dA = (y + destinationY) * dRowSize;
1059
- sA = (y + sourceY) * sRowSize;
960
+ const _w = Math.min(width, source.width - sourceX, this.width - destinationX);
961
+ const _h = Math.min(height, source.height - sourceY, this.height - destinationY);
1060
962
 
1061
- sOffset = sA + (sourceX + width - 1) * dItemSize;
1062
963
 
1063
- for (x = destinationX + width, xMax = Math.min(this.width, x + marginRight); x < xMax; x++) {
964
+ const dItemSize = this.itemSize;
965
+ const sItemSize = source.itemSize;
966
+ const _itemSize = Math.min(dItemSize, sItemSize);
1064
967
 
1065
- dOffset = dA + x * dItemSize;
1066
968
 
1067
- for (i = 0; i < _itemSize; i++) {
1068
- dData[dOffset + i] = sData[sOffset + i];
969
+ const dRowSize = dItemSize * this.width;
970
+ const sRowSize = sItemSize * source.width;
971
+
972
+ const sData = source.data;
973
+ const dData = this.data;
974
+
975
+ let x, y, i;
976
+
977
+ for (y = 0; y < _h; y++) {
978
+ const dA = (y + destinationY) * dRowSize;
979
+ const sA = (y + sourceY) * sRowSize;
980
+ for (x = 0; x < _w; x++) {
981
+ const dOffset = dA + (x + destinationX) * dItemSize;
982
+ const sOffset = sA + (x + sourceX) * sItemSize;
983
+ for (i = 0; i < _itemSize; i++) {
984
+ dData[dOffset + i] = sData[sOffset + i];
985
+ }
1069
986
  }
1070
987
  }
988
+
989
+ this.version++;
1071
990
  }
1072
991
 
1073
- //Write Bottom-left margin
1074
- sOffset = (sourceY + height - 1) * sRowSize + sourceX * dItemSize;
1075
- for (y = destinationY + width, yMax = Math.min(this.height, y + marginBottom); y < yMax; y++) {
1076
- dA = y * dRowSize;
992
+ /**
993
+ * Copy a patch from another sampler with the same itemSize
994
+ * @param {Sampler2D} source where to copy from
995
+ * @param {Number} sourceX where to start reading from, X coordinate
996
+ * @param {Number} sourceY where to start reading from, X coordinate
997
+ * @param {Number} destinationX where to start writing to, X coordinate
998
+ * @param {Number} destinationY where to start writing to, X coordinate
999
+ * @param {Number} width size of the patch that is to be copied
1000
+ * @param {Number} height size of the patch that is to be copied
1001
+ */
1002
+ copy_sameItemSize(source, sourceX, sourceY, destinationX, destinationY, width, height) {
1003
+ const itemSize = this.itemSize;
1004
+ const sItemSize = source.itemSize;
1077
1005
 
1078
- for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {
1006
+ assert.equal(sItemSize, sItemSize, `source.itemSize(=${sItemSize}) != this.itemSize(=${itemSize})`);
1079
1007
 
1080
- dOffset = dA + x * dItemSize;
1008
+ const _w = Math.min(width, source.width - sourceX, this.width - destinationX);
1009
+ const _h = Math.min(height, source.height - sourceY, this.height - destinationY);
1081
1010
 
1082
- for (i = 0; i < _itemSize; i++) {
1083
- dData[dOffset + i] = sData[sOffset + i];
1084
- }
1085
- }
1086
- }
1087
- //Write Bottom margin
1088
- sA = (sourceY + height - 1) * sRowSize;
1089
- for (y = destinationY + width, yMax = Math.min(this.height, y + marginBottom); y < yMax; y++) {
1090
- dA = y * dRowSize;
1091
1011
 
1092
- for (x = 0; x < width; x++) {
1012
+ const dRowSize = itemSize * this.width;
1013
+ const sRowSize = itemSize * source.width;
1093
1014
 
1094
- dOffset = dA + (x + destinationX) * dItemSize;
1095
- sOffset = sA + (x + sourceX) * dItemSize;
1096
- for (i = 0; i < _itemSize; i++) {
1097
- dData[dOffset + i] = sData[sOffset + i];
1098
- }
1099
- }
1100
- }
1101
- //Write Bottom-right margin
1102
- sOffset = (sourceY + height - 1) * sRowSize + (sourceX + width - 1) * dItemSize;
1103
- for (y = destinationY + width, yMax = Math.min(this.height, y + marginBottom); y < yMax; y++) {
1104
- dA = y * dRowSize;
1015
+ const sData = source.data;
1016
+ const dData = this.data;
1105
1017
 
1106
- for (x = destinationX + width, xMax = Math.min(this.width, x + marginRight); x < xMax; x++) {
1018
+ const patchRowSize = _w * itemSize;
1107
1019
 
1108
- dOffset = dA + x * dItemSize;
1020
+ let y, i;
1021
+
1022
+ for (y = 0; y < _h; y++) {
1023
+ const dA = (y + destinationY) * dRowSize;
1024
+ const sA = (y + sourceY) * sRowSize;
1025
+
1026
+ const dOffset = dA + destinationX * itemSize;
1027
+ const sOffset = sA + sourceX * itemSize;
1028
+
1029
+ for (i = 0; i < patchRowSize; i++) {
1109
1030
 
1110
- for (i = 0; i < _itemSize; i++) {
1111
1031
  dData[dOffset + i] = sData[sOffset + i];
1032
+
1112
1033
  }
1113
1034
  }
1035
+
1036
+ this.version++;
1114
1037
  }
1115
1038
 
1116
- this.version++;
1117
- };
1039
+ /**
1040
+ * Assumes both samplers are uint8 with values 0-255
1041
+ * @param {Sampler2D} source
1042
+ * @param sourceX
1043
+ * @param sourceY
1044
+ * @param destinationX
1045
+ * @param destinationY
1046
+ * @param width
1047
+ * @param height
1048
+ * @param {BlendingType} [blendMode]
1049
+ */
1050
+ paint(source, sourceX, sourceY, destinationX, destinationY, width, height, blendMode = BlendingType.Normal) {
1051
+ let blendFunction;
1052
+ if (blendMode === BlendingType.Normal) {
1053
+ blendFunction = blendFunctionNormal;
1054
+ } else {
1055
+ throw new Error(`Unsupported blendType(=${blendMode})`);
1056
+ }
1118
1057
 
1119
- /**
1120
- * Copy a patch from another sampler
1121
- * @param {Sampler2D} source where to copy from
1122
- * @param {Number} sourceX where to start reading from, X coordinate
1123
- * @param {Number} sourceY where to start reading from, X coordinate
1124
- * @param {Number} destinationX where to start writing to, X coordinate
1125
- * @param {Number} destinationY where to start writing to, X coordinate
1126
- * @param {Number} width size of the patch that is to be copied
1127
- * @param {Number} height size of the patch that is to be copied
1128
- */
1129
- Sampler2D.prototype.copy = function (
1130
- source, sourceX, sourceY,
1131
- destinationX, destinationY, width, height
1132
- ) {
1058
+ const _w = Math.min(width, source.width - sourceX, this.width - destinationX);
1059
+ const _h = Math.min(height, source.height - sourceY, this.height - destinationY);
1133
1060
 
1134
- assert.isNumber(sourceX, 'sourceX');
1135
- assert.isNumber(sourceY, 'sourceY');
1061
+ const _x0 = Math.max(0, -destinationX);
1062
+ const _y0 = Math.max(0, -destinationY);
1136
1063
 
1137
- assert.isNumber(destinationX, 'destinationX');
1138
- assert.isNumber(destinationY, 'destinationY');
1064
+ const c0 = [0, 0, 0, 255];
1065
+ const c1 = [0, 0, 0, 255];
1139
1066
 
1140
- assert.isNumber(width, 'width');
1141
- assert.isNumber(height, 'height');
1067
+ const c3 = [];
1142
1068
 
1143
- const _w = Math.min(width, source.width - sourceX, this.width - destinationX);
1144
- const _h = Math.min(height, source.height - sourceY, this.height - destinationY);
1069
+ let x, y;
1145
1070
 
1071
+ for (y = _y0; y < _h; y++) {
1072
+ for (x = _x0; x < _w; x++) {
1073
+ const d_x = Math.round(x + destinationX);
1074
+ const d_y = Math.round(y + destinationY);
1146
1075
 
1147
- const dItemSize = this.itemSize;
1148
- const sItemSize = source.itemSize;
1149
- const _itemSize = Math.min(dItemSize, sItemSize);
1076
+ this.read(d_x, d_y, c0);
1150
1077
 
1078
+ const s_x = Math.round(x + sourceY);
1079
+ const s_y = Math.round(y + sourceY);
1151
1080
 
1152
- const dRowSize = dItemSize * this.width;
1153
- const sRowSize = sItemSize * source.width;
1081
+ source.read(s_x, s_y, c1);
1154
1082
 
1155
- const sData = source.data;
1156
- const dData = this.data;
1083
+ blendFunction(c1, c0, c3);
1157
1084
 
1158
- let x, y, i;
1085
+ this.set(d_x, d_y, c3);
1159
1086
 
1160
- for (y = 0; y < _h; y++) {
1161
- const dA = (y + destinationY) * dRowSize;
1162
- const sA = (y + sourceY) * sRowSize;
1163
- for (x = 0; x < _w; x++) {
1164
- const dOffset = dA + (x + destinationX) * dItemSize;
1165
- const sOffset = sA + (x + sourceX) * sItemSize;
1166
- for (i = 0; i < _itemSize; i++) {
1167
- dData[dOffset + i] = sData[sOffset + i];
1168
1087
  }
1169
1088
  }
1170
- }
1171
1089
 
1172
- this.version++;
1173
- };
1174
1090
 
1091
+ }
1175
1092
 
1176
- /**
1177
- * Copy a patch from another sampler with the same itemSize
1178
- * @param {Sampler2D} source where to copy from
1179
- * @param {Number} sourceX where to start reading from, X coordinate
1180
- * @param {Number} sourceY where to start reading from, X coordinate
1181
- * @param {Number} destinationX where to start writing to, X coordinate
1182
- * @param {Number} destinationY where to start writing to, X coordinate
1183
- * @param {Number} width size of the patch that is to be copied
1184
- * @param {Number} height size of the patch that is to be copied
1185
- */
1186
- Sampler2D.prototype.copy_sameItemSize = function (source, sourceX, sourceY, destinationX, destinationY, width, height) {
1187
- const itemSize = this.itemSize;
1188
- const sItemSize = source.itemSize;
1093
+ /**
1094
+ * Fill data values with zeros for a given area
1095
+ * @param {Number} x
1096
+ * @param {Number} y
1097
+ * @param {Number} width
1098
+ * @param {Number} height
1099
+ */
1100
+ zeroFill(x, y, width, height) {
1189
1101
 
1190
- assert.equal(sItemSize, sItemSize, `source.itemSize(=${sItemSize}) != this.itemSize(=${itemSize})`);
1102
+ const x0 = clamp(x, 0, this.width);
1103
+ const y0 = clamp(y, 0, this.height);
1104
+ const x1 = clamp(x + width, 0, this.width);
1105
+ const y1 = clamp(y + height, 0, this.height);
1191
1106
 
1192
- const _w = Math.min(width, source.width - sourceX, this.width - destinationX);
1193
- const _h = Math.min(height, source.height - sourceY, this.height - destinationY);
1107
+ const data = this.data;
1108
+ const itemSize = this.itemSize;
1194
1109
 
1110
+ const rowSize = itemSize * this.width;
1195
1111
 
1196
- const dRowSize = itemSize * this.width;
1197
- const sRowSize = itemSize * source.width;
1112
+ const clearRowOffset0 = x0 * itemSize;
1113
+ const clearRowOffset1 = x1 * itemSize;
1198
1114
 
1199
- const sData = source.data;
1200
- const dData = this.data;
1115
+ let _y;
1201
1116
 
1202
- const patchRowSize = _w * itemSize;
1117
+ for (_y = y0; _y < y1; _y++) {
1203
1118
 
1204
- let y, i;
1119
+ const a = _y * rowSize;
1205
1120
 
1206
- for (y = 0; y < _h; y++) {
1207
- const dA = (y + destinationY) * dRowSize;
1208
- const sA = (y + sourceY) * sRowSize;
1121
+ data.fill(0, a + clearRowOffset0, a + clearRowOffset1);
1209
1122
 
1210
- const dOffset = dA + destinationX * itemSize;
1211
- const sOffset = sA + sourceX * itemSize;
1123
+ }
1212
1124
 
1213
- for (i = 0; i < patchRowSize; i++) {
1125
+ this.version++;
1126
+ }
1214
1127
 
1215
- dData[dOffset + i] = sData[sOffset + i];
1128
+ /**
1129
+ *
1130
+ * @param {number} channel_index
1131
+ * @param {number} value
1132
+ */
1133
+ fill_channel(channel_index, value) {
1134
+ const itemSize = this.itemSize;
1135
+ const data = this.data;
1136
+ const length = data.length;
1216
1137
 
1138
+ for (let i = channel_index; i < length; i += itemSize) {
1139
+ data[i] = value;
1217
1140
  }
1141
+
1142
+ this.version++;
1218
1143
  }
1219
1144
 
1220
- this.version++;
1221
- };
1145
+ /**
1146
+ *
1147
+ * @param {Number} x
1148
+ * @param {Number} y
1149
+ * @param {Number} width
1150
+ * @param {Number} height
1151
+ * @param {Array.<Number>} value
1152
+ */
1153
+ fill(x, y, width, height, value) {
1154
+
1155
+ const _w = this.width;
1156
+ const _h = this.height;
1222
1157
 
1158
+ const x0 = clamp(x, 0, _w);
1159
+ const y0 = clamp(y, 0, _h);
1160
+ const x1 = clamp(x + width, 0, _w);
1161
+ const y1 = clamp(y + height, 0, _h);
1223
1162
 
1224
- /**
1225
- *
1226
- * @param {number[]} source
1227
- * @param {number[]} destination
1228
- * @param {Array} result
1229
- */
1230
- function blendFunctionNormal(source, destination, result) {
1163
+ const data = this.data;
1164
+ const itemSize = this.itemSize;
1231
1165
 
1232
- const a1 = source[3] / 255;
1233
- const a0 = destination[3] / 255;
1166
+ const rowSize = itemSize * _w;
1234
1167
 
1235
- result[0] = source[0] * a1 + destination[0] * (1 - a1);
1236
- result[1] = source[1] * a1 + destination[1] * (1 - a1);
1237
- result[2] = source[2] * a1 + destination[2] * (1 - a1);
1238
- result[3] = (a1 + a0 * (1 - a1)) * 255;
1239
- }
1168
+ let _y, _x, i;
1240
1169
 
1241
- /**
1242
- * Assumes both samplers are uint8 with values 0-255
1243
- * @param {Sampler2D} source
1244
- * @param sourceX
1245
- * @param sourceY
1246
- * @param destinationX
1247
- * @param destinationY
1248
- * @param width
1249
- * @param height
1250
- * @param {BlendingType} [blendMode]
1251
- */
1252
- Sampler2D.prototype.paint = function (source, sourceX, sourceY, destinationX, destinationY, width, height, blendMode = BlendingType.Normal) {
1253
- let blendFunction;
1254
- if (blendMode === BlendingType.Normal) {
1255
- blendFunction = blendFunctionNormal;
1256
- } else {
1257
- throw new Error(`Unsupported blendType(=${blendMode})`);
1258
- }
1170
+ for (_y = y0; _y < y1; _y++) {
1259
1171
 
1260
- const _w = Math.min(width, source.width - sourceX, this.width - destinationX);
1261
- const _h = Math.min(height, source.height - sourceY, this.height - destinationY);
1172
+ const a = _y * rowSize;
1262
1173
 
1263
- const _x0 = Math.max(0, -destinationX);
1264
- const _y0 = Math.max(0, -destinationY);
1174
+ for (_x = x0; _x < x1; _x++) {
1265
1175
 
1266
- const c0 = [0, 0, 0, 255];
1267
- const c1 = [0, 0, 0, 255];
1176
+ const offset = a + _x * itemSize;
1268
1177
 
1269
- const c3 = [];
1178
+ for (i = 0; i < itemSize; i++) {
1270
1179
 
1271
- let x, y;
1180
+ data[offset + i] = value[i];
1272
1181
 
1273
- for (y = _y0; y < _h; y++) {
1274
- for (x = _x0; x < _w; x++) {
1275
- const d_x = Math.round(x + destinationX);
1276
- const d_y = Math.round(y + destinationY);
1182
+ }
1183
+
1184
+ }
1185
+ }
1277
1186
 
1278
- this.read(d_x, d_y, c0);
1187
+ this.version++;
1188
+ }
1279
1189
 
1280
- const s_x = Math.round(x + sourceY);
1281
- const s_y = Math.round(y + sourceY);
1190
+ /**
1191
+ * Set channel value of a specific texel
1192
+ * @param {number} x
1193
+ * @param {number} y
1194
+ * @param {number} channel
1195
+ * @param {number} value
1196
+ */
1197
+ writeChannel(x, y, channel, value) {
1198
+ assert.isNumber(x, "x");
1199
+ assert.isNumber(y, "y");
1282
1200
 
1283
- source.read(s_x, s_y, c1);
1201
+ assert.greaterThanOrEqual(x, 0);
1202
+ assert.greaterThanOrEqual(y, 0);
1203
+ assert.lessThan(x, this.width);
1204
+ assert.lessThan(y, this.height);
1284
1205
 
1285
- blendFunction(c1, c0, c3);
1206
+ const pointIndex = y * this.width + x;
1207
+ const pointAddress = pointIndex * this.itemSize;
1208
+ const channelAddress = pointAddress + channel;
1286
1209
 
1287
- this.set(d_x, d_y, c3);
1210
+ this.data[channelAddress] = value;
1288
1211
 
1289
- }
1212
+ this.version++;
1290
1213
  }
1291
1214
 
1215
+ /**
1216
+ *
1217
+ * @param {number} x
1218
+ * @param {number} y
1219
+ * @param {number[]} value
1220
+ */
1221
+ set(x, y, value) {
1222
+ const data = this.data;
1223
+ const itemSize = this.itemSize;
1224
+
1225
+ const rowSize = itemSize * this.width;
1292
1226
 
1293
- };
1227
+ const offset = (rowSize * y) + x * itemSize;
1294
1228
 
1295
- /**
1296
- * Fill data values with zeros for a given area
1297
- * @param {Number} x
1298
- * @param {Number} y
1299
- * @param {Number} width
1300
- * @param {Number} height
1301
- */
1302
- Sampler2D.prototype.zeroFill = function (x, y, width, height) {
1229
+ for (let i = 0; i < itemSize; i++) {
1230
+ data[offset + i] = value[i];
1231
+ }
1303
1232
 
1304
- const x0 = clamp(x, 0, this.width);
1305
- const y0 = clamp(y, 0, this.height);
1306
- const x1 = clamp(x + width, 0, this.width);
1307
- const y1 = clamp(y + height, 0, this.height);
1233
+ this.version++;
1234
+ }
1308
1235
 
1309
- const data = this.data;
1310
- const itemSize = this.itemSize;
1236
+ /**
1237
+ * Traverses area inside a circle
1238
+ * NOTE: Based on palm3d answer on stack overflow: https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles
1239
+ * @param {number} centerX
1240
+ * @param {number} centerY
1241
+ * @param {number} radius
1242
+ * @param {function(x:number,y:number, sampler:Sampler2D)} visitor
1243
+ */
1244
+ traverseCircle(centerX, centerY, radius, visitor) {
1245
+ let x, y;
1311
1246
 
1312
- const rowSize = itemSize * this.width;
1247
+ //convert offsets to integers for safety
1248
+ const offsetX = centerX | 0;
1249
+ const offsetY = centerY | 0;
1313
1250
 
1314
- const clearRowOffset0 = x0 * itemSize;
1315
- const clearRowOffset1 = x1 * itemSize;
1251
+ const r2 = radius * radius;
1316
1252
 
1317
- let _y;
1253
+ const radiusCeil = Math.ceil(radius);
1318
1254
 
1319
- for (_y = y0; _y < y1; _y++) {
1255
+ for (y = -radiusCeil; y <= radiusCeil; y++) {
1256
+ const y2 = y * y;
1320
1257
 
1321
- const a = _y * rowSize;
1258
+ for (x = -radiusCeil; x <= radiusCeil; x++) {
1322
1259
 
1323
- data.fill(0, a + clearRowOffset0, a + clearRowOffset1);
1260
+ if (x * x + y2 <= r2) {
1261
+ visitor(offsetX + x, offsetY + y, this);
1262
+ }
1324
1263
 
1264
+ }
1265
+ }
1325
1266
  }
1326
1267
 
1327
- this.version++;
1328
- };
1268
+ /**
1269
+ *
1270
+ * @param {number} x
1271
+ * @param {number} y
1272
+ * @param {boolean} [preserveData=true]
1273
+ */
1274
+ resize(x, y, preserveData = true) {
1275
+ assert.isNonNegativeInteger(x, 'x');
1276
+ assert.isNonNegativeInteger(y, 'y');
1329
1277
 
1330
- /**
1331
- *
1332
- * @param {number} channel_index
1333
- * @param {number} value
1334
- */
1335
- Sampler2D.prototype.fill_channel = function (channel_index, value) {
1336
- const itemSize = this.itemSize;
1337
- const data = this.data;
1338
- const length = data.length;
1278
+ const _w = this.width;
1279
+ const _h = this.height;
1339
1280
 
1340
- for (let i = channel_index; i < length; i += itemSize) {
1341
- data[i] = value;
1342
- }
1281
+ if (_w === x && _h === y) {
1282
+ // size didn't change
1283
+ return;
1284
+ }
1343
1285
 
1344
- this.version++;
1345
- };
1286
+ const itemSize = this.itemSize;
1346
1287
 
1347
- /**
1348
- *
1349
- * @param {Number} x
1350
- * @param {Number} y
1351
- * @param {Number} width
1352
- * @param {Number} height
1353
- * @param {Array.<Number>} value
1354
- */
1355
- Sampler2D.prototype.fill = function (x, y, width, height, value) {
1288
+ const length = x * y * itemSize;
1356
1289
 
1357
- const _w = this.width;
1358
- const _h = this.height;
1290
+ const oldData = this.data;
1359
1291
 
1360
- const x0 = clamp(x, 0, _w);
1361
- const y0 = clamp(y, 0, _h);
1362
- const x1 = clamp(x + width, 0, _w);
1363
- const y1 = clamp(y + height, 0, _h);
1292
+ const Constructor = typedArrayConstructorByInstance(oldData);
1364
1293
 
1365
- const data = this.data;
1366
- const itemSize = this.itemSize;
1294
+ const newData = new Constructor(length);
1367
1295
 
1368
- const rowSize = itemSize * _w;
1296
+ if (preserveData) {
1297
+ //copy old data
1298
+ if (x === _w) {
1299
+ // number of columns is preserved, we can just copy the old data across
1300
+ newData.set(oldData.subarray(0, Math.min(oldData.length, length)));
1301
+ } else {
1302
+ //we need to copy new data row-by-row
1303
+ const rowCount = min2(y, _h);
1369
1304
 
1370
- let _y, _x, i;
1305
+ const columnCount = min2(x, _w);
1371
1306
 
1372
- for (_y = y0; _y < y1; _y++) {
1307
+ for (let i = 0; i < rowCount; i++) {
1308
+ for (let j = 0; j < columnCount; j++) {
1373
1309
 
1374
- const a = _y * rowSize;
1310
+ const targetItemAddress = (i * x + j) * itemSize;
1311
+ const sourceItemAddress = (i * _w + j) * itemSize;
1375
1312
 
1376
- for (_x = x0; _x < x1; _x++) {
1313
+ for (let k = 0; k < itemSize; k++) {
1377
1314
 
1378
- const offset = a + _x * itemSize;
1315
+ newData[targetItemAddress + k] = oldData[sourceItemAddress + k];
1379
1316
 
1380
- for (i = 0; i < itemSize; i++) {
1317
+ }
1318
+ }
1319
+ }
1320
+ }
1321
+ }
1381
1322
 
1382
- data[offset + i] = value[i];
1323
+ this.width = x;
1324
+ this.height = y;
1383
1325
 
1384
- }
1326
+ this.data = newData;
1385
1327
 
1386
- }
1328
+ this.version++;
1387
1329
  }
1388
1330
 
1389
- this.version++;
1390
- };
1331
+ /**
1332
+ * Estimate memory requirement of the object
1333
+ * @return {number}
1334
+ */
1335
+ computeByteSize() {
1336
+ let dataSize;
1391
1337
 
1392
- /**
1393
- * Set channel value of a specific texel
1394
- * @param {number} x
1395
- * @param {number} y
1396
- * @param {number} channel
1397
- * @param {number} value
1398
- */
1399
- Sampler2D.prototype.writeChannel = function (x, y, channel, value) {
1400
- assert.isNumber(x, "x");
1401
- assert.isNumber(y, "y");
1338
+ if (Array.isArray(this.data)) {
1339
+ dataSize = 8 * this.data.length;
1340
+ } else {
1341
+ dataSize = this.data.buffer.byteLength;
1342
+ }
1402
1343
 
1403
- assert.greaterThanOrEqual(x, 0);
1404
- assert.greaterThanOrEqual(y, 0);
1405
- assert.lessThan(x, this.width);
1406
- assert.lessThan(y, this.height);
1344
+ return dataSize + 280;
1345
+ }
1407
1346
 
1408
- const pointIndex = y * this.width + x;
1409
- const pointAddress = pointIndex * this.itemSize;
1410
- const channelAddress = pointAddress + channel;
1347
+ /**
1348
+ * @deprecated Use {@link Sampler2DSerializationAdapter} adapter instead
1349
+ * @param {BinaryBuffer} buffer
1350
+ */
1351
+ toBinaryBuffer(buffer) {
1352
+ const width = this.width;
1353
+ const height = this.height;
1411
1354
 
1412
- this.data[channelAddress] = value;
1355
+ const itemSize = this.itemSize;
1413
1356
 
1414
- this.version++;
1415
- };
1357
+ buffer.writeUint16(width);
1358
+ buffer.writeUint16(height);
1416
1359
 
1417
- /**
1418
- *
1419
- * @param {number} x
1420
- * @param {number} y
1421
- * @param {number[]} value
1422
- */
1423
- Sampler2D.prototype.set = function (x, y, value) {
1424
- const data = this.data;
1425
- const itemSize = this.itemSize;
1360
+ buffer.writeUint8(itemSize);
1426
1361
 
1427
- const rowSize = itemSize * this.width;
1362
+ if (this.data instanceof Uint8Array) {
1363
+ //data type
1364
+ buffer.writeUint8(0);
1428
1365
 
1429
- const offset = (rowSize * y) + x * itemSize;
1430
1366
 
1431
- for (let i = 0; i < itemSize; i++) {
1432
- data[offset + i] = value[i];
1433
- }
1367
+ const byteSize = width * height * itemSize;
1434
1368
 
1435
- this.version++;
1436
- };
1369
+ buffer.writeBytes(this.data, 0, byteSize);
1437
1370
 
1438
- /**
1439
- * Traverses area inside a circle
1440
- * NOTE: Based on palm3d answer on stack overflow: https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles
1441
- * @param {number} centerX
1442
- * @param {number} centerY
1443
- * @param {number} radius
1444
- * @param {function(x:number,y:number, sampler:Sampler2D)} visitor
1445
- */
1446
- Sampler2D.prototype.traverseCircle = function (centerX, centerY, radius, visitor) {
1447
- let x, y;
1371
+ } else {
1372
+ throw new TypeError(`Unsupported data type`);
1373
+ }
1374
+ }
1448
1375
 
1449
- //convert offsets to integers for safety
1450
- const offsetX = centerX | 0;
1451
- const offsetY = centerY | 0;
1376
+ /**
1377
+ * @deprecated Use {@link Sampler2DSerializationAdapter} adapter instead
1378
+ * @param {BinaryBuffer} buffer
1379
+ */
1380
+ fromBinaryBuffer(buffer) {
1381
+ this.width = buffer.readUint16();
1382
+ this.height = buffer.readUint16();
1452
1383
 
1453
- const r2 = radius * radius;
1384
+ this.itemSize = buffer.readUint8();
1454
1385
 
1455
- const radiusCeil = Math.ceil(radius);
1386
+ const dataType = buffer.readUint8();
1456
1387
 
1457
- for (y = -radiusCeil; y <= radiusCeil; y++) {
1458
- const y2 = y * y;
1388
+ if (dataType === 0) {
1459
1389
 
1460
- for (x = -radiusCeil; x <= radiusCeil; x++) {
1390
+ const numBytes = this.height * this.width * this.itemSize;
1391
+ this.data = new Uint8Array(numBytes);
1461
1392
 
1462
- if (x * x + y2 <= r2) {
1463
- visitor(offsetX + x, offsetY + y, this);
1464
- }
1393
+ buffer.readBytes(this.data, 0, numBytes);
1465
1394
 
1395
+ this.version++;
1396
+ } else {
1397
+ throw new TypeError(`Unsupported data type (${dataType})`);
1466
1398
  }
1467
1399
  }
1468
- };
1469
1400
 
1470
- /**
1471
- *
1472
- * @param {number} x
1473
- * @param {number} y
1474
- * @param {boolean} [preserveData=true]
1475
- */
1476
- Sampler2D.prototype.resize = function (x, y, preserveData = true) {
1477
- assert.isNonNegativeInteger(x, 'x');
1478
- assert.isNonNegativeInteger(y, 'y');
1401
+ /**
1402
+ *
1403
+ * @param {number} x
1404
+ * @param {number} y
1405
+ * @param {function(x:number, y:number, value:number, index:number):boolean?} visitor
1406
+ * @param {*} [thisArg]
1407
+ */
1408
+ traverseOrthogonalNeighbours(x, y, visitor, thisArg) {
1409
+ const width = this.width;
1410
+ const height = this.height;
1479
1411
 
1480
- const _w = this.width;
1481
- const _h = this.height;
1412
+ const index = this.point2index(x, y);
1482
1413
 
1483
- if (_w === x && _h === y) {
1484
- // size didn't change
1485
- return;
1414
+ let i = 0;
1415
+ const data = this.data;
1416
+ if (x > 0) {
1417
+ i = index - 1;
1418
+ visitor.call(thisArg, x - 1, y, data[i], i);
1419
+ }
1420
+ if (x < width - 1) {
1421
+ i = index + 1;
1422
+ visitor.call(thisArg, x + 1, y, data[i], i);
1423
+ }
1424
+ if (y > 0) {
1425
+ i = index - width;
1426
+ visitor.call(thisArg, x, y - 1, data[i], i);
1427
+ }
1428
+ if (y < height - 1) {
1429
+ i = index + width;
1430
+ visitor.call(thisArg, x, y + 1, data[i], i);
1431
+ }
1486
1432
  }
1487
1433
 
1488
- const itemSize = this.itemSize;
1489
-
1490
- const length = x * y * itemSize;
1434
+ /**
1435
+ * @returns {Sampler2D}
1436
+ */
1437
+ clone() {
1438
+ let data_clone;
1491
1439
 
1492
- const oldData = this.data;
1440
+ if (Array.isArray(this.data)) {
1441
+ data_clone = this.data.slice();
1442
+ } else {
1443
+ // storage is a typed array
1444
+ const T = this.data.constructor;
1493
1445
 
1494
- const Constructor = typedArrayConstructorByInstance(oldData);
1446
+ data_clone = new T(this.data);
1447
+ }
1495
1448
 
1496
- const newData = new Constructor(length);
1449
+ return new Sampler2D(data_clone, this.itemSize, this.width, this.height);
1450
+ }
1497
1451
 
1498
- if (preserveData) {
1499
- //copy old data
1500
- if (x === _w) {
1501
- // number of columns is preserved, we can just copy the old data across
1502
- newData.set(oldData.subarray(0, Math.min(oldData.length, length)));
1503
- } else {
1504
- //we need to copy new data row-by-row
1505
- const rowCount = min2(y, _h);
1452
+ toJSON() {
1453
+ return {
1454
+ height: this.height,
1455
+ width: this.width,
1456
+ itemSize: this.itemSize,
1457
+ type: typedArrayToDataType(this.data),
1458
+ data: Array.from(this.data)
1459
+ }
1460
+ }
1506
1461
 
1507
- const columnCount = min2(x, _w);
1462
+ fromJSON({ height, width, itemSize, type, data }) {
1463
+ const CTOR = compute_typed_array_constructor_from_data_type(type);
1508
1464
 
1509
- for (let i = 0; i < rowCount; i++) {
1510
- for (let j = 0; j < columnCount; j++) {
1465
+ this.data = new CTOR(data);
1466
+ this.height = height;
1467
+ this.width = width;
1468
+ this.itemSize = itemSize;
1469
+ }
1511
1470
 
1512
- const targetItemAddress = (i * x + j) * itemSize;
1513
- const sourceItemAddress = (i * _w + j) * itemSize;
1471
+ /**
1472
+ *
1473
+ * @param {int} itemSize
1474
+ * @param {int} width
1475
+ * @param {int} height
1476
+ * @return {Sampler2D}
1477
+ */
1478
+ static uint8clamped(itemSize, width, height) {
1479
+ const data = new Uint8ClampedArray(width * height * itemSize);
1480
+ const sampler = new Sampler2D(data, itemSize, width, height);
1481
+ return sampler;
1482
+ }
1514
1483
 
1515
- for (let k = 0; k < itemSize; k++) {
1484
+ /**
1485
+ *
1486
+ * @param {int} itemSize
1487
+ * @param {int} width
1488
+ * @param {int} height
1489
+ * @return {Sampler2D}
1490
+ */
1491
+ static uint8(itemSize, width, height) {
1492
+ const data = new Uint8Array(width * height * itemSize);
1493
+ const sampler = new Sampler2D(data, itemSize, width, height);
1494
+ return sampler;
1495
+ }
1516
1496
 
1517
- newData[targetItemAddress + k] = oldData[sourceItemAddress + k];
1497
+ /**
1498
+ *
1499
+ * @param {int} itemSize
1500
+ * @param {int} width
1501
+ * @param {int} height
1502
+ * @return {Sampler2D}
1503
+ */
1504
+ static uint16(itemSize, width, height) {
1505
+ const data = new Uint16Array(width * height * itemSize);
1506
+ const sampler = new Sampler2D(data, itemSize, width, height);
1507
+ return sampler;
1508
+ }
1518
1509
 
1519
- }
1520
- }
1521
- }
1522
- }
1510
+ /**
1511
+ *
1512
+ * @param {int} itemSize
1513
+ * @param {int} width
1514
+ * @param {int} height
1515
+ * @return {Sampler2D}
1516
+ */
1517
+ static uint32(itemSize, width, height) {
1518
+ const data = new Uint32Array(width * height * itemSize);
1519
+ const sampler = new Sampler2D(data, itemSize, width, height);
1520
+ return sampler;
1523
1521
  }
1524
1522
 
1525
- this.width = x;
1526
- this.height = y;
1523
+ /**
1524
+ *
1525
+ * @param {int} itemSize
1526
+ * @param {int} width
1527
+ * @param {int} height
1528
+ * @return {Sampler2D}
1529
+ */
1530
+ static int8(itemSize, width, height) {
1531
+ const data = new Int8Array(width * height * itemSize);
1532
+ const sampler = new Sampler2D(data, itemSize, width, height);
1533
+ return sampler;
1534
+ }
1527
1535
 
1528
- this.data = newData;
1536
+ /**
1537
+ *
1538
+ * @param {int} itemSize
1539
+ * @param {int} width
1540
+ * @param {int} height
1541
+ * @return {Sampler2D}
1542
+ */
1543
+ static int16(itemSize, width, height) {
1544
+ const data = new Int16Array(width * height * itemSize);
1545
+ const sampler = new Sampler2D(data, itemSize, width, height);
1546
+ return sampler;
1547
+ }
1529
1548
 
1530
- this.version++;
1531
- };
1549
+ /**
1550
+ *
1551
+ * @param {int} itemSize
1552
+ * @param {int} width
1553
+ * @param {int} height
1554
+ * @return {Sampler2D}
1555
+ */
1556
+ static int32(itemSize, width, height) {
1557
+ const data = new Int32Array(width * height * itemSize);
1558
+ const sampler = new Sampler2D(data, itemSize, width, height);
1559
+ return sampler;
1560
+ }
1532
1561
 
1533
- /**
1534
- * Estimate memory requirement of the object
1535
- * @return {number}
1536
- */
1537
- Sampler2D.prototype.computeByteSize = function () {
1538
- let dataSize;
1562
+ /**
1563
+ *
1564
+ * @param {int} itemSize
1565
+ * @param {int} width
1566
+ * @param {int} height
1567
+ * @return {Sampler2D}
1568
+ */
1569
+ static float32(itemSize, width, height) {
1570
+ const data = new Float32Array(width * height * itemSize);
1571
+ const sampler = new Sampler2D(data, itemSize, width, height);
1572
+ return sampler;
1573
+ }
1539
1574
 
1540
- if (Array.isArray(this.data)) {
1541
- dataSize = 8 * this.data.length;
1542
- } else {
1543
- dataSize = this.data.buffer.byteLength;
1575
+ /**
1576
+ *
1577
+ * @param {int} itemSize
1578
+ * @param {int} width
1579
+ * @param {int} height
1580
+ * @return {Sampler2D}
1581
+ */
1582
+ static float64(itemSize, width, height) {
1583
+ const data = new Float64Array(width * height * itemSize);
1584
+ const sampler = new Sampler2D(data, itemSize, width, height);
1585
+ return sampler;
1544
1586
  }
1545
1587
 
1546
- return dataSize + 280;
1547
- };
1588
+ /**
1589
+ *
1590
+ * @param {Sampler2D} input0
1591
+ * @param {Sampler2D} input1
1592
+ * @param {Sampler2D} result
1593
+ * @param {function( value0 : number[], value1 : number[], result : number[], index : number) : void} operation
1594
+ */
1595
+ static combine(input0, input1, result, operation) {
1596
+ assert.notEqual(input0, undefined, "input0 is undefined");
1597
+ assert.notEqual(input1, undefined, "input1 is undefined");
1598
+ assert.notEqual(result, undefined, "result is undefined");
1548
1599
 
1600
+ assert.typeOf(operation, "function", "operation");
1549
1601
 
1550
- /**
1551
- * @deprecated Use {@link Sampler2DSerializationAdapter} adapter instead
1552
- * @param {BinaryBuffer} buffer
1553
- */
1554
- Sampler2D.prototype.toBinaryBuffer = function (buffer) {
1555
- const width = this.width;
1556
- const height = this.height;
1602
+ assert.equal(input0.width, input1.width, `input0.width(=${input0.width}) is not equal to input1.width(=${input1.width})`);
1603
+ assert.equal(input0.height, input1.height, `input0.height(=${input0.height}) is not equal to input1.height(=${input1.height})`);
1557
1604
 
1558
- const itemSize = this.itemSize;
1605
+ assert.equal(input0.width, result.width, `input width(=${input0.width}) is not equal to result.width(=${result.width})`);
1606
+ assert.equal(input0.height, result.height, `input height(=${input0.height}) is not equal to result.height(=${result.height})`);
1559
1607
 
1560
- buffer.writeUint16(width);
1561
- buffer.writeUint16(height);
1608
+ const width = input0.width;
1609
+ const height = input0.height;
1562
1610
 
1563
- buffer.writeUint8(itemSize);
1611
+ const length = width * height;
1564
1612
 
1565
- if (this.data instanceof Uint8Array) {
1566
- //data type
1567
- buffer.writeUint8(0);
1613
+ const arg0 = [];
1614
+ const arg1 = [];
1615
+ const res = [];
1568
1616
 
1617
+ const itemSize0 = input0.itemSize;
1618
+ const itemSize1 = input1.itemSize;
1619
+ const itemSizeR = result.itemSize;
1569
1620
 
1570
- const byteSize = width * height * itemSize;
1621
+ const data0 = input0.data;
1622
+ const data1 = input1.data;
1623
+ const dataR = result.data;
1571
1624
 
1572
- buffer.writeBytes(this.data, 0, byteSize);
1573
1625
 
1574
- } else {
1575
- throw new TypeError(`Unsupported data type`);
1576
- }
1577
- };
1626
+ let i, j;
1578
1627
 
1579
- /**
1580
- * @deprecated Use {@link Sampler2DSerializationAdapter} adapter instead
1581
- * @param {BinaryBuffer} buffer
1582
- */
1583
- Sampler2D.prototype.fromBinaryBuffer = function (buffer) {
1584
- this.width = buffer.readUint16();
1585
- this.height = buffer.readUint16();
1628
+ for (i = 0; i < length; i++) {
1586
1629
 
1587
- this.itemSize = buffer.readUint8();
1630
+ // read input 0
1631
+ for (j = 0; j < itemSize0; j++) {
1632
+ arg0[j] = data0[j + i * itemSize0];
1633
+ }
1588
1634
 
1589
- const dataType = buffer.readUint8();
1635
+ // read input 1
1636
+ for (j = 0; j < itemSize0; j++) {
1637
+ arg1[j] = data1[j + i * itemSize1];
1638
+ }
1590
1639
 
1591
- if (dataType === 0) {
1640
+ //perform operation
1641
+ operation(arg0, arg1, res, i);
1592
1642
 
1593
- const numBytes = this.height * this.width * this.itemSize;
1594
- this.data = new Uint8Array(numBytes);
1643
+ //write result
1644
+ for (j = 0; j < itemSizeR; j++) {
1645
+ dataR[j + i * itemSizeR] = res[j];
1646
+ }
1595
1647
 
1596
- buffer.readBytes(this.data, 0, numBytes);
1648
+ }
1597
1649
 
1598
- this.version++;
1599
- } else {
1600
- throw new TypeError(`Unsupported data type (${dataType})`);
1650
+ result.version++;
1601
1651
  }
1602
- };
1652
+ }
1653
+
1603
1654
 
1604
1655
  /**
1605
- *
1606
- * @param {number} x
1607
- * @param {number} y
1608
- * @param {function(x:number, y:number, value:number, index:number):boolean?} visitor
1609
- * @param {*} [thisArg]
1656
+ * Based on code from reddit https://www.reddit.com/r/javascript/comments/jxa8x/bicubic_interpolation/
1657
+ * @param {number} t
1658
+ * @param {number} a
1659
+ * @param {number} b
1660
+ * @param {number} c
1661
+ * @param {number} d
1662
+ * @returns {number}
1610
1663
  */
1611
- Sampler2D.prototype.traverseOrthogonalNeighbours = function (x, y, visitor, thisArg) {
1612
- const width = this.width;
1613
- const height = this.height;
1614
-
1615
- const index = this.point2index(x, y);
1664
+ function bicubic_terp(t, a, b, c, d) {
1665
+ return 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * t) * t) * t + b;
1666
+ }
1616
1667
 
1617
- let i = 0;
1618
- const data = this.data;
1619
- if (x > 0) {
1620
- i = index - 1;
1621
- visitor.call(thisArg, x - 1, y, data[i], i);
1622
- }
1623
- if (x < width - 1) {
1624
- i = index + 1;
1625
- visitor.call(thisArg, x + 1, y, data[i], i);
1626
- }
1627
- if (y > 0) {
1628
- i = index - width;
1629
- visitor.call(thisArg, x, y - 1, data[i], i);
1630
- }
1631
- if (y < height - 1) {
1632
- i = index + width;
1633
- visitor.call(thisArg, x, y + 1, data[i], i);
1634
- }
1635
- };
1636
1668
 
1637
1669
  /**
1638
- * @returns {Sampler2D}
1670
+ *
1671
+ * @param {number[]} source
1672
+ * @param {number[]} destination
1673
+ * @param {Array} result
1639
1674
  */
1640
- Sampler2D.prototype.clone = function () {
1641
- let data_clone;
1642
-
1643
- if (Array.isArray(this.data)) {
1644
- data_clone = this.data.slice();
1645
- } else {
1646
- // storage is a typed array
1647
- const T = this.data.constructor;
1675
+ function blendFunctionNormal(source, destination, result) {
1648
1676
 
1649
- data_clone = new T(this.data);
1650
- }
1677
+ const a1 = source[3] / 255;
1678
+ const a0 = destination[3] / 255;
1651
1679
 
1652
- return new Sampler2D(data_clone, this.itemSize, this.width, this.height);
1653
- };
1680
+ result[0] = source[0] * a1 + destination[0] * (1 - a1);
1681
+ result[1] = source[1] * a1 + destination[1] * (1 - a1);
1682
+ result[2] = source[2] * a1 + destination[2] * (1 - a1);
1683
+ result[3] = (a1 + a0 * (1 - a1)) * 255;
1684
+ }
1654
1685
 
1655
- /**
1656
- * @readonly
1657
- * @type {boolean}
1658
- */
1659
- Sampler2D.prototype.isSampler2D = true;
1660
1686