castle-web-cli 0.4.84 → 0.4.85
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/dist/ide.js +35 -13
- package/dist/shell/assets/{index-BOgm5T3W.js → index-BJLaUTJE.js} +21 -21
- package/dist/shell/index.html +1 -1
- package/kits/physics-2d/.prettierrc +8 -0
- package/kits/physics-2d/CLAUDE.md +329 -0
- package/kits/physics-2d/behaviors/Camera.jsx +43 -0
- package/kits/physics-2d/behaviors/Collider.jsx +199 -0
- package/kits/physics-2d/behaviors/Goal.jsx +29 -0
- package/kits/physics-2d/behaviors/Layout.jsx +53 -0
- package/kits/physics-2d/behaviors/Sprite.jsx +352 -0
- package/kits/physics-2d/behaviors/tint.js +47 -0
- package/kits/physics-2d/blueprints/ball.scene +14 -0
- package/kits/physics-2d/blueprints/block.scene +12 -0
- package/kits/physics-2d/blueprints/cauldron.scene +18 -0
- package/kits/physics-2d/blueprints/crate.scene +14 -0
- package/kits/physics-2d/blueprints/goal.scene +12 -0
- package/kits/physics-2d/castle.json +13 -0
- package/kits/physics-2d/docs/pxart-format.md +377 -0
- package/kits/physics-2d/drawings/block.pxart +25 -0
- package/kits/physics-2d/drawings/cauldron.pxart +113 -0
- package/kits/physics-2d/editors/BlueprintLibrary.jsx +247 -0
- package/kits/physics-2d/editors/ErrorBoundary.jsx +59 -0
- package/kits/physics-2d/editors/PlayOnly.jsx +31 -0
- package/kits/physics-2d/editors/PxArtEditor.jsx +954 -0
- package/kits/physics-2d/editors/SceneEditor.jsx +1681 -0
- package/kits/physics-2d/editors/SelectionOverlay.jsx +909 -0
- package/kits/physics-2d/editors/SingleEditor.jsx +122 -0
- package/kits/physics-2d/editors/behaviorRegistry.js +30 -0
- package/kits/physics-2d/editors/editorHistory.js +157 -0
- package/kits/physics-2d/editors/inspectorSheet.js +13 -0
- package/kits/physics-2d/editors/pixelCanvas.js +11 -0
- package/kits/physics-2d/editors/pixelEditorChrome.jsx +74 -0
- package/kits/physics-2d/editors/pixelGeometry.js +140 -0
- package/kits/physics-2d/editors/pixelInspector.jsx +633 -0
- package/kits/physics-2d/editors/pxArtEditorModel.js +732 -0
- package/kits/physics-2d/editors/pxArtPlayback.js +92 -0
- package/kits/physics-2d/editors/pxArtTimeline.jsx +752 -0
- package/kits/physics-2d/editors/pxArtTimeline.module.css +506 -0
- package/kits/physics-2d/editors/pxArtTools.js +232 -0
- package/kits/physics-2d/editors/useArtboardFit.js +102 -0
- package/kits/physics-2d/engine/ScenePlayer.jsx +196 -0
- package/kits/physics-2d/engine/SceneUI.jsx +59 -0
- package/kits/physics-2d/engine/assets.js +15 -0
- package/kits/physics-2d/engine/autoInspector.jsx +70 -0
- package/kits/physics-2d/engine/blueprint.js +521 -0
- package/kits/physics-2d/engine/collider.js +196 -0
- package/kits/physics-2d/engine/files.js +117 -0
- package/kits/physics-2d/engine/liveReload.js +88 -0
- package/kits/physics-2d/engine/pxart.js +1032 -0
- package/kits/physics-2d/engine/pxartSmooth.js +222 -0
- package/kits/physics-2d/engine/scene.js +686 -0
- package/kits/physics-2d/engine/spriteGeometry.js +32 -0
- package/kits/physics-2d/engine/ui.jsx +688 -0
- package/kits/physics-2d/engine/ui.module.css +2287 -0
- package/kits/physics-2d/eslint.config.js +71 -0
- package/kits/physics-2d/index.html +24 -0
- package/kits/physics-2d/main.jsx +24 -0
- package/kits/physics-2d/package-lock.json +2706 -0
- package/kits/physics-2d/package.json +42 -0
- package/kits/physics-2d/physics/PhysicsSystem.js +290 -0
- package/kits/physics-2d/physics/behaviors/AnalogStick.jsx +101 -0
- package/kits/physics-2d/physics/behaviors/Draggable.jsx +79 -0
- package/kits/physics-2d/physics/behaviors/RigidBody.jsx +55 -0
- package/kits/physics-2d/physics/behaviors/Slingshot.jsx +118 -0
- package/kits/physics-2d/physics/controls.js +79 -0
- package/kits/physics-2d/physics/index.js +26 -0
- package/kits/physics-2d/physics/matterBridge.js +126 -0
- package/kits/physics-2d/pnpm-lock.yaml +1761 -0
- package/kits/physics-2d/scenes/main.scene +12 -0
- package/kits/physics-2d/scenes/sandbox.scene +13 -0
- package/kits/physics-2d/scripts/draw.mjs +121 -0
- package/kits/physics-2d/vite.config.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Pure control math shared by the touch-first physics control behaviors
|
|
2
|
+
// (Draggable, Slingshot, AnalogStick). No React or DOM here, so it stays simple
|
|
3
|
+
// to reason about and unit-testable outside a browser. The .jsx behaviors are
|
|
4
|
+
// thin wrappers: read input + runtime state, call these, push the result
|
|
5
|
+
// through scene.physics. (Controls are touch/pointer-first; keyboard, when
|
|
6
|
+
// added, should only DUPLICATE an on-screen control, never be the only input.)
|
|
7
|
+
|
|
8
|
+
export function centerOf(layout) {
|
|
9
|
+
return { x: layout.x + layout.width / 2, y: layout.y + layout.height / 2 };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function length(v) {
|
|
13
|
+
return Math.hypot(v.x, v.y);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Clamp a vector's magnitude to `max` (keeps direction).
|
|
17
|
+
export function clampLength(v, max) {
|
|
18
|
+
const len = length(v);
|
|
19
|
+
if (len <= max || len === 0) return { x: v.x, y: v.y };
|
|
20
|
+
const s = max / len;
|
|
21
|
+
return { x: v.x * s, y: v.y * s };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Springy chase velocity toward a target point (Draggable): a fraction of the
|
|
25
|
+
// gap per step, so the body accelerates toward the pointer and still collides.
|
|
26
|
+
export function chaseVelocity(target, layout, props = {}) {
|
|
27
|
+
const stiffness = props.stiffness ?? 0.5;
|
|
28
|
+
const c = centerOf(layout);
|
|
29
|
+
return { x: (target.x - c.x) * stiffness, y: (target.y - c.y) * stiffness };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Run `draw(ctx)` in world space, undoing the actor's Layout rotation that the
|
|
33
|
+
// runtime applies about the actor's center. Overlay controls (Slingshot,
|
|
34
|
+
// AnalogStick) use this so their on-screen UI stays anchored where the user
|
|
35
|
+
// pressed instead of rotating/sliding with the actor.
|
|
36
|
+
export function inActorWorldSpace(ctx, layout, draw) {
|
|
37
|
+
const rot = layout?.rotation ?? 0;
|
|
38
|
+
ctx.save();
|
|
39
|
+
if (rot) {
|
|
40
|
+
const cx = layout.x + layout.width / 2;
|
|
41
|
+
const cy = layout.y + layout.height / 2;
|
|
42
|
+
ctx.translate(cx, cy);
|
|
43
|
+
ctx.rotate((-rot * Math.PI) / 180);
|
|
44
|
+
ctx.translate(-cx, -cy);
|
|
45
|
+
}
|
|
46
|
+
draw(ctx);
|
|
47
|
+
ctx.restore();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Slingshot: how far the pointer is pulled from the anchor, clamped to maxDrag.
|
|
51
|
+
// This is the "pull" vector; the launch flings the opposite way.
|
|
52
|
+
export function slingPull(anchor, pointer, maxDrag = 160) {
|
|
53
|
+
return clampLength({ x: pointer.x - anchor.x, y: pointer.y - anchor.y }, maxDrag);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Launch velocity when the slingshot is released: opposite the pull, scaled.
|
|
57
|
+
export function slingLaunch(anchor, pointer, props = {}) {
|
|
58
|
+
const speed = props.speed ?? 0.08;
|
|
59
|
+
const pull = slingPull(anchor, pointer, props.maxDrag ?? 160);
|
|
60
|
+
return { x: -pull.x * speed, y: -pull.y * speed };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// AnalogStick: stick displacement from its origin, clamped to maxRadius.
|
|
64
|
+
export function stickVector(pointer, origin, maxRadius = 60) {
|
|
65
|
+
return clampLength({ x: pointer.x - origin.x, y: pointer.y - origin.y }, maxRadius);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Velocity a virtual stick commands: proportional to displacement/maxRadius,
|
|
69
|
+
// up to `speed`, restricted to the enabled axes.
|
|
70
|
+
export function analogVelocity(pointer, origin, props = {}) {
|
|
71
|
+
const speed = props.speed ?? 6;
|
|
72
|
+
const maxRadius = props.maxRadius ?? 60;
|
|
73
|
+
const axes = props.axes ?? 'both';
|
|
74
|
+
const v = stickVector(pointer, origin, maxRadius);
|
|
75
|
+
const out = { x: (v.x / maxRadius) * speed, y: (v.y / maxRadius) * speed };
|
|
76
|
+
if (axes === 'horizontal') out.y = 0;
|
|
77
|
+
if (axes === 'vertical') out.x = 0;
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Entry point for the self-contained physics module.
|
|
2
|
+
//
|
|
3
|
+
// To add physics to another kit:
|
|
4
|
+
// 1. Copy this `physics/` folder into the kit.
|
|
5
|
+
// 2. Add `matter-js` to the kit's package.json dependencies.
|
|
6
|
+
// 3. In the kit's engine/scene.js: add a systems registry (`this.systems`,
|
|
7
|
+
// `registerSystem`, and an `afterBehaviors` call in `update`), then call
|
|
8
|
+
// `installPhysics(runtime)` from `makeScene`.
|
|
9
|
+
// 4. In the kit's editors/behaviorRegistry, also glob `../physics/behaviors/*.jsx`.
|
|
10
|
+
//
|
|
11
|
+
// The behaviors themselves (RigidBody, Collider extensions, controls) live in
|
|
12
|
+
// `physics/behaviors/` and are auto-discovered by the registry.
|
|
13
|
+
|
|
14
|
+
import { PhysicsSystem } from './PhysicsSystem';
|
|
15
|
+
|
|
16
|
+
// Register a physics system on a runtime and wire up `runtime.physics`.
|
|
17
|
+
// Returns the system. Cheap to call on every runtime -- the matter engine is
|
|
18
|
+
// created lazily on the first simulation step.
|
|
19
|
+
export function installPhysics(runtime) {
|
|
20
|
+
const system = new PhysicsSystem();
|
|
21
|
+
runtime.registerSystem(system);
|
|
22
|
+
system.attach(runtime);
|
|
23
|
+
return system;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { PhysicsSystem };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Pure helpers bridging Cauldron actors (Layout + Collider + RigidBody
|
|
2
|
+
// components) to matter-js bodies. No engine state lives here -- the
|
|
3
|
+
// PhysicsSystem owns the world and calls these, feeding in the collider rect it
|
|
4
|
+
// gets from the kit's engine/collider.js (the single source of truth for
|
|
5
|
+
// collider geometry, including sprite-fit `auto` mode).
|
|
6
|
+
//
|
|
7
|
+
// Coordinate systems:
|
|
8
|
+
// - Cauldron Layout: top-left origin, +y down, `rotation` in degrees CW.
|
|
9
|
+
// - matter-js Body: centroid origin, +y down, `angle` in radians.
|
|
10
|
+
// Rotation converts by DEG<->RAD; position via the body's cached center-offset
|
|
11
|
+
// from Layout's top-left (see PhysicsSystem).
|
|
12
|
+
//
|
|
13
|
+
// matter-js unit notes (see Body defaults): `frictionAir` is linear damping,
|
|
14
|
+
// `restitution` is bounciness, `friction` is contact friction. There is no
|
|
15
|
+
// native per-body gravity multiplier and no kinematic body type -- both are
|
|
16
|
+
// emulated here (applyWorldGravity, and kinematic == static-moved-from-Layout).
|
|
17
|
+
|
|
18
|
+
import Matter from 'matter-js';
|
|
19
|
+
|
|
20
|
+
export const DEG_TO_RAD = Math.PI / 180;
|
|
21
|
+
export const RAD_TO_DEG = 180 / Math.PI;
|
|
22
|
+
|
|
23
|
+
// Fixed simulation step. matter-js is tuned for a constant delta in
|
|
24
|
+
// milliseconds and pixel-space coordinates -- our card is already in pixels.
|
|
25
|
+
export const FIXED_STEP_MS = 1000 / 60;
|
|
26
|
+
export const FIXED_STEP_S = FIXED_STEP_MS / 1000;
|
|
27
|
+
export const MAX_STEPS_PER_FRAME = 5;
|
|
28
|
+
|
|
29
|
+
// World gravity in matter's own units. `scale` matches matter's internal
|
|
30
|
+
// default; `y` is what a scene tunes (1 ~= a gentle arcade fall).
|
|
31
|
+
export const DEFAULT_GRAVITY = { x: 0, y: 1, scale: 0.001 };
|
|
32
|
+
|
|
33
|
+
// An actor participates in physics if it has a Collider (+ Layout). A RigidBody
|
|
34
|
+
// makes it move (dynamic/kinematic); a Collider alone is a static obstacle.
|
|
35
|
+
export function isPhysicsActor(actor) {
|
|
36
|
+
return Boolean(actor?.components?.Collider && actor?.components?.Layout);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function rigidBodyType(actor) {
|
|
40
|
+
const rb = actor?.components?.RigidBody;
|
|
41
|
+
if (!rb) return 'static';
|
|
42
|
+
return rb.bodyType ?? 'dynamic';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function rectCenter(rect) {
|
|
46
|
+
return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function bodyRadius(actor, rect) {
|
|
50
|
+
// `radius` of 0 (the default) means "derive from the collider size" -- note
|
|
51
|
+
// `??` won't do here since 0 is a real number; a 0 radius would be a
|
|
52
|
+
// degenerate, zero-mass body that NaNs the simulation.
|
|
53
|
+
const r = actor.components.Collider.radius;
|
|
54
|
+
return r && r > 0 ? r : Math.min(rect.width, rect.height) / 2;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Structural signature: when this changes we rebuild the body (shape/size/type)
|
|
58
|
+
// rather than live-patching material props in place.
|
|
59
|
+
export function bodySignature(actor, rect) {
|
|
60
|
+
const shape = actor.components.Collider.shape ?? 'box';
|
|
61
|
+
const dims =
|
|
62
|
+
shape === 'circle'
|
|
63
|
+
? `r${Math.round(bodyRadius(actor, rect))}`
|
|
64
|
+
: `${Math.round(rect.width)}x${Math.round(rect.height)}`;
|
|
65
|
+
return `${shape}|${dims}|${rigidBodyType(actor)}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Build a fresh matter body at the collider rect's center, oriented by Layout.
|
|
69
|
+
export function createBody(actor, rect) {
|
|
70
|
+
const center = rectCenter(rect);
|
|
71
|
+
const options = { angle: (actor.components.Layout.rotation ?? 0) * DEG_TO_RAD };
|
|
72
|
+
const body =
|
|
73
|
+
(actor.components.Collider.shape ?? 'box') === 'circle'
|
|
74
|
+
? Matter.Bodies.circle(center.x, center.y, bodyRadius(actor, rect), options)
|
|
75
|
+
: Matter.Bodies.rectangle(center.x, center.y, rect.width, rect.height, options);
|
|
76
|
+
body.plugin.actorId = actor.id;
|
|
77
|
+
Matter.Body.setStatic(body, rigidBodyType(actor) !== 'dynamic');
|
|
78
|
+
patchBody(body, actor);
|
|
79
|
+
const rb = actor.components.RigidBody;
|
|
80
|
+
if (rb && rigidBodyType(actor) === 'dynamic' && (rb.velocityX || rb.velocityY)) {
|
|
81
|
+
Matter.Body.setVelocity(body, { x: rb.velocityX ?? 0, y: rb.velocityY ?? 0 });
|
|
82
|
+
}
|
|
83
|
+
return body;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Live-patch the mutable material/motion props (safe to call every step).
|
|
87
|
+
export function patchBody(body, actor) {
|
|
88
|
+
const collider = actor.components.Collider;
|
|
89
|
+
const rb = actor.components.RigidBody;
|
|
90
|
+
body.restitution = collider.bounciness ?? 0;
|
|
91
|
+
body.friction = collider.friction ?? 0.1;
|
|
92
|
+
// `isTrigger` is the source of truth for solid-vs-sensor; legacy decks that
|
|
93
|
+
// used the retired `kind: 'pickup'` label are still honored as sensors.
|
|
94
|
+
body.isSensor = Boolean(collider.isTrigger || collider.kind === 'pickup');
|
|
95
|
+
body.frictionAir = rb?.drag ?? 0.01;
|
|
96
|
+
body.plugin.gravityScale = rb?.gravityScale ?? 1;
|
|
97
|
+
body.plugin.angularDrag = rb?.angularDrag ?? 0;
|
|
98
|
+
if (rb?.freezeRotation) Matter.Body.setInertia(body, Infinity);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Push a static/kinematic body to a world point + Layout angle.
|
|
102
|
+
export function syncBodyToPoint(body, point, angleDeg) {
|
|
103
|
+
Matter.Body.setPosition(body, point);
|
|
104
|
+
Matter.Body.setAngle(body, (angleDeg ?? 0) * DEG_TO_RAD);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Emulated per-body gravity: replicate matter's own gravity math but multiplied
|
|
108
|
+
// by each body's gravityScale. The system zeroes engine gravity so this is the
|
|
109
|
+
// only source.
|
|
110
|
+
export function applyWorldGravity(bodies, gravity) {
|
|
111
|
+
for (const body of bodies) {
|
|
112
|
+
if (body.isStatic || body.isSleeping) continue;
|
|
113
|
+
const scale = (body.plugin.gravityScale ?? 1) * gravity.scale;
|
|
114
|
+
body.force.x += body.mass * gravity.x * scale;
|
|
115
|
+
body.force.y += body.mass * gravity.y * scale;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// matter damps angular velocity via frictionAir; `angularDrag` adds extra
|
|
120
|
+
// per-step damping on top for authors who want spin to settle faster.
|
|
121
|
+
export function applyAngularDrag(bodies) {
|
|
122
|
+
for (const body of bodies) {
|
|
123
|
+
const drag = body.plugin.angularDrag ?? 0;
|
|
124
|
+
if (drag > 0 && !body.isStatic) body.angularVelocity *= Math.max(0, 1 - drag * FIXED_STEP_S);
|
|
125
|
+
}
|
|
126
|
+
}
|