@blorkfield/overlay-core 0.3.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 ADDED
@@ -0,0 +1,417 @@
1
+ # @blorkfield/overlay-core
2
+
3
+ A physics based interactive canvas library built on Matter.js. Create dynamic scenes where objects fall under gravity, stack on obstacles, and trigger collapse events when pressure thresholds are exceeded. The library is optimized for interactive text effects where letters can collapse under accumulated weight.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @blorkfield/overlay-core
9
+ ```
10
+
11
+ Or with pnpm:
12
+
13
+ ```bash
14
+ pnpm add @blorkfield/overlay-core
15
+ ```
16
+
17
+ ## Core Concepts
18
+
19
+ ### The Scene
20
+
21
+ `OverlayScene` is the central class that manages the physics simulation, rendering, and object lifecycle. It wraps Matter.js to provide a simplified API for common interactive scenarios.
22
+
23
+ ### Tag Based Behavior
24
+
25
+ Objects don't have fixed types. Instead, their behavior is determined by string tags:
26
+
27
+ | Tag | Behavior |
28
+ |-----|----------|
29
+ | `falling` | Object is dynamic and affected by gravity |
30
+ | `follow` | Object follows mouse position when grounded |
31
+ | `grabable` | Object can be dragged via mouse constraint |
32
+
33
+ Without the `falling` tag, objects are static and won't move.
34
+
35
+ ### Pressure System
36
+
37
+ Static obstacles track how many dynamic objects are resting on them. When the accumulated pressure reaches a threshold, the obstacle collapses (becomes dynamic and falls). You can configure:
38
+
39
+ | Option | Description |
40
+ |--------|-------------|
41
+ | Per letter thresholds | Each letter collapses independently |
42
+ | Word collapse mode | All letters in a word collapse together |
43
+ | Weighted pressure | Objects contribute configurable weight values |
44
+ | Shadows | Leave a faded copy behind when collapsing |
45
+ | Click to fall | Collapse after being clicked a specified number of times |
46
+
47
+ ### Floor Segments
48
+
49
+ The floor can be divided into independent segments, each with its own pressure threshold. When a segment receives too much weight, it collapses and objects fall through.
50
+
51
+ ## Quick Start
52
+
53
+ ```typescript
54
+ import { OverlayScene } from '@blorkfield/overlay-core';
55
+
56
+ // Create container and canvas
57
+ const container = document.getElementById('container');
58
+ const { canvas, bounds } = OverlayScene.createContainer(container, {
59
+ fullscreen: true
60
+ });
61
+
62
+ // Create scene
63
+ const scene = new OverlayScene(canvas, {
64
+ bounds,
65
+ gravity: 1,
66
+ wrapHorizontal: true,
67
+ background: 'transparent'
68
+ });
69
+
70
+ scene.start();
71
+ ```
72
+
73
+ ## Spawning Objects
74
+
75
+ ### Basic Shapes
76
+
77
+ ```typescript
78
+ // Circle (dynamic, falls with gravity)
79
+ scene.spawnObject({
80
+ x: 100,
81
+ y: 50,
82
+ radius: 20,
83
+ fillStyle: '#ff0000',
84
+ tags: ['falling']
85
+ });
86
+
87
+ // Rectangle (static, doesn't move)
88
+ scene.spawnObject({
89
+ x: 200,
90
+ y: 300,
91
+ width: 100,
92
+ height: 20,
93
+ fillStyle: '#0000ff'
94
+ });
95
+
96
+ // Polygon shapes
97
+ scene.spawnObject({
98
+ x: 150,
99
+ y: 100,
100
+ radius: 25,
101
+ fillStyle: '#00ff00',
102
+ tags: ['falling'],
103
+ shape: { type: 'hexagon' }
104
+ });
105
+ ```
106
+
107
+ ### Image Based Objects
108
+
109
+ When you provide an `imageUrl`, the library extracts the shape from the image's alpha channel for accurate collision detection.
110
+
111
+ ```typescript
112
+ const id = await scene.spawnObjectAsync({
113
+ x: 150,
114
+ y: 100,
115
+ imageUrl: '/images/coin.png',
116
+ size: 50,
117
+ tags: ['falling', 'grabable']
118
+ });
119
+ ```
120
+
121
+ ## Text Obstacles
122
+
123
+ ### PNG Based Text
124
+
125
+ Uses individual letter images stored in a fonts directory. Each character's collision shape is extracted from its PNG.
126
+
127
+ ```typescript
128
+ await scene.initializeFonts('/fonts/');
129
+
130
+ const result = await scene.addTextObstacles({
131
+ text: 'Hello World',
132
+ x: 100,
133
+ y: 200,
134
+ letterSize: 48,
135
+ fontName: 'handwritten',
136
+ letterColor: '#ff00ff',
137
+ pressureThreshold: { value: 5 },
138
+ weight: { value: 2 },
139
+ shadow: { opacity: 0.3 },
140
+ clickToFall: { clicks: 2 }
141
+ });
142
+
143
+ // Access created elements
144
+ console.log(result.stringTag); // Tag for entire string
145
+ console.log(result.wordTags); // Tags for each word
146
+ console.log(result.letterIds); // Individual letter IDs
147
+ ```
148
+
149
+ ### TTF Font Text
150
+
151
+ Renders text using TrueType/OpenType fonts with proper kerning and glyph outlines for collision.
152
+
153
+ ```typescript
154
+ const result = await scene.addTTFTextObstacles({
155
+ text: 'Build Stuff',
156
+ x: 100,
157
+ y: 200,
158
+ fontSize: 40,
159
+ fontUrl: '/fonts/Roboto/static/Roboto-Regular.ttf',
160
+ fillColor: '#ffffff',
161
+ pressureThreshold: { value: 10 },
162
+ clickToFall: { clicks: 3 }
163
+ });
164
+ ```
165
+
166
+ ## Effects
167
+
168
+ Effects are persistent spawning mechanisms that create objects over time.
169
+
170
+ ### Rain Effect
171
+
172
+ Objects fall continuously from the top of the scene.
173
+
174
+ ```typescript
175
+ scene.setEffect({
176
+ id: 'my-rain',
177
+ type: 'rain',
178
+ enabled: true,
179
+ spawnRate: 5,
180
+ spawnWidth: 0.8,
181
+ objectConfigs: [{
182
+ objectConfig: {
183
+ radius: 15,
184
+ fillStyle: '#4a90d9',
185
+ tags: ['falling']
186
+ },
187
+ probability: 1,
188
+ minScale: 0.8,
189
+ maxScale: 1.2,
190
+ baseRadius: 15
191
+ }]
192
+ });
193
+ ```
194
+
195
+ ### Burst Effect
196
+
197
+ Objects explode outward from a point at intervals.
198
+
199
+ ```typescript
200
+ scene.setEffect({
201
+ id: 'my-burst',
202
+ type: 'burst',
203
+ enabled: true,
204
+ burstInterval: 2000,
205
+ burstCount: 8,
206
+ burstForce: 15,
207
+ origin: { x: 400, y: 300 },
208
+ objectConfigs: [/* ... */]
209
+ });
210
+ ```
211
+
212
+ ### Stream Effect
213
+
214
+ Objects emit from a point in a specific direction with cone spread.
215
+
216
+ ```typescript
217
+ scene.setEffect({
218
+ id: 'my-stream',
219
+ type: 'stream',
220
+ enabled: true,
221
+ origin: { x: 0, y: 0 },
222
+ direction: { x: 1, y: 1 },
223
+ spawnRate: 10,
224
+ force: 15,
225
+ coneAngle: Math.PI / 8,
226
+ objectConfigs: [/* ... */]
227
+ });
228
+ ```
229
+
230
+ ## Managing Objects
231
+
232
+ ```typescript
233
+ // Release objects (make them fall)
234
+ scene.releaseObject(id);
235
+ scene.releaseObjectsByTag('my-text');
236
+ scene.releaseAllObjects();
237
+
238
+ // Remove objects
239
+ scene.removeObject(id);
240
+ scene.removeObjectsByTag('welcome-text');
241
+ scene.removeAllObjects();
242
+
243
+ // Add or remove tags
244
+ scene.addTag(id, 'falling');
245
+ scene.removeTag(id, 'grabable');
246
+
247
+ // Get object info
248
+ const ids = scene.getObjectIds();
249
+ const tagged = scene.getObjectIdsByTag('falling');
250
+ const allTags = scene.getAllTags();
251
+ ```
252
+
253
+ ## Configuration
254
+
255
+ ### Scene Config
256
+
257
+ ```typescript
258
+ const scene = new OverlayScene(canvas, {
259
+ bounds: { top: 0, bottom: 600, left: 0, right: 800 },
260
+ gravity: 1,
261
+ wrapHorizontal: true,
262
+ debug: false,
263
+ background: '#16213e',
264
+ floorConfig: {
265
+ segments: 10,
266
+ threshold: 100
267
+ },
268
+ despawnBelowFloor: 1.0
269
+ });
270
+ ```
271
+
272
+ | Option | Default | Description |
273
+ |--------|---------|-------------|
274
+ | `gravity` | 1 | Gravity strength |
275
+ | `wrapHorizontal` | true | Objects wrap around screen edges |
276
+ | `debug` | false | Show collision wireframes |
277
+ | `background` | transparent | Canvas background color |
278
+ | `floorConfig.segments` | 1 | Number of floor segments |
279
+ | `floorConfig.threshold` | none | Pressure threshold for floor collapse |
280
+ | `despawnBelowFloor` | 1.0 | Distance below floor to despawn objects (as fraction of height) |
281
+
282
+ ## Pressure Tracking
283
+
284
+ ```typescript
285
+ // Get pressure on a specific obstacle
286
+ const pressure = scene.getPressure(obstacleId);
287
+
288
+ // Get IDs of objects resting on an obstacle
289
+ const restingIds = scene.getObjectsRestingOn(obstacleId);
290
+
291
+ // Get all obstacles with pressure
292
+ const allPressure = scene.getAllPressure();
293
+
294
+ // Debug summary
295
+ const summary = scene.getPressureSummary();
296
+ ```
297
+
298
+ ## Update Callbacks
299
+
300
+ ```typescript
301
+ scene.onUpdate((data) => {
302
+ // data.objects contains all dynamic (falling) objects
303
+ for (const obj of data.objects) {
304
+ console.log(obj.id, obj.x, obj.y, obj.angle, obj.tags);
305
+ }
306
+ });
307
+ ```
308
+
309
+ ## Font Setup
310
+
311
+ ### PNG Based Fonts
312
+
313
+ Create a directory structure under `/public/fonts/`:
314
+
315
+ ```
316
+ public/
317
+ fonts/
318
+ fonts.json
319
+ handwritten/
320
+ A.png
321
+ B.png
322
+ ...
323
+ block/
324
+ A.png
325
+ a.png
326
+ ...
327
+ ```
328
+
329
+ The `fonts.json` manifest:
330
+
331
+ ```json
332
+ {
333
+ "fonts": [
334
+ {
335
+ "name": "handwritten",
336
+ "type": "png",
337
+ "characters": "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
338
+ },
339
+ {
340
+ "name": "block",
341
+ "type": "png",
342
+ "characters": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
343
+ }
344
+ ]
345
+ }
346
+ ```
347
+
348
+ ### TTF Fonts
349
+
350
+ ```json
351
+ {
352
+ "fonts": [
353
+ {
354
+ "name": "Roboto",
355
+ "type": "ttf",
356
+ "characters": "*",
357
+ "fontUrl": "/fonts/Roboto/static/Roboto-Regular.ttf"
358
+ }
359
+ ]
360
+ }
361
+ ```
362
+
363
+ ## Logging
364
+
365
+ ```typescript
366
+ import { setLogLevel, getLogLevel } from '@blorkfield/overlay-core';
367
+
368
+ setLogLevel('debug'); // Options: debug, info, warn, error
369
+ ```
370
+
371
+ ## Lifecycle
372
+
373
+ ```typescript
374
+ scene.start(); // Start simulation
375
+ scene.stop(); // Pause simulation
376
+ scene.resize(w, h); // Resize canvas and bounds
377
+ scene.setDebug(true); // Toggle wireframe mode
378
+ scene.destroy(); // Clean up resources
379
+ ```
380
+
381
+ ## Examples
382
+
383
+ Working examples are provided in the `/examples` directory:
384
+
385
+ | Example | Description |
386
+ |---------|-------------|
387
+ | [examples/astro](./examples/astro) | Basic integration with Astro |
388
+ | [examples/astro-svelte](./examples/astro-svelte) | Using Svelte components within Astro |
389
+
390
+ ## Dependencies
391
+
392
+ | Package | Purpose |
393
+ |---------|---------|
394
+ | matter-js | Physics engine for collision, gravity, and forces |
395
+ | opentype.js | TTF/OTF font parsing for glyph extraction |
396
+
397
+ ## TypeScript
398
+
399
+ The package is written in TypeScript and ships with full type definitions. All configuration interfaces are exported:
400
+
401
+ ```typescript
402
+ import type {
403
+ OverlaySceneConfig,
404
+ ObjectConfig,
405
+ TextObstacleConfig,
406
+ TTFTextObstacleConfig,
407
+ EffectConfig,
408
+ BurstEffectConfig,
409
+ RainEffectConfig,
410
+ StreamEffectConfig,
411
+ PressureThresholdConfig,
412
+ WeightConfig,
413
+ ShadowConfig,
414
+ ClickToFallConfig,
415
+ FloorConfig
416
+ } from '@blorkfield/overlay-core';
417
+ ```