@litecanvas/utils 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,7 @@ Small collection of tools for developing games with [Litecanvas](https://github.
6
6
 
7
7
  - **Camera**: Move-, zoom- and rotatable camera with shake. [Usage & Docs](https://github.com/litecanvas/utils/tree/main/src/camera)
8
8
  - **Vector**: Modular 2D vector. [Usage & Docs](https://github.com/litecanvas/utils/tree/main/src/vector)
9
+ - **Grid** class. [Usage & Docs](https://github.com/litecanvas/utils/tree/main/src/grid)
9
10
  - **Collision** utilities. [Usage & Docs](https://github.com/litecanvas/utils/tree/main/src/collision)
10
11
  - And [some math utilities](https://github.com/litecanvas/utils/tree/main/src/math)
11
12
 
package/dist/all.js CHANGED
@@ -10,6 +10,7 @@
10
10
  __export(src_exports, {
11
11
  Camera: () => Camera,
12
12
  DOWN: () => DOWN,
13
+ Grid: () => Grid,
13
14
  LEFT: () => LEFT,
14
15
  ONE: () => ONE,
15
16
  RIGHT: () => RIGHT,
@@ -198,6 +199,204 @@
198
199
  }
199
200
  };
200
201
 
202
+ // src/grid/index.js
203
+ var Grid = class _Grid {
204
+ /** @type {number} The grid width */
205
+ _w;
206
+ /** @type {number} The grid height */
207
+ _h;
208
+ /** @type {any[]} The grid cells */
209
+ _c;
210
+ /**
211
+ * @static
212
+ * @param {number} width
213
+ * @param {number} height
214
+ * @param {any[]} values
215
+ */
216
+ static fromArray(width, height, values) {
217
+ const grid = new _Grid(width, height);
218
+ for (let i = 0; i < values.length; i++) {
219
+ grid._c[i] = values[i];
220
+ }
221
+ return grid;
222
+ }
223
+ /**
224
+ * @param {number} width The grid width
225
+ * @param {number} height The grid height
226
+ */
227
+ constructor(width, height) {
228
+ this.width = width;
229
+ this.height = height;
230
+ this.clear();
231
+ }
232
+ /**
233
+ * Delete all cell values.
234
+ */
235
+ clear() {
236
+ this._c = Array(this._w * this._h);
237
+ }
238
+ /**
239
+ * @param {number} value
240
+ */
241
+ set width(value) {
242
+ this._w = Math.max(1, ~~value);
243
+ }
244
+ get width() {
245
+ return this._w;
246
+ }
247
+ /**
248
+ * @param {number} value
249
+ */
250
+ set height(value) {
251
+ this._h = Math.max(1, ~~value);
252
+ }
253
+ get height() {
254
+ return this._h;
255
+ }
256
+ /**
257
+ * The the value of a grid's cell.
258
+ *
259
+ * @param {number} x
260
+ * @param {number} y
261
+ * @param {any} value
262
+ */
263
+ set(x, y, value) {
264
+ this._c[this.pointToIndex(x, y)] = value;
265
+ }
266
+ /**
267
+ * Returns the value of a grid's cell.
268
+ *
269
+ * @param {number} x
270
+ * @param {number} y
271
+ * @returns {any}
272
+ */
273
+ get(x, y) {
274
+ return this._c[this.pointToIndex(x, y)];
275
+ }
276
+ /**
277
+ * Returns true if the which cell has any value not equal to `null` or `undefined`.
278
+ *
279
+ * @param {number} x
280
+ * @param {number} y
281
+ * @returns {boolean}
282
+ */
283
+ has(x, y) {
284
+ return this.get(x, y) != null;
285
+ }
286
+ /**
287
+ * Returns the total of cells.
288
+ *
289
+ * @returns {number}
290
+ */
291
+ get length() {
292
+ return this._w * this._h;
293
+ }
294
+ /**
295
+ * Convert a grid point (X, Y) to a index.
296
+ *
297
+ * @param {number} x
298
+ * @param {number} y
299
+ * @returns {number} The index
300
+ */
301
+ pointToIndex(x, y) {
302
+ return this.clampX(~~x) + this.clampY(~~y) * this._w;
303
+ }
304
+ /**
305
+ * Convert index to a grid point X.
306
+ *
307
+ * @param {number} index
308
+ * @returns {number}
309
+ */
310
+ indexToPointX(index) {
311
+ return index % this._w;
312
+ }
313
+ /**
314
+ * Convert index to a grid point Y.
315
+ *
316
+ * @param {number} index
317
+ * @returns {number}
318
+ */
319
+ indexToPointY(index) {
320
+ return Math.floor(index / this._w);
321
+ }
322
+ /**
323
+ * Loops over all grid cells.
324
+ *
325
+ * @callback GridForEachCallback
326
+ * @param {number} x
327
+ * @param {number} y
328
+ * @param {any} value
329
+ * @param {Grid} grid
330
+ * @returns {boolean?} returns `false` to stop/break the loop
331
+ *
332
+ * @param {GridForEachCallback} handler
333
+ * @param {boolean} [reverse=false]
334
+ */
335
+ forEach(handler, reverse = false) {
336
+ let i = reverse ? this.length - 1 : 0, limit = reverse ? -1 : this.length, step = reverse ? -1 : 1;
337
+ while (i !== limit) {
338
+ const x = this.indexToPointX(i), y = this.indexToPointY(i), cellValue = this._c[i];
339
+ if (false === handler(x, y, cellValue, this)) break;
340
+ i += step;
341
+ }
342
+ }
343
+ /**
344
+ * @param {*} value
345
+ */
346
+ fill(value) {
347
+ this.forEach((x, y) => {
348
+ this.set(x, y, value);
349
+ });
350
+ }
351
+ /**
352
+ * @returns {Grid} the cloned grid
353
+ */
354
+ clone() {
355
+ return _Grid.fromArray(this._w, this._h, this._c);
356
+ }
357
+ /**
358
+ * @param {number} y
359
+ * @returns {number}
360
+ */
361
+ clampX(x) {
362
+ return _clamp(x, 0, this._w - 1);
363
+ }
364
+ /**
365
+ * @param {number} y
366
+ * @returns {number}
367
+ */
368
+ clampY(y) {
369
+ return _clamp(y, 0, this._h - 1);
370
+ }
371
+ /**
372
+ * Returns the cell values in a single array.
373
+ *
374
+ * @returns {any[]}
375
+ */
376
+ toArray() {
377
+ return this._c.slice(0);
378
+ }
379
+ /**
380
+ * @param {string} separator
381
+ * @param {boolean} format
382
+ * @returns {string}
383
+ */
384
+ toString(separator = " ", format = true) {
385
+ if (!format) return this._c.join(separator);
386
+ const rows = [];
387
+ this.forEach((x, y, value) => {
388
+ rows[y] = rows[y] || "";
389
+ rows[y] += value + separator;
390
+ });
391
+ return rows.join("\n");
392
+ }
393
+ };
394
+ function _clamp(value, min, max) {
395
+ if (value < min) return min;
396
+ if (value > max) return max;
397
+ return value;
398
+ }
399
+
201
400
  // src/vector/index.js
202
401
  var Vector = class {
203
402
  /** @type {number} */
@@ -224,7 +423,7 @@
224
423
  if (isvector(x)) {
225
424
  return veceq(v, x.x, x.y);
226
425
  }
227
- return v.x === x && v.y === (y || x);
426
+ return v.x === x && v.y === y;
228
427
  };
229
428
  var veccopy = (v) => vec(v.x, v.y);
230
429
  var vecset = (v, x, y = x) => {
package/dist/all.min.js CHANGED
@@ -1 +1,2 @@
1
- (()=>{var N=Object.defineProperty;var P=(e,t)=>{for(var s in t)N(e,s,{get:t[s],enumerable:!0})};var T={};P(T,{Camera:()=>a,DOWN:()=>K,LEFT:()=>Q,ONE:()=>B,RIGHT:()=>J,UP:()=>C,Vector:()=>l,ZERO:()=>j,diff:()=>L,fract:()=>q,intersection:()=>y,isvector:()=>h,resolve:()=>z,vec:()=>i,vecadd:()=>I,vecconfig:()=>k,veccopy:()=>R,veccross:()=>S,vecdir:()=>A,vecdist:()=>$,vecdist2:()=>b,vecdiv:()=>d,vecdot:()=>F,veceq:()=>E,veclerp:()=>U,veclimit:()=>Y,vecmag:()=>D,vecmag2:()=>G,vecmult:()=>M,vecnorm:()=>X,vecrand:()=>Z,vecrot:()=>W,vecset:()=>H,vecsub:()=>O,wave:()=>v});var L=(e,t)=>Math.abs(t-e)||0;var q=e=>e%1||0;var v=(e,t,s,o=Math.sin)=>e+(o(s)+1)/2*(t-e);var y=(e,t,s,o,r,n,x,p)=>{let u=Math.max(e,r),w=Math.min(e+s,r+x)-u,f=Math.max(t,n),g=Math.min(t+o,n+p)-f;return[u,f,w,g]};var z=(e,t,s,o,r,n,x,p)=>{let[u,w,f,g]=y(e,t,s,o,r,n,x,p),c="",m=e,_=t;return f<g?e<r?(c="right",m=r-s):(c="left",m=r+x):t<n?(c="bottom",_=n-o):(c="top",_=n+p),{direction:c,x:m,y:_}};var a=class{_engine=null;x=0;y=0;width=0;height=0;rotation=0;scale=1;_shake={x:0,y:0,removeListener:null};constructor(t=null){t=t||globalThis,this._engine=t,this.size(t.WIDTH||0,t.HEIGHT||0),this.x=this.width/2,this.y=this.height/2}size(t,s){this.width=t,this.height=s}start(t=!1){let s=this.width/2,o=this.height/2;this._engine.push(),this._engine.translate(s,o),this._engine.scale(this.scale),this._engine.rotate(this.rotation),this._engine.translate(-this.x+this._shake.x,-this.y+this._shake.y),t&&this._engine.cliprect(this.x,this.y,this.width,this.height)}end(){this._engine.pop()}lookAt(t,s){this.x=t,this.y=s}move(t,s){this.x+=t,this.y+=s}zoom(t){this.scale*=t}zoomTo(t){this.scale=t}rotate(t){this.rotation+=t}rotateTo(t){this.rotation=t}shake(t=.3,s=1){this.shaking()||(this._shake.removeListener=this._engine.listen("update",o=>{this._shake.x=this._engine.randi(-s,s),this._shake.y=this._engine.randi(-s,s),t-=o,t<=0&&this.unshake()}))}unshake(){this.shaking()&&(this._shake.removeListener(),this._shake.removeListener=null,this._shake.x=this._shake.y=0)}shaking(){return this._shake.removeListener!==null}};var l=class{x;y;constructor(t=0,s=t){this.x=t,this.y=s}toString(){return`Vector (${this.x}, ${this.y})`}},i=(e=0,t=e)=>new l(e,t),E=(e,t,s=t)=>h(t)?E(e,t.x,t.y):e.x===t&&e.y===(s||t),R=e=>i(e.x,e.y),H=(e,t,s=t)=>{h(t)?H(e,t.x,t.y):(e.x=t,e.y=s)},I=(e,t,s=t)=>{h(t)?I(e,t.x,t.y):(e.x+=t,e.y+=s)},O=(e,t,s=t)=>{h(t)?O(e,t.x,t.y):(e.x-=t,e.y-=s)},M=(e,t,s=t)=>{h(t)?M(e,t.x,t.y):(e.x*=t,e.y*=s)},d=(e,t,s=t)=>{h(t)?d(e,t.x,t.y):(e.x/=t,e.y/=s)},W=(e,t)=>{let s=Math.cos(t),o=Math.sin(t);e.x=s*e.x-o*e.y,e.y=o*e.x+s*e.y},D=e=>Math.sqrt(e.x*e.x+e.y*e.y),G=e=>e.x*e.x+e.y*e.y,X=e=>{let t=D(e);t>0&&d(e,t)},Y=(e,t)=>{let s=G(e);s>t*t&&(d(e,Math.sqrt(s)),M(e,t))},$=(e,t)=>{let s=e.x-t.x,o=e.y-t.y;return Math.sqrt(s*s+o*o)},b=(e,t)=>{let s=e.x-t.x,o=e.y-t.y;return s*s+o*o},A=e=>Math.atan2(e.y,e.x),F=(e,t)=>e.x*t.x+e.y*t.y,S=(e,t)=>e.x*t.y-e.y*t.x,U=(e,t,s)=>{e.x+=(t.x-e.x)*s||0,e.y+=(t.y-e.y)*s||0},Z=(e=1,t=e)=>{let s=k.random()*2*Math.PI,o=k.random()*(t-e)+e;return i(Math.cos(s)*o,Math.sin(s)*o)},h=e=>e instanceof l,k={random:()=>globalThis.rand?rand():Math.random()},j=i(0,0),B=i(1,1),C=i(0,-1),J=i(1,0),K=i(0,1),Q=i(-1,0);globalThis.utils=T;})();
1
+ (()=>{var j=Object.defineProperty;var D=(e,t)=>{for(var s in t)j(e,s,{get:t[s],enumerable:!0})};var T={};D(T,{Camera:()=>f,DOWN:()=>Q,Grid:()=>p,LEFT:()=>tt,ONE:()=>C,RIGHT:()=>K,UP:()=>J,Vector:()=>u,ZERO:()=>B,diff:()=>I,fract:()=>L,intersection:()=>_,isvector:()=>c,resolve:()=>X,vec:()=>h,vecadd:()=>z,vecconfig:()=>k,veccopy:()=>G,veccross:()=>V,vecdir:()=>F,vecdist:()=>W,vecdist2:()=>$,vecdiv:()=>y,vecdot:()=>U,veceq:()=>q,veclerp:()=>Z,veclimit:()=>S,vecmag:()=>O,vecmag2:()=>b,vecmult:()=>M,vecnorm:()=>R,vecrand:()=>v,vecrot:()=>N,vecset:()=>A,vecsub:()=>H,wave:()=>P});var I=(e,t)=>Math.abs(t-e)||0;var L=e=>e%1||0;var P=(e,t,s,i=Math.sin)=>e+(i(s)+1)/2*(t-e);var _=(e,t,s,i,r,o,n,a)=>{let l=Math.max(e,r),E=Math.min(e+s,r+n)-l,d=Math.max(t,o),g=Math.min(t+i,o+a)-d;return[l,d,E,g]};var X=(e,t,s,i,r,o,n,a)=>{let[l,E,d,g]=_(e,t,s,i,r,o,n,a),x="",m=e,w=t;return d<g?e<r?(x="right",m=r-s):(x="left",m=r+n):t<o?(x="bottom",w=o-i):(x="top",w=o+a),{direction:x,x:m,y:w}};var f=class{_engine=null;x=0;y=0;width=0;height=0;rotation=0;scale=1;_shake={x:0,y:0,removeListener:null};constructor(t=null){t=t||globalThis,this._engine=t,this.size(t.WIDTH||0,t.HEIGHT||0),this.x=this.width/2,this.y=this.height/2}size(t,s){this.width=t,this.height=s}start(t=!1){let s=this.width/2,i=this.height/2;this._engine.push(),this._engine.translate(s,i),this._engine.scale(this.scale),this._engine.rotate(this.rotation),this._engine.translate(-this.x+this._shake.x,-this.y+this._shake.y),t&&this._engine.cliprect(this.x,this.y,this.width,this.height)}end(){this._engine.pop()}lookAt(t,s){this.x=t,this.y=s}move(t,s){this.x+=t,this.y+=s}zoom(t){this.scale*=t}zoomTo(t){this.scale=t}rotate(t){this.rotation+=t}rotateTo(t){this.rotation=t}shake(t=.3,s=1){this.shaking()||(this._shake.removeListener=this._engine.listen("update",i=>{this._shake.x=this._engine.randi(-s,s),this._shake.y=this._engine.randi(-s,s),t-=i,t<=0&&this.unshake()}))}unshake(){this.shaking()&&(this._shake.removeListener(),this._shake.removeListener=null,this._shake.x=this._shake.y=0)}shaking(){return this._shake.removeListener!==null}};var p=class e{_w;_h;_c;static fromArray(t,s,i){let r=new e(t,s);for(let o=0;o<i.length;o++)r._c[o]=i[o];return r}constructor(t,s){this.width=t,this.height=s,this.clear()}clear(){this._c=Array(this._w*this._h)}set width(t){this._w=Math.max(1,~~t)}get width(){return this._w}set height(t){this._h=Math.max(1,~~t)}get height(){return this._h}set(t,s,i){this._c[this.pointToIndex(t,s)]=i}get(t,s){return this._c[this.pointToIndex(t,s)]}has(t,s){return this.get(t,s)!=null}get length(){return this._w*this._h}pointToIndex(t,s){return this.clampX(~~t)+this.clampY(~~s)*this._w}indexToPointX(t){return t%this._w}indexToPointY(t){return Math.floor(t/this._w)}forEach(t,s=!1){let i=s?this.length-1:0,r=s?-1:this.length,o=s?-1:1;for(;i!==r;){let n=this.indexToPointX(i),a=this.indexToPointY(i),l=this._c[i];if(t(n,a,l,this)===!1)break;i+=o}}fill(t){this.forEach((s,i)=>{this.set(s,i,t)})}clone(){return e.fromArray(this._w,this._h,this._c)}clampX(t){return Y(t,0,this._w-1)}clampY(t){return Y(t,0,this._h-1)}toArray(){return this._c.slice(0)}toString(t=" ",s=!0){if(!s)return this._c.join(t);let i=[];return this.forEach((r,o,n)=>{i[o]=i[o]||"",i[o]+=n+t}),i.join(`
2
+ `)}};function Y(e,t,s){return e<t?t:e>s?s:e}var u=class{x;y;constructor(t=0,s=t){this.x=t,this.y=s}toString(){return`Vector (${this.x}, ${this.y})`}},h=(e=0,t=e)=>new u(e,t),q=(e,t,s=t)=>c(t)?q(e,t.x,t.y):e.x===t&&e.y===s,G=e=>h(e.x,e.y),A=(e,t,s=t)=>{c(t)?A(e,t.x,t.y):(e.x=t,e.y=s)},z=(e,t,s=t)=>{c(t)?z(e,t.x,t.y):(e.x+=t,e.y+=s)},H=(e,t,s=t)=>{c(t)?H(e,t.x,t.y):(e.x-=t,e.y-=s)},M=(e,t,s=t)=>{c(t)?M(e,t.x,t.y):(e.x*=t,e.y*=s)},y=(e,t,s=t)=>{c(t)?y(e,t.x,t.y):(e.x/=t,e.y/=s)},N=(e,t)=>{let s=Math.cos(t),i=Math.sin(t);e.x=s*e.x-i*e.y,e.y=i*e.x+s*e.y},O=e=>Math.sqrt(e.x*e.x+e.y*e.y),b=e=>e.x*e.x+e.y*e.y,R=e=>{let t=O(e);t>0&&y(e,t)},S=(e,t)=>{let s=b(e);s>t*t&&(y(e,Math.sqrt(s)),M(e,t))},W=(e,t)=>{let s=e.x-t.x,i=e.y-t.y;return Math.sqrt(s*s+i*i)},$=(e,t)=>{let s=e.x-t.x,i=e.y-t.y;return s*s+i*i},F=e=>Math.atan2(e.y,e.x),U=(e,t)=>e.x*t.x+e.y*t.y,V=(e,t)=>e.x*t.y-e.y*t.x,Z=(e,t,s)=>{e.x+=(t.x-e.x)*s||0,e.y+=(t.y-e.y)*s||0},v=(e=1,t=e)=>{let s=k.random()*2*Math.PI,i=k.random()*(t-e)+e;return h(Math.cos(s)*i,Math.sin(s)*i)},c=e=>e instanceof u,k={random:()=>globalThis.rand?rand():Math.random()},B=h(0,0),C=h(1,1),J=h(0,-1),K=h(1,0),Q=h(0,1),tt=h(-1,0);globalThis.utils=T;})();
package/dist/grid.js ADDED
@@ -0,0 +1,202 @@
1
+ (() => {
2
+ // src/grid/index.js
3
+ var Grid = class _Grid {
4
+ /** @type {number} The grid width */
5
+ _w;
6
+ /** @type {number} The grid height */
7
+ _h;
8
+ /** @type {any[]} The grid cells */
9
+ _c;
10
+ /**
11
+ * @static
12
+ * @param {number} width
13
+ * @param {number} height
14
+ * @param {any[]} values
15
+ */
16
+ static fromArray(width, height, values) {
17
+ const grid = new _Grid(width, height);
18
+ for (let i = 0; i < values.length; i++) {
19
+ grid._c[i] = values[i];
20
+ }
21
+ return grid;
22
+ }
23
+ /**
24
+ * @param {number} width The grid width
25
+ * @param {number} height The grid height
26
+ */
27
+ constructor(width, height) {
28
+ this.width = width;
29
+ this.height = height;
30
+ this.clear();
31
+ }
32
+ /**
33
+ * Delete all cell values.
34
+ */
35
+ clear() {
36
+ this._c = Array(this._w * this._h);
37
+ }
38
+ /**
39
+ * @param {number} value
40
+ */
41
+ set width(value) {
42
+ this._w = Math.max(1, ~~value);
43
+ }
44
+ get width() {
45
+ return this._w;
46
+ }
47
+ /**
48
+ * @param {number} value
49
+ */
50
+ set height(value) {
51
+ this._h = Math.max(1, ~~value);
52
+ }
53
+ get height() {
54
+ return this._h;
55
+ }
56
+ /**
57
+ * The the value of a grid's cell.
58
+ *
59
+ * @param {number} x
60
+ * @param {number} y
61
+ * @param {any} value
62
+ */
63
+ set(x, y, value) {
64
+ this._c[this.pointToIndex(x, y)] = value;
65
+ }
66
+ /**
67
+ * Returns the value of a grid's cell.
68
+ *
69
+ * @param {number} x
70
+ * @param {number} y
71
+ * @returns {any}
72
+ */
73
+ get(x, y) {
74
+ return this._c[this.pointToIndex(x, y)];
75
+ }
76
+ /**
77
+ * Returns true if the which cell has any value not equal to `null` or `undefined`.
78
+ *
79
+ * @param {number} x
80
+ * @param {number} y
81
+ * @returns {boolean}
82
+ */
83
+ has(x, y) {
84
+ return this.get(x, y) != null;
85
+ }
86
+ /**
87
+ * Returns the total of cells.
88
+ *
89
+ * @returns {number}
90
+ */
91
+ get length() {
92
+ return this._w * this._h;
93
+ }
94
+ /**
95
+ * Convert a grid point (X, Y) to a index.
96
+ *
97
+ * @param {number} x
98
+ * @param {number} y
99
+ * @returns {number} The index
100
+ */
101
+ pointToIndex(x, y) {
102
+ return this.clampX(~~x) + this.clampY(~~y) * this._w;
103
+ }
104
+ /**
105
+ * Convert index to a grid point X.
106
+ *
107
+ * @param {number} index
108
+ * @returns {number}
109
+ */
110
+ indexToPointX(index) {
111
+ return index % this._w;
112
+ }
113
+ /**
114
+ * Convert index to a grid point Y.
115
+ *
116
+ * @param {number} index
117
+ * @returns {number}
118
+ */
119
+ indexToPointY(index) {
120
+ return Math.floor(index / this._w);
121
+ }
122
+ /**
123
+ * Loops over all grid cells.
124
+ *
125
+ * @callback GridForEachCallback
126
+ * @param {number} x
127
+ * @param {number} y
128
+ * @param {any} value
129
+ * @param {Grid} grid
130
+ * @returns {boolean?} returns `false` to stop/break the loop
131
+ *
132
+ * @param {GridForEachCallback} handler
133
+ * @param {boolean} [reverse=false]
134
+ */
135
+ forEach(handler, reverse = false) {
136
+ let i = reverse ? this.length - 1 : 0, limit = reverse ? -1 : this.length, step = reverse ? -1 : 1;
137
+ while (i !== limit) {
138
+ const x = this.indexToPointX(i), y = this.indexToPointY(i), cellValue = this._c[i];
139
+ if (false === handler(x, y, cellValue, this)) break;
140
+ i += step;
141
+ }
142
+ }
143
+ /**
144
+ * @param {*} value
145
+ */
146
+ fill(value) {
147
+ this.forEach((x, y) => {
148
+ this.set(x, y, value);
149
+ });
150
+ }
151
+ /**
152
+ * @returns {Grid} the cloned grid
153
+ */
154
+ clone() {
155
+ return _Grid.fromArray(this._w, this._h, this._c);
156
+ }
157
+ /**
158
+ * @param {number} y
159
+ * @returns {number}
160
+ */
161
+ clampX(x) {
162
+ return _clamp(x, 0, this._w - 1);
163
+ }
164
+ /**
165
+ * @param {number} y
166
+ * @returns {number}
167
+ */
168
+ clampY(y) {
169
+ return _clamp(y, 0, this._h - 1);
170
+ }
171
+ /**
172
+ * Returns the cell values in a single array.
173
+ *
174
+ * @returns {any[]}
175
+ */
176
+ toArray() {
177
+ return this._c.slice(0);
178
+ }
179
+ /**
180
+ * @param {string} separator
181
+ * @param {boolean} format
182
+ * @returns {string}
183
+ */
184
+ toString(separator = " ", format = true) {
185
+ if (!format) return this._c.join(separator);
186
+ const rows = [];
187
+ this.forEach((x, y, value) => {
188
+ rows[y] = rows[y] || "";
189
+ rows[y] += value + separator;
190
+ });
191
+ return rows.join("\n");
192
+ }
193
+ };
194
+ function _clamp(value, min, max) {
195
+ if (value < min) return min;
196
+ if (value > max) return max;
197
+ return value;
198
+ }
199
+
200
+ // src/grid/_web.js
201
+ globalThis.utils = Object.assign(globalThis.utils || {}, { Grid });
202
+ })();
@@ -0,0 +1,2 @@
1
+ (()=>{var e=class s{_w;_h;_c;static fromArray(t,i,h){let n=new s(t,i);for(let r=0;r<h.length;r++)n._c[r]=h[r];return n}constructor(t,i){this.width=t,this.height=i,this.clear()}clear(){this._c=Array(this._w*this._h)}set width(t){this._w=Math.max(1,~~t)}get width(){return this._w}set height(t){this._h=Math.max(1,~~t)}get height(){return this._h}set(t,i,h){this._c[this.pointToIndex(t,i)]=h}get(t,i){return this._c[this.pointToIndex(t,i)]}has(t,i){return this.get(t,i)!=null}get length(){return this._w*this._h}pointToIndex(t,i){return this.clampX(~~t)+this.clampY(~~i)*this._w}indexToPointX(t){return t%this._w}indexToPointY(t){return Math.floor(t/this._w)}forEach(t,i=!1){let h=i?this.length-1:0,n=i?-1:this.length,r=i?-1:1;for(;h!==n;){let o=this.indexToPointX(h),c=this.indexToPointY(h),_=this._c[h];if(t(o,c,_,this)===!1)break;h+=r}}fill(t){this.forEach((i,h)=>{this.set(i,h,t)})}clone(){return s.fromArray(this._w,this._h,this._c)}clampX(t){return l(t,0,this._w-1)}clampY(t){return l(t,0,this._h-1)}toArray(){return this._c.slice(0)}toString(t=" ",i=!0){if(!i)return this._c.join(t);let h=[];return this.forEach((n,r,o)=>{h[r]=h[r]||"",h[r]+=o+t}),h.join(`
2
+ `)}};function l(s,t,i){return s<t?t:s>i?i:s}globalThis.utils=Object.assign(globalThis.utils||{},{Grid:e});})();
package/dist/vector.js CHANGED
@@ -63,7 +63,7 @@
63
63
  if (isvector(x)) {
64
64
  return veceq(v, x.x, x.y);
65
65
  }
66
- return v.x === x && v.y === (y || x);
66
+ return v.x === x && v.y === y;
67
67
  };
68
68
  var veccopy = (v) => vec(v.x, v.y);
69
69
  var vecset = (v, x, y = x) => {
@@ -1 +1 @@
1
- (()=>{var M=Object.defineProperty;var g=(t,o)=>{for(var s in o)M(t,s,{get:o[s],enumerable:!0})};var i={};g(i,{DOWN:()=>G,LEFT:()=>H,ONE:()=>w,RIGHT:()=>F,UP:()=>D,Vector:()=>n,ZERO:()=>j,isvector:()=>r,vec:()=>c,vecadd:()=>l,vecconfig:()=>x,veccopy:()=>m,veccross:()=>R,vecdir:()=>N,vecdist:()=>E,vecdist2:()=>I,vecdiv:()=>y,vecdot:()=>P,veceq:()=>a,veclerp:()=>U,veclimit:()=>O,vecmag:()=>u,vecmag2:()=>f,vecmult:()=>p,vecnorm:()=>T,vecrand:()=>$,vecrot:()=>q,vecset:()=>d,vecsub:()=>h});var n=class{x;y;constructor(o=0,s=o){this.x=o,this.y=s}toString(){return`Vector (${this.x}, ${this.y})`}},c=(t=0,o=t)=>new n(t,o),a=(t,o,s=o)=>r(o)?a(t,o.x,o.y):t.x===o&&t.y===(s||o),m=t=>c(t.x,t.y),d=(t,o,s=o)=>{r(o)?d(t,o.x,o.y):(t.x=o,t.y=s)},l=(t,o,s=o)=>{r(o)?l(t,o.x,o.y):(t.x+=o,t.y+=s)},h=(t,o,s=o)=>{r(o)?h(t,o.x,o.y):(t.x-=o,t.y-=s)},p=(t,o,s=o)=>{r(o)?p(t,o.x,o.y):(t.x*=o,t.y*=s)},y=(t,o,s=o)=>{r(o)?y(t,o.x,o.y):(t.x/=o,t.y/=s)},q=(t,o)=>{let s=Math.cos(o),e=Math.sin(o);t.x=s*t.x-e*t.y,t.y=e*t.x+s*t.y},u=t=>Math.sqrt(t.x*t.x+t.y*t.y),f=t=>t.x*t.x+t.y*t.y,T=t=>{let o=u(t);o>0&&y(t,o)},O=(t,o)=>{let s=f(t);s>o*o&&(y(t,Math.sqrt(s)),p(t,o))},E=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return Math.sqrt(s*s+e*e)},I=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return s*s+e*e},N=t=>Math.atan2(t.y,t.x),P=(t,o)=>t.x*o.x+t.y*o.y,R=(t,o)=>t.x*o.y-t.y*o.x,U=(t,o,s)=>{t.x+=(o.x-t.x)*s||0,t.y+=(o.y-t.y)*s||0},$=(t=1,o=t)=>{let s=x.random()*2*Math.PI,e=x.random()*(o-t)+t;return c(Math.cos(s)*e,Math.sin(s)*e)},r=t=>t instanceof n,x={random:()=>globalThis.rand?rand():Math.random()},j=c(0,0),w=c(1,1),D=c(0,-1),F=c(1,0),G=c(0,1),H=c(-1,0);globalThis.utils=Object.assign(globalThis.utils||{},i);})();
1
+ (()=>{var M=Object.defineProperty;var g=(t,o)=>{for(var s in o)M(t,s,{get:o[s],enumerable:!0})};var i={};g(i,{DOWN:()=>G,LEFT:()=>H,ONE:()=>w,RIGHT:()=>F,UP:()=>D,Vector:()=>n,ZERO:()=>j,isvector:()=>r,vec:()=>c,vecadd:()=>l,vecconfig:()=>y,veccopy:()=>m,veccross:()=>R,vecdir:()=>N,vecdist:()=>E,vecdist2:()=>I,vecdiv:()=>x,vecdot:()=>P,veceq:()=>a,veclerp:()=>U,veclimit:()=>O,vecmag:()=>u,vecmag2:()=>f,vecmult:()=>p,vecnorm:()=>T,vecrand:()=>$,vecrot:()=>q,vecset:()=>d,vecsub:()=>h});var n=class{x;y;constructor(o=0,s=o){this.x=o,this.y=s}toString(){return`Vector (${this.x}, ${this.y})`}},c=(t=0,o=t)=>new n(t,o),a=(t,o,s=o)=>r(o)?a(t,o.x,o.y):t.x===o&&t.y===s,m=t=>c(t.x,t.y),d=(t,o,s=o)=>{r(o)?d(t,o.x,o.y):(t.x=o,t.y=s)},l=(t,o,s=o)=>{r(o)?l(t,o.x,o.y):(t.x+=o,t.y+=s)},h=(t,o,s=o)=>{r(o)?h(t,o.x,o.y):(t.x-=o,t.y-=s)},p=(t,o,s=o)=>{r(o)?p(t,o.x,o.y):(t.x*=o,t.y*=s)},x=(t,o,s=o)=>{r(o)?x(t,o.x,o.y):(t.x/=o,t.y/=s)},q=(t,o)=>{let s=Math.cos(o),e=Math.sin(o);t.x=s*t.x-e*t.y,t.y=e*t.x+s*t.y},u=t=>Math.sqrt(t.x*t.x+t.y*t.y),f=t=>t.x*t.x+t.y*t.y,T=t=>{let o=u(t);o>0&&x(t,o)},O=(t,o)=>{let s=f(t);s>o*o&&(x(t,Math.sqrt(s)),p(t,o))},E=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return Math.sqrt(s*s+e*e)},I=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return s*s+e*e},N=t=>Math.atan2(t.y,t.x),P=(t,o)=>t.x*o.x+t.y*o.y,R=(t,o)=>t.x*o.y-t.y*o.x,U=(t,o,s)=>{t.x+=(o.x-t.x)*s||0,t.y+=(o.y-t.y)*s||0},$=(t=1,o=t)=>{let s=y.random()*2*Math.PI,e=y.random()*(o-t)+t;return c(Math.cos(s)*e,Math.sin(s)*e)},r=t=>t instanceof n,y={random:()=>globalThis.rand?rand():Math.random()},j=c(0,0),w=c(1,1),D=c(0,-1),F=c(1,0),G=c(0,1),H=c(-1,0);globalThis.utils=Object.assign(globalThis.utils||{},i);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litecanvas/utils",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Utilities to help build litecanvas games",
5
5
  "author": "Luiz Bills <luizbills@pm.me>",
6
6
  "license": "MIT",
@@ -40,6 +40,8 @@ function draw() {
40
40
 
41
41
  Syntax: `resolve(x1, y1, w1, h1, x2, y2, w2, h2): { direction: string, x: number, y: number }`
42
42
 
43
+ > Note: possible values for `direction` is `"top"`, `"botton"`, `"left"`, `"right"` or `""` (empty string, if no collision).
44
+
43
45
  ```js
44
46
  import litecanvas from "litecanvas"
45
47
  import { resolve } from "@litecanvas/utils"
@@ -0,0 +1,45 @@
1
+ # Grid
2
+
3
+ **CDN**: https://unpkg.com/@litecanvas/utils/dist/grid.js
4
+
5
+ ## Usage
6
+
7
+ Lets build an arena with [ASCII graphics](https://en.wikipedia.org/wiki/ASCII_art) like in classic roguelikes.
8
+
9
+ ```js
10
+ // make a grid 13x13
11
+ let grid = new utils.Grid(13, 13)
12
+
13
+ // fill the entire grid with "."
14
+ grid.fill(".")
15
+
16
+ // put a '@' in the middle
17
+ grid.set(grid.width / 2, grid.height / 2, "@")
18
+
19
+ // put '#' around the grid
20
+ grid.forEach((x, y, cellValue) => {
21
+ if (x === 0 || y === 0 || x === grid.width - 1 || y === grid.height - 1) {
22
+ grid.set(x, y, "#")
23
+ }
24
+ })
25
+
26
+ document.body.innerHTML = "<pre>" + grid.toString() + "</pre>"
27
+ ```
28
+
29
+ The result:
30
+
31
+ ```
32
+ # # # # # # # # # # # # #
33
+ # . . . . . . . . . . . #
34
+ # . . . . . . . . . . . #
35
+ # . . . . . . . . . . . #
36
+ # . . . . . . . . . . . #
37
+ # . . . . . . . . . . . #
38
+ # . . . . . @ . . . . . #
39
+ # . . . . . . . . . . . #
40
+ # . . . . . . . . . . . #
41
+ # . . . . . . . . . . . #
42
+ # . . . . . . . . . . . #
43
+ # . . . . . . . . . . . #
44
+ # # # # # # # # # # # # #
45
+ ```
@@ -0,0 +1,3 @@
1
+ import Grid from "./index.js"
2
+
3
+ globalThis.utils = Object.assign(globalThis.utils || {}, { Grid })
package/src/grid/index.js CHANGED
@@ -1,91 +1,239 @@
1
- class Grid {
1
+ export default class Grid {
2
+ /** @type {number} The grid width */
3
+ _w
4
+
5
+ /** @type {number} The grid height */
6
+ _h
7
+
8
+ /** @type {any[]} The grid cells */
9
+ _c
10
+
11
+ /**
12
+ * @static
13
+ * @param {number} width
14
+ * @param {number} height
15
+ * @param {any[]} values
16
+ */
17
+ static fromArray(width, height, values) {
18
+ const grid = new Grid(width, height)
19
+ for (let i = 0; i < values.length; i++) {
20
+ grid._c[i] = values[i]
21
+ }
22
+ return grid
23
+ }
24
+
25
+ /**
26
+ * @param {number} width The grid width
27
+ * @param {number} height The grid height
28
+ */
2
29
  constructor(width, height) {
3
- this._width = Math.abs(width)
4
- this._height = Math.abs(height)
30
+ this.width = width
31
+ this.height = height
5
32
  this.clear()
6
33
  }
7
34
 
8
- set(x, y, value) {
9
- this.__cells[this.__getIndex(x, y)] = value
10
- return this
35
+ /**
36
+ * Delete all cell values.
37
+ */
38
+ clear() {
39
+ this._c = Array(this._w * this._h)
11
40
  }
12
41
 
13
- get(x, y) {
14
- return this.__cells[this.__getIndex(x, y)] || null
42
+ /**
43
+ * @param {number} value
44
+ */
45
+ set width(value) {
46
+ this._w = Math.max(1, ~~value)
15
47
  }
16
48
 
17
- has(x, y) {
18
- return this.get(x, y) != null
49
+ get width() {
50
+ return this._w
19
51
  }
20
52
 
21
- get width() {
22
- return this._width
53
+ /**
54
+ * @param {number} value
55
+ */
56
+ set height(value) {
57
+ this._h = Math.max(1, ~~value)
23
58
  }
24
59
 
25
60
  get height() {
26
- return this._height
61
+ return this._h
62
+ }
63
+
64
+ /**
65
+ * The the value of a grid's cell.
66
+ *
67
+ * @param {number} x
68
+ * @param {number} y
69
+ * @param {any} value
70
+ */
71
+ set(x, y, value) {
72
+ this._c[this.pointToIndex(x, y)] = value
73
+ }
74
+
75
+ /**
76
+ * Returns the value of a grid's cell.
77
+ *
78
+ * @param {number} x
79
+ * @param {number} y
80
+ * @returns {any}
81
+ */
82
+ get(x, y) {
83
+ return this._c[this.pointToIndex(x, y)]
27
84
  }
28
85
 
86
+ /**
87
+ * Returns true if the which cell has any value not equal to `null` or `undefined`.
88
+ *
89
+ * @param {number} x
90
+ * @param {number} y
91
+ * @returns {boolean}
92
+ */
93
+ has(x, y) {
94
+ return this.get(x, y) != null
95
+ }
96
+
97
+ /**
98
+ * Returns the total of cells.
99
+ *
100
+ * @returns {number}
101
+ */
29
102
  get length() {
30
- return this._width * this._height
31
- }
32
-
33
- forEach(callback, reverse = false) {
34
- const cells = this.__cells,
35
- w = this._width,
36
- h = this._height,
37
- limitX = !reverse ? w : -1,
38
- limitY = !reverse ? h : -1
39
- let x = !reverse ? 0 : w - 1,
40
- y = !reverse ? 0 : h - 1
41
-
42
- while (y != limitY) {
43
- let proceed
44
- x = !reverse ? 0 : w - 1
45
- while (x != limitX) {
46
- proceed = callback(cells[this.__getIndex(x, y)], x, y, this)
47
- if (false === proceed) break
48
- x += reverse ? -1 : 1
49
- }
50
- if (false === proceed) break
51
- y += reverse ? -1 : 1
103
+ return this._w * this._h
104
+ }
105
+
106
+ /**
107
+ * Convert a grid point (X, Y) to a index.
108
+ *
109
+ * @param {number} x
110
+ * @param {number} y
111
+ * @returns {number} The index
112
+ */
113
+ pointToIndex(x, y) {
114
+ return this.clampX(~~x) + this.clampY(~~y) * this._w
115
+ }
116
+
117
+ /**
118
+ * Convert index to a grid point X.
119
+ *
120
+ * @param {number} index
121
+ * @returns {number}
122
+ */
123
+ indexToPointX(index) {
124
+ return index % this._w
125
+ }
126
+
127
+ /**
128
+ * Convert index to a grid point Y.
129
+ *
130
+ * @param {number} index
131
+ * @returns {number}
132
+ */
133
+ indexToPointY(index) {
134
+ return Math.floor(index / this._w)
135
+ }
136
+
137
+ /**
138
+ * Loops over all grid cells.
139
+ *
140
+ * @callback GridForEachCallback
141
+ * @param {number} x
142
+ * @param {number} y
143
+ * @param {any} value
144
+ * @param {Grid} grid
145
+ * @returns {boolean?} returns `false` to stop/break the loop
146
+ *
147
+ * @param {GridForEachCallback} handler
148
+ * @param {boolean} [reverse=false]
149
+ */
150
+ forEach(handler, reverse = false) {
151
+ let i = reverse ? this.length - 1 : 0,
152
+ limit = reverse ? -1 : this.length,
153
+ step = reverse ? -1 : 1
154
+
155
+ while (i !== limit) {
156
+ const x = this.indexToPointX(i),
157
+ y = this.indexToPointY(i),
158
+ cellValue = this._c[i]
159
+
160
+ if (false === handler(x, y, cellValue, this)) break
161
+
162
+ i += step
52
163
  }
53
- return this
54
164
  }
55
165
 
56
- clone() {
57
- return Grid.fromArray(this._width, this._height, this.__cells)
166
+ /**
167
+ * @param {*} value
168
+ */
169
+ fill(value) {
170
+ this.forEach((x, y) => {
171
+ this.set(x, y, value)
172
+ })
58
173
  }
59
174
 
60
- clear() {
61
- this.__cells = Array(this._width * this._height)
62
- return this
175
+ /**
176
+ * @returns {Grid} the cloned grid
177
+ */
178
+ clone() {
179
+ return Grid.fromArray(this._w, this._h, this._c)
63
180
  }
64
181
 
65
- clipX(x) {
66
- return this.__clip(x, 0, this._width - 1)
182
+ /**
183
+ * @param {number} y
184
+ * @returns {number}
185
+ */
186
+ clampX(x) {
187
+ return _clamp(x, 0, this._w - 1)
67
188
  }
68
189
 
69
- clipY(y) {
70
- return this.__clip(y, 0, this._height - 1)
190
+ /**
191
+ * @param {number} y
192
+ * @returns {number}
193
+ */
194
+ clampY(y) {
195
+ return _clamp(y, 0, this._h - 1)
71
196
  }
72
197
 
198
+ /**
199
+ * Returns the cell values in a single array.
200
+ *
201
+ * @returns {any[]}
202
+ */
73
203
  toArray() {
74
- return this.__cells.slice(0)
204
+ return this._c.slice(0)
75
205
  }
76
206
 
77
- toString() {
78
- return this.__cells.join(",")
79
- }
207
+ /**
208
+ * @param {string} separator
209
+ * @param {boolean} format
210
+ * @returns {string}
211
+ */
212
+ toString(separator = " ", format = true) {
213
+ if (!format) return this._c.join(separator)
80
214
 
81
- // internals
82
- __getIndex(x, y) {
83
- x = this.clipX(x)
84
- y = this.clipY(y)
85
- return x + y * this._width
86
- }
215
+ const rows = []
216
+ this.forEach((x, y, value) => {
217
+ rows[y] = rows[y] || ""
218
+ rows[y] += value + separator
219
+ })
87
220
 
88
- __clip(n, min, max) {
89
- return Math.min(Math.max(n, min), max)
221
+ return rows.join("\n")
90
222
  }
91
223
  }
224
+
225
+ /**
226
+ * Constrains a number between `min` and `max`.
227
+ *
228
+ * @param {number} value
229
+ * @param {number} min
230
+ * @param {number} max
231
+ * @returns {number}
232
+ */
233
+ function _clamp(value, min, max) {
234
+ if (value < min) return min
235
+ if (value > max) return max
236
+ return value
237
+ }
238
+
239
+ function _fill(x, y) {}
package/src/index.js CHANGED
@@ -4,4 +4,5 @@ export { default as wave } from "./math/wave.js"
4
4
  export { default as resolve } from "./collision/resolve.js"
5
5
  export { default as intersection } from "./collision/intersection.js"
6
6
  export { default as Camera } from "./camera/index.js"
7
+ export { default as Grid } from "./grid/index.js"
7
8
  export * from "./vector/index.js"
@@ -42,7 +42,7 @@ export const veceq = (v, x, y = x) => {
42
42
  if (isvector(x)) {
43
43
  return veceq(v, x.x, x.y)
44
44
  }
45
- return v.x === x && v.y === (y || x)
45
+ return v.x === x && v.y === y
46
46
  }
47
47
 
48
48
  /**