@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 +417 -0
- package/dist/index.cjs +2519 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +700 -0
- package/dist/index.d.ts +700 -0
- package/dist/index.js +2474 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
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
|
+
```
|