@dryanovski/gamefoo 0.2.1 → 0.2.5

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 (84) hide show
  1. package/dist/core/animate.js +147 -0
  2. package/dist/core/asset.js +74 -0
  3. package/dist/core/behaviour.js +88 -0
  4. package/dist/core/behaviours/collidable.js +186 -0
  5. package/dist/core/behaviours/control.js +75 -0
  6. package/dist/core/behaviours/healtkit.js +153 -0
  7. package/dist/core/behaviours/sprite_render.js +193 -0
  8. package/dist/core/camera.js +134 -0
  9. package/dist/core/engine.d.ts +1 -1
  10. package/dist/core/engine.d.ts.map +1 -1
  11. package/dist/core/engine.js +527 -0
  12. package/dist/core/fonts/font_bitmap.js +205 -0
  13. package/dist/core/fonts/font_bitmap_prebuild.js +137 -0
  14. package/dist/core/fonts/internal/font_3x5.js +169 -0
  15. package/dist/core/fonts/internal/font_4x6.js +171 -0
  16. package/dist/core/fonts/internal/font_5x5.js +129 -0
  17. package/dist/core/fonts/internal/font_6x8.js +171 -0
  18. package/dist/core/fonts/internal/font_8x13.js +171 -0
  19. package/dist/core/fonts/internal/font_8x8.js +171 -0
  20. package/dist/core/game_object_register.js +134 -0
  21. package/dist/core/input.js +170 -0
  22. package/dist/core/sprite.js +222 -0
  23. package/dist/core/utils/perlin_noise.js +183 -0
  24. package/dist/core/world.js +304 -0
  25. package/dist/debug/monitor.js +47 -0
  26. package/dist/decorators/index.js +1 -0
  27. package/dist/decorators/log.js +42 -0
  28. package/dist/entities/dynamic_entity.js +99 -0
  29. package/dist/entities/entity.js +283 -0
  30. package/dist/entities/player.js +93 -0
  31. package/dist/entities/text.js +62 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +47 -29
  35. package/dist/subsystems/camera_system.d.ts +2 -2
  36. package/dist/subsystems/camera_system.d.ts.map +1 -1
  37. package/dist/subsystems/camera_system.js +41 -0
  38. package/dist/subsystems/collision_system.js +17 -0
  39. package/dist/subsystems/monitor_system.js +20 -0
  40. package/dist/subsystems/object_system.d.ts +1 -1
  41. package/dist/subsystems/object_system.d.ts.map +1 -1
  42. package/dist/subsystems/object_system.js +29 -0
  43. package/dist/subsystems/types.d.ts +1 -1
  44. package/dist/subsystems/types.d.ts.map +1 -1
  45. package/dist/subsystems/types.js +0 -0
  46. package/dist/types.js +10 -0
  47. package/package.json +17 -6
  48. package/src/core/animate.ts +159 -0
  49. package/src/core/asset.ts +76 -0
  50. package/src/core/behaviour.ts +145 -0
  51. package/src/core/behaviours/collidable.ts +296 -0
  52. package/src/core/behaviours/control.ts +80 -0
  53. package/src/core/behaviours/healtkit.ts +166 -0
  54. package/src/core/behaviours/sprite_render.ts +216 -0
  55. package/src/core/camera.ts +145 -0
  56. package/src/core/engine.ts +607 -0
  57. package/src/core/fonts/font_bitmap.ts +232 -0
  58. package/src/core/fonts/font_bitmap_prebuild.ts +141 -0
  59. package/src/core/fonts/internal/font_3x5.ts +178 -0
  60. package/src/core/fonts/internal/font_4x6.ts +180 -0
  61. package/src/core/fonts/internal/font_5x5.ts +137 -0
  62. package/src/core/fonts/internal/font_6x8.ts +180 -0
  63. package/src/core/fonts/internal/font_8x13.ts +180 -0
  64. package/src/core/fonts/internal/font_8x8.ts +180 -0
  65. package/src/core/game_object_register.ts +146 -0
  66. package/src/core/input.ts +182 -0
  67. package/src/core/sprite.ts +339 -0
  68. package/src/core/utils/perlin_noise.ts +196 -0
  69. package/src/core/world.ts +331 -0
  70. package/src/debug/monitor.ts +60 -0
  71. package/src/decorators/index.ts +1 -0
  72. package/src/decorators/log.ts +45 -0
  73. package/src/entities/dynamic_entity.ts +106 -0
  74. package/src/entities/entity.ts +322 -0
  75. package/src/entities/player.ts +99 -0
  76. package/src/entities/text.ts +72 -0
  77. package/src/index.ts +51 -0
  78. package/src/subsystems/camera_system.ts +52 -0
  79. package/src/subsystems/collision_system.ts +21 -0
  80. package/src/subsystems/monitor_system.ts +26 -0
  81. package/src/subsystems/object_system.ts +37 -0
  82. package/src/subsystems/types.ts +46 -0
  83. package/src/types.ts +178 -0
  84. package/dist/index.js.map +0 -9
@@ -0,0 +1,339 @@
1
+ import Asset from "./asset";
2
+
3
+ /**
4
+ * Describes a single named animation within a {@link Sprite} sheet.
5
+ *
6
+ * @category Core
7
+ * @since 0.1.0
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const walkAnim: AnimationDefinition = {
12
+ * frames: [0, 1, 2, 3],
13
+ * duration: 0.15,
14
+ * loop: true,
15
+ * };
16
+ * ```
17
+ */
18
+ interface AnimationDefinition {
19
+ /** Ordered frame indices into the spritesheet grid. */
20
+ frames: (string | number)[];
21
+ /** Time in seconds each frame is displayed before advancing. */
22
+ duration: number;
23
+ /** Whether the animation restarts from frame 0 after the last frame. */
24
+ loop: boolean;
25
+ }
26
+
27
+ /**
28
+ * Describes the position and size of a single frame within a {@link Sprite}
29
+ * sheet.
30
+ *
31
+ * @category Core
32
+ * @since 0.2.0
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const frame: SpriteFrame = {
37
+ * x: 32,
38
+ * y: 64,
39
+ * width: 32,
40
+ * height: 32,
41
+ * anchor: { x: 16, y: 16 },
42
+ * };
43
+ * ```
44
+ */
45
+ interface SpriteFrame {
46
+ x: number;
47
+ y: number;
48
+ width: number;
49
+ height: number;
50
+ anchor?: { x: number; y: number };
51
+ }
52
+
53
+ /**
54
+ * Configuration options for slicing a {@link Sprite} sheet into a grid of
55
+ * frames.
56
+ *
57
+ * @category Core
58
+ * @since 0.2.0
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const config: GridConfig = {
63
+ * frameWidth: 32,
64
+ * frameHeight: 32,
65
+ * offsetX: 0,
66
+ * offsetY: 0,
67
+ * spacingX: 0,
68
+ * spacingY: 0,
69
+ * count: 16,
70
+ * };
71
+ * ```
72
+ */
73
+ interface GridConfig {
74
+ frameWidth: number;
75
+ frameHeight: number;
76
+ /* left margin before the first column */
77
+ offsetX?: number;
78
+ /* top margin before the first row */
79
+ offsetY?: number;
80
+ /* horizontal gap between cells */
81
+ spacingX?: number;
82
+ /* vertical gap between cells */
83
+ spacingY?: number;
84
+ /* total number of frames in the sheet (optional, can be inferred from image size) */
85
+ count?: number;
86
+ }
87
+
88
+ /**
89
+ * Metadata wrapper around an {@link HTMLImageElement} that describes how
90
+ * it is sliced into a uniform grid of frames and what named animations
91
+ * are available.
92
+ *
93
+ * `Sprite` does **not** handle rendering itself — use
94
+ * {@link SpriteRender} to play animations on an entity.
95
+ *
96
+ * @category Core
97
+ * @since 0.1.0
98
+ *
99
+ * @example Loading and creating a sprite
100
+ * ```ts
101
+ * import { Asset } from "gamefoo";
102
+ *
103
+ * const image = await Asset.load("hero.png");
104
+ * const sprite = new Sprite(image, 32, 32, {
105
+ * idle: { frames: [0, 1], duration: 0.25, loop: true },
106
+ * run: { frames: [2, 3, 4, 5], duration: 0.1, loop: true },
107
+ * });
108
+ * ```
109
+ *
110
+ * @example Querying frame coordinates
111
+ * ```ts
112
+ * const rect = sprite.getFrameRect(5);
113
+ * ctx.drawImage(
114
+ * sprite.image,
115
+ * rect.x, rect.y, rect.width, rect.height,
116
+ * destX, destY, rect.width, rect.height,
117
+ * );
118
+ * ```
119
+ *
120
+ * @see {@link SpriteRender} — behaviour that plays sprite animations
121
+ * @see {@link Asset} — image loading utility
122
+ */
123
+ export default class Sprite {
124
+ /** The underlying image element containing the full spritesheet. */
125
+ public image: HTMLImageElement;
126
+
127
+ /** Width of a single frame cell in pixels. */
128
+ readonly width: number;
129
+
130
+ /** Height of a single frame cell in pixels. */
131
+ readonly height: number;
132
+
133
+ /**
134
+ * Number of frame columns in the spritesheet, computed as
135
+ * `Math.floor(image.width / width)`.
136
+ */
137
+ readonly columns: number;
138
+
139
+ /**
140
+ * Number of frame rows in the spritesheet, computed as
141
+ * `Math.floor(image.height / height)`.
142
+ */
143
+ readonly rows: number;
144
+
145
+ /**
146
+ * Named animation definitions keyed by animation name.
147
+ *
148
+ * Populated from the optional `animations` parameter passed to the
149
+ * constructor.
150
+ */
151
+ public animations: Map<string, AnimationDefinition>;
152
+
153
+ /**
154
+ * @since 0.2.0
155
+ */
156
+ public frames: Map<number | string, SpriteFrame>;
157
+
158
+ /**
159
+ * Creates a new spritesheet descriptor.
160
+ *
161
+ * @param image - A fully-loaded `HTMLImageElement` containing the
162
+ * spritesheet texture.
163
+ * @param width - Width of each individual frame in pixels.
164
+ * @param height - Height of each individual frame in pixels.
165
+ * @param animations - Optional map of named animation definitions.
166
+ * Keys are animation names (e.g. `"idle"`, `"run"`).
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * const sprite = new Sprite(img, 64, 64, {
171
+ * idle: { frames: [0], duration: 1, loop: false },
172
+ * });
173
+ * ```
174
+ */
175
+ constructor(
176
+ image: HTMLImageElement,
177
+ width: number,
178
+ height: number,
179
+ animations?: Record<string, AnimationDefinition>,
180
+ ) {
181
+ this.image = image;
182
+ this.width = width;
183
+ this.height = height;
184
+ this.columns = Math.floor(image.width / width);
185
+ this.rows = Math.floor(image.height / height);
186
+
187
+ this.frames = Sprite.generateGridFrames(image, {
188
+ frameWidth: width,
189
+ frameHeight: height,
190
+ });
191
+
192
+ this.animations = new Map(Object.entries(animations || {}));
193
+ }
194
+
195
+ /**
196
+ * Alternative constructor for spritesheets that are already sliced into a
197
+ * uniform grid of frames.
198
+ *
199
+ * @since 0.2.0
200
+ *
201
+ * @param image - A fully-loaded `HTMLImageElement` containing the
202
+ * spritesheet texture.
203
+ * @param config - Configuration options for slicing the image into a
204
+ * grid of frames.
205
+ * @param animations - Optional map of named animation definitions.
206
+ * Keys are animation names (e.g. `"idle"`, `"run"`).
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * const sprite = Sprite.fromGrid(img, {
211
+ * frameWidth: 64,
212
+ *
213
+ * frameHeight: 64,
214
+ * offsetX: 0,
215
+ * offsetY: 0,
216
+ * spacingX: 0,
217
+ * spacingY: 0,
218
+ * count: 16,
219
+ * }, {
220
+ * idle: { frames: [0, 1], duration: 0.25, loop: true },
221
+ * run: { frames: [2, 3, 4, 5], duration: 0.1, loop: true },
222
+ * });
223
+ * ```
224
+ */
225
+ static fromGrid(
226
+ image: HTMLImageElement,
227
+ config: GridConfig,
228
+ animations?: Record<string, AnimationDefinition>,
229
+ ): Sprite {
230
+ const sprite = Object.create(Sprite.prototype) as Sprite;
231
+ sprite.image = image;
232
+ sprite.frames = Sprite.generateGridFrames(image, config);
233
+ sprite.animations = new Map(Object.entries(animations || {}));
234
+ return sprite;
235
+ }
236
+
237
+ /**
238
+ * Helper method to compute frame rectangles for a spritesheet sliced into a
239
+ * uniform grid.
240
+ *
241
+ * @since 0.2.0
242
+ *
243
+ * @param image - A fully-loaded `HTMLImageElement` containing the
244
+ * spritesheet texture.
245
+ * @param config - Configuration options for slicing the image into a grid of
246
+ * frames.
247
+ *
248
+ * @returns A map of frame indices to their corresponding source rectangles.
249
+ * Frame indices are zero-based and laid out left-to-right, top-to-bottom.
250
+ * The source rectangles are in pixel coordinates relative to the top-left corner
251
+ * of the source image.
252
+ */
253
+ private static generateGridFrames(image: HTMLImageElement, config: GridConfig): Map<number, SpriteFrame> {
254
+ const { frameWidth, frameHeight, offsetX = 0, offsetY = 0, spacingX = 0, spacingY = 0 } = config;
255
+
256
+ const cols = Math.floor((image.width - offsetX + spacingX) / (frameWidth + spacingX));
257
+ const rows = Math.floor((image.height - offsetY + spacingY) / (frameHeight + spacingY));
258
+ const total = config.count ?? cols * rows;
259
+ const frames = new Map<number, SpriteFrame>();
260
+
261
+ for (let i = 0; i < total; i++) {
262
+ const col = i % cols;
263
+ const row = Math.floor(i / cols);
264
+ frames.set(i, {
265
+ x: offsetX + col * (frameWidth + spacingX),
266
+ y: offsetY + row * (frameHeight + spacingY),
267
+ width: frameWidth,
268
+ height: frameHeight,
269
+ });
270
+ }
271
+ return frames;
272
+ }
273
+
274
+ static fromAtlas(
275
+ image: HTMLImageElement,
276
+ regions: Record<string, SpriteFrame>,
277
+ animations?: Record<string, AnimationDefinition>,
278
+ ): Sprite {
279
+ const sprite = Object.create(Sprite.prototype) as Sprite;
280
+ sprite.image = image;
281
+ sprite.frames = new Map(Object.entries(regions));
282
+ sprite.animations = new Map(Object.entries(animations || {}));
283
+ return sprite;
284
+ }
285
+
286
+ static async fromAseprite(imagePath: string, jsonPath: string): Promise<Sprite> {
287
+ const [image, response] = await Promise.all([Asset.load(imagePath), fetch(jsonPath)]);
288
+ const data = await response.json();
289
+
290
+ const regions: Record<string, SpriteFrame> = {};
291
+ for (const [name, entry] of Object.entries(data.frames)) {
292
+ const f = (entry as any).frame;
293
+ regions[name] = { x: f.x, y: f.y, width: f.w, height: f.h };
294
+ }
295
+
296
+ const animations: Record<string, AnimationDefinition> = {};
297
+ if (data.meta?.frameTags) {
298
+ for (const tag of data.meta.frameTags) {
299
+ const frameNames: string[] = [];
300
+ for (let i = tag.from; i <= tag.to; i++) {
301
+ const key = Object.keys(data.frames)[i];
302
+ if (key) frameNames.push(key);
303
+ }
304
+ animations[tag.name] = {
305
+ frames: frameNames,
306
+ duration: ((data.frames[frameNames[0]!] as any)?.duration ?? 100) / 1000,
307
+ loop: tag.direction !== "forward_once",
308
+ };
309
+ }
310
+ }
311
+
312
+ return Sprite.fromAtlas(image, regions, animations);
313
+ }
314
+
315
+ /**
316
+ * Computes the source rectangle for a given frame index within the
317
+ * spritesheet.
318
+ *
319
+ * Frame indices are zero-based and laid out left-to-right,
320
+ * top-to-bottom.
321
+ *
322
+ * @param frame - Zero-based frame index.
323
+ * @returns An `{ x, y, width, height }` rectangle in pixel coordinates
324
+ * relative to the top-left corner of the source image.
325
+ *
326
+ * @example
327
+ * ```ts
328
+ * // For a 4-column sheet, frame 5 → col 1, row 1
329
+ * const rect = sprite.getFrameRect(5);
330
+ * ```
331
+ */
332
+ getFrameRect(frame: number | string): SpriteFrame {
333
+ const rect = this.frames.get(frame);
334
+ if (!rect) {
335
+ throw new Error(`Frame "${frame}" not found in sprite`);
336
+ }
337
+ return rect;
338
+ }
339
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Deterministic 2-D Perlin noise generator with fractal Brownian motion
3
+ * (fBm) support.
4
+ *
5
+ * Produces smooth, continuous noise suitable for terrain heightmaps,
6
+ * cloud textures, procedural vegetation placement, and other organic
7
+ * patterns. The output is fully reproducible for a given seed.
8
+ *
9
+ * The implementation uses a 256-entry permutation table shuffled by a
10
+ * seeded linear congruential generator (LCG) and Ken Perlin's improved
11
+ * 5th-order fade curve \( 6t^5 - 15t^4 + 10t^3 \).
12
+ *
13
+ * @category Utilities
14
+ * @since 0.1.0
15
+ *
16
+ * @example Basic noise sampling
17
+ * ```ts
18
+ * import { PerlinNoise } from "gamefoo";
19
+ *
20
+ * const noise = new PerlinNoise(42);
21
+ * const value = noise.noise2d(1.5, 2.3); // returns a value in [-1, 1]
22
+ * ```
23
+ *
24
+ * @example Generating a heightmap with fBm
25
+ * ```ts
26
+ * const noise = new PerlinNoise(12345);
27
+ * const map: number[][] = [];
28
+ *
29
+ * for (let y = 0; y < 128; y++) {
30
+ * map[y] = [];
31
+ * for (let x = 0; x < 128; x++) {
32
+ * map[y][x] = noise.fbm(x * 0.05, y * 0.05, 6, 2, 0.5);
33
+ * }
34
+ * }
35
+ * ```
36
+ *
37
+ * @example Seeded reproducibility
38
+ * ```ts
39
+ * const a = new PerlinNoise(7);
40
+ * const b = new PerlinNoise(7);
41
+ * console.log(a.noise2d(3, 4) === b.noise2d(3, 4)); // true
42
+ * ```
43
+ */
44
+ export class PerlinNoise {
45
+ /**
46
+ * Doubled permutation table (512 entries) used for gradient hashing.
47
+ * Built from a 256-entry shuffle of `[0..255]` seeded by the LCG.
48
+ */
49
+ private perm: Uint8Array;
50
+
51
+ /**
52
+ * Creates a new noise generator with the given seed.
53
+ *
54
+ * @param seed - An integer seed for the internal LCG. Identical seeds
55
+ * produce identical noise fields.
56
+ *
57
+ * @defaultValue `0`
58
+ */
59
+ constructor(seed: number = 0) {
60
+ this.perm = new Uint8Array(512);
61
+ const p = new Uint8Array(256);
62
+
63
+ for (let i = 0; i < 256; i++) p[i] = i;
64
+
65
+ let s = seed >>> 0;
66
+ for (let i = 255; i > 0; i--) {
67
+ s = (Math.imul(1664525, s) + 1013904223) >>> 0;
68
+ const j = s % (i + 1);
69
+ [p[i], p[j]] = [p[j]!, p[i]!];
70
+ }
71
+
72
+ for (let i = 0; i < 512; i++) this.perm[i] = p[i & 255]!;
73
+ }
74
+
75
+ /**
76
+ * Ken Perlin's improved 5th-order fade (smoothstep) curve:
77
+ * \( 6t^5 - 15t^4 + 10t^3 \).
78
+ *
79
+ * @param t - Value in `[0, 1]`.
80
+ * @returns Smoothed value in `[0, 1]`.
81
+ *
82
+ * @internal
83
+ */
84
+ private fade(t: number): number {
85
+ return t * t * t * (t * (t * 6 - 15) + 10);
86
+ }
87
+
88
+ /**
89
+ * Standard linear interpolation.
90
+ *
91
+ * @param a - Start value.
92
+ * @param b - End value.
93
+ * @param t - Interpolant in `[0, 1]`.
94
+ * @returns Interpolated value.
95
+ *
96
+ * @internal
97
+ */
98
+ private lerp(a: number, b: number, t: number): number {
99
+ return a + t * (b - a);
100
+ }
101
+
102
+ /**
103
+ * Computes a pseudo-random gradient dot product from a hash and
104
+ * 2-D offset.
105
+ *
106
+ * Uses the bottom 2 bits of `hash` to select one of four gradient
107
+ * directions.
108
+ *
109
+ * @param hash - Permutation table entry.
110
+ * @param x - X offset from the grid point.
111
+ * @param y - Y offset from the grid point.
112
+ * @returns Dot product of the gradient and offset vectors.
113
+ *
114
+ * @internal
115
+ */
116
+ private grad(hash: number, x: number, y: number): number {
117
+ const h = hash & 3;
118
+ const u = h < 2 ? x : y;
119
+ const v = h < 2 ? y : x;
120
+ return (h & 1 ? -u : u) + (h & 2 ? -v : v);
121
+ }
122
+
123
+ /**
124
+ * Samples 2-D Perlin noise at the given coordinates.
125
+ *
126
+ * @param x - X coordinate (any real number).
127
+ * @param y - Y coordinate (any real number).
128
+ * @returns A noise value in the range `[-1, 1]`.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * const n = noise.noise2d(0.5, 0.5);
133
+ * // n is a smooth, deterministic value in [-1, 1]
134
+ * ```
135
+ */
136
+ noise2d(x: number, y: number): number {
137
+ const xi = Math.floor(x) & 255;
138
+ const yi = Math.floor(y) & 255;
139
+ const xf = x - Math.floor(x);
140
+ const yf = y - Math.floor(y);
141
+
142
+ const u = this.fade(xf);
143
+ const v = this.fade(yf);
144
+
145
+ const aa = this.perm[this.perm[xi]! + yi]!;
146
+ const ab = this.perm[this.perm[xi]! + yi + 1]!;
147
+ const ba = this.perm[this.perm[xi + 1]! + yi]!;
148
+ const bb = this.perm[this.perm[xi + 1]! + yi + 1]!;
149
+
150
+ const x1 = this.lerp(this.grad(aa, xf, yf), this.grad(ba, xf - 1, yf), u);
151
+ const x2 = this.lerp(this.grad(ab, xf, yf - 1), this.grad(bb, xf - 1, yf - 1), u);
152
+
153
+ return this.lerp(x1, x2, v);
154
+ }
155
+
156
+ /**
157
+ * Computes fractal Brownian motion (fBm) by layering multiple
158
+ * octaves of {@link PerlinNoise.noise2d} with increasing frequency
159
+ * and decreasing amplitude.
160
+ *
161
+ * The result is normalised to `[-1, 1]`.
162
+ *
163
+ * @param x - X coordinate.
164
+ * @param y - Y coordinate.
165
+ * @param octaves - Number of noise layers to sum.
166
+ * @param lacunarity - Frequency multiplier per octave.
167
+ * @param persistence - Amplitude multiplier per octave (controls
168
+ * roughness).
169
+ * @returns A noise value in `[-1, 1]`.
170
+ *
171
+ * @defaultValue octaves = `4`
172
+ * @defaultValue lacunarity = `2`
173
+ * @defaultValue persistence = `0.5`
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * // 6 octaves for high detail:
178
+ * const height = noise.fbm(x * 0.01, y * 0.01, 6, 2.0, 0.5);
179
+ * ```
180
+ */
181
+ fbm(x: number, y: number, octaves = 4, lacunarity = 2, persistence = 0.5): number {
182
+ let value = 0;
183
+ let amplitude = 1;
184
+ let frequency = 1;
185
+ let max = 0;
186
+
187
+ for (let i = 0; i < octaves; i++) {
188
+ value += this.noise2d(x * frequency, y * frequency) * amplitude;
189
+ max += amplitude;
190
+ amplitude *= persistence;
191
+ frequency *= lacunarity;
192
+ }
193
+
194
+ return value / max;
195
+ }
196
+ }