@mcptoolshop/physics-svg 0.1.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/LICENSE +21 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +179 -0
- package/dist/index.js +142 -0
- package/dist/react/index.cjs +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +107 -0
- package/dist/svg-renderer-BOSG5i9F.js +636 -0
- package/dist/svg-renderer-CiwhJO3-.cjs +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mcp-tool-shop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var h=Object.defineProperty;var l=(i,t,e)=>t in i?h(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var a=(i,t,e)=>l(i,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./svg-renderer-CiwhJO3-.cjs");class p{constructor(){a(this,"canvas",null);a(this,"ctx",null)}init(t){this.canvas=document.createElement("canvas"),this.canvas.width=t.clientWidth||800,this.canvas.height=t.clientHeight||600,this.ctx=this.canvas.getContext("2d"),t.appendChild(this.canvas)}render(t,e){if(!(!this.ctx||!this.canvas)){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);for(const s of t){switch(this.ctx.save(),this.ctx.translate(s.position.x,s.position.y),this.ctx.fillStyle="#6366f1",s.shape.type){case"circle":this.ctx.beginPath(),this.ctx.arc(0,0,s.shape.radius,0,Math.PI*2),this.ctx.fill();break;case"rect":this.ctx.fillRect(-s.shape.width/2,-s.shape.height/2,s.shape.width,s.shape.height);break;case"polygon":{const o=s.shape.vertices;if(o.length<2)break;this.ctx.beginPath(),this.ctx.moveTo(o[0].x,o[0].y);for(let n=1;n<o.length;n++)this.ctx.lineTo(o[n].x,o[n].y);this.ctx.closePath(),this.ctx.fill();break}}this.ctx.restore()}}}destroy(){var t;(t=this.canvas)==null||t.remove(),this.canvas=null,this.ctx=null}}class c{constructor(t,e){a(this,"currentState");a(this,"transitions");a(this,"listeners",[]);this.currentState=t,this.transitions=e}send(t){var o;const e=this.transitions.find(n=>n.from===this.currentState&&n.on===t&&(n.guard?n.guard():!0));if(!e)return;const s=this.currentState;this.currentState=e.to,(o=e.action)==null||o.call(e);for(const n of this.listeners)n(s,e.to)}getState(){return this.currentState}onTransition(t){this.listeners.push(t)}}function d(){const i=[{from:"idle",to:"picked-up",on:"GRAB"},{from:"picked-up",to:"moving",on:"MOVE"},{from:"moving",to:"dropping",on:"RELEASE"},{from:"dropping",to:"bouncing",on:"BOUNCE_START"},{from:"dropping",to:"settled",on:"SETTLE"},{from:"bouncing",to:"settled",on:"BOUNCE_END"},{from:"settled",to:"picked-up",on:"GRAB"}];return new c("idle",i)}function u(){const i=[{from:"reserve",to:"deploying",on:"DEPLOY"},{from:"deploying",to:"landing",on:"LAND"},{from:"landing",to:"settled",on:"SETTLE"}];return new c("reserve",i)}function f(i){typeof window>"u"||(window.__PHYSICS_SVG_DEVTOOLS_HOOK__=i)}class g{constructor(){a(this,"subscribers",[])}emit(t){for(const e of this.subscribers)e(t)}subscribe(t){return this.subscribers.push(t),()=>{const e=this.subscribers.indexOf(t);e!==-1&&this.subscribers.splice(e,1)}}}exports.PhysicsEngine=r.PhysicsEngine;exports.SvgRenderer=r.SvgRenderer;exports.Vec2=r.vec2;exports.applyAttraction=r.applyAttraction;exports.applyDrag=r.applyDrag;exports.applyForceFields=r.applyForceFields;exports.applyGravity=r.applyGravity;exports.applyWind=r.applyWind;exports.createBody=r.createBody;exports.createConstraint=r.createConstraint;exports.wakeBody=r.wakeBody;exports.AnimationEmitter=g;exports.CanvasRenderer=p;exports.StateMachine=c;exports.createDeployMachine=u;exports.createPickupDropMachine=d;exports.installDevToolsHook=f;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { BodyShape } from '@mcp-tool-shop/siege-types';
|
|
2
|
+
import { Constraint } from '@mcp-tool-shop/siege-types';
|
|
3
|
+
import { Context } from 'react';
|
|
4
|
+
import { ForceField } from '@mcp-tool-shop/siege-types';
|
|
5
|
+
import { JSX } from 'react/jsx-runtime';
|
|
6
|
+
import { PhysicsBody } from '@mcp-tool-shop/siege-types';
|
|
7
|
+
import { ReactNode } from 'react';
|
|
8
|
+
import { Vec2 } from '@mcp-tool-shop/siege-types';
|
|
9
|
+
import { WorldConfig } from '@mcp-tool-shop/siege-types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Body — declarative React component that registers a physics body with
|
|
13
|
+
* the engine on mount and removes it on unmount.
|
|
14
|
+
*
|
|
15
|
+
* Renders nothing to the DOM. Visual representation is handled entirely
|
|
16
|
+
* by the {@link SvgRenderer}.
|
|
17
|
+
*/
|
|
18
|
+
declare function Body_2(props: BodyProps): null;
|
|
19
|
+
export { Body_2 as Body }
|
|
20
|
+
|
|
21
|
+
/** Props for the {@link Body} component. */
|
|
22
|
+
export declare interface BodyProps {
|
|
23
|
+
/** Shape of the physics body. */
|
|
24
|
+
shape?: BodyShape;
|
|
25
|
+
/** Mass (kg). Default 1. */
|
|
26
|
+
mass?: number;
|
|
27
|
+
/** Initial position. */
|
|
28
|
+
position?: Vec2;
|
|
29
|
+
/** Initial velocity. */
|
|
30
|
+
velocity?: Vec2;
|
|
31
|
+
/** Coefficient of restitution (bounciness). 0-1. */
|
|
32
|
+
restitution?: number;
|
|
33
|
+
/** Friction coefficient. */
|
|
34
|
+
friction?: number;
|
|
35
|
+
/** If true, the body is immovable. */
|
|
36
|
+
isStatic?: boolean;
|
|
37
|
+
/** Arbitrary user data attached to the body. */
|
|
38
|
+
userData?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* React context that provides the {@link PhysicsEngine} instance to
|
|
43
|
+
* descendant components and hooks.
|
|
44
|
+
*/
|
|
45
|
+
export declare const PhysicsContext: Context<PhysicsEngine | null>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* PhysicsEngine — top-level facade for the physics-svg simulation.
|
|
49
|
+
*
|
|
50
|
+
* Features:
|
|
51
|
+
* - Fixed-timestep simulation with accumulator
|
|
52
|
+
* - Interpolated body positions for smooth rendering
|
|
53
|
+
* - Body and constraint management
|
|
54
|
+
* - Force field system
|
|
55
|
+
* - Sleep/wake management
|
|
56
|
+
*/
|
|
57
|
+
declare class PhysicsEngine {
|
|
58
|
+
private world;
|
|
59
|
+
private accumulator;
|
|
60
|
+
private _alpha;
|
|
61
|
+
constructor(config: WorldConfig);
|
|
62
|
+
/**
|
|
63
|
+
* Advance the simulation by `frameTime` seconds.
|
|
64
|
+
*
|
|
65
|
+
* Uses Glenn Fiedler's fixed-timestep-with-accumulator pattern:
|
|
66
|
+
* - Clamp frame time to prevent spiral of death
|
|
67
|
+
* - Step physics at fixed 60Hz intervals
|
|
68
|
+
* - Store interpolation alpha for smooth rendering
|
|
69
|
+
*/
|
|
70
|
+
update(frameTime: number): void;
|
|
71
|
+
/**
|
|
72
|
+
* Get the interpolation alpha for the current frame.
|
|
73
|
+
*
|
|
74
|
+
* Use this to blend between previousPosition and position:
|
|
75
|
+
* renderPos = lerp(body.previousPosition, body.position, alpha)
|
|
76
|
+
*/
|
|
77
|
+
get alpha(): number;
|
|
78
|
+
/**
|
|
79
|
+
* Get the interpolated render position for a body.
|
|
80
|
+
*/
|
|
81
|
+
getInterpolatedPosition(body: PhysicsBody): Vec2;
|
|
82
|
+
/** Register a body and return its id. */
|
|
83
|
+
addBody(body: PhysicsBody): string;
|
|
84
|
+
/** Remove a body by id. */
|
|
85
|
+
removeBody(id: string): void;
|
|
86
|
+
/** Look up a single body. */
|
|
87
|
+
getBody(id: string): PhysicsBody | undefined;
|
|
88
|
+
/** Return every body currently in the world. */
|
|
89
|
+
getBodies(): PhysicsBody[];
|
|
90
|
+
/** Apply an instantaneous impulse to a body. */
|
|
91
|
+
applyImpulse(id: string, impulse: Vec2): void;
|
|
92
|
+
/** Set a body's position directly (teleport). */
|
|
93
|
+
setPosition(id: string, position: Vec2): void;
|
|
94
|
+
/** Set a body's velocity directly. */
|
|
95
|
+
setVelocity(id: string, velocity: Vec2): void;
|
|
96
|
+
/** Register a constraint and return its id. */
|
|
97
|
+
addConstraint(constraint: Constraint): string;
|
|
98
|
+
/** Remove a constraint by id. */
|
|
99
|
+
removeConstraint(id: string): void;
|
|
100
|
+
/** Return every constraint currently in the world. */
|
|
101
|
+
getConstraints(): Constraint[];
|
|
102
|
+
/** Add a force field to the world. */
|
|
103
|
+
addForceField(field: ForceField): void;
|
|
104
|
+
/** Remove all force fields of a given type. */
|
|
105
|
+
removeForceFields(type: ForceField['type']): void;
|
|
106
|
+
/** Access the underlying world config. */
|
|
107
|
+
getConfig(): WorldConfig;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* PhysicsScene — root React component that owns the physics engine and SVG
|
|
112
|
+
* renderer.
|
|
113
|
+
*
|
|
114
|
+
* Provides the engine via React context so child components and hooks can
|
|
115
|
+
* register bodies and constraints.
|
|
116
|
+
*
|
|
117
|
+
* Runs a `requestAnimationFrame` loop that steps the simulation and renders
|
|
118
|
+
* each frame.
|
|
119
|
+
*/
|
|
120
|
+
export declare function PhysicsScene({ config, width, height, children, }: PhysicsSceneProps): JSX.Element;
|
|
121
|
+
|
|
122
|
+
/** Props for the {@link PhysicsScene} component. */
|
|
123
|
+
export declare interface PhysicsSceneProps {
|
|
124
|
+
/** World configuration for the physics engine. */
|
|
125
|
+
config?: Partial<WorldConfig>;
|
|
126
|
+
/** Width of the SVG viewport. */
|
|
127
|
+
width?: number | string;
|
|
128
|
+
/** Height of the SVG viewport. */
|
|
129
|
+
height?: number | string;
|
|
130
|
+
/** Child components (e.g. `<Body>`, `<Spring>`). */
|
|
131
|
+
children?: ReactNode;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Spring — declarative React component that registers a spring constraint
|
|
136
|
+
* between two bodies on mount and removes it on unmount.
|
|
137
|
+
*
|
|
138
|
+
* Renders nothing to the DOM.
|
|
139
|
+
*/
|
|
140
|
+
export declare function Spring(props: SpringProps): null;
|
|
141
|
+
|
|
142
|
+
/** Props for the {@link Spring} component. */
|
|
143
|
+
export declare interface SpringProps {
|
|
144
|
+
/** ID of the first body. */
|
|
145
|
+
bodyA: string;
|
|
146
|
+
/** ID of the second body. */
|
|
147
|
+
bodyB: string;
|
|
148
|
+
/** Spring stiffness. Default 0.5. */
|
|
149
|
+
stiffness?: number;
|
|
150
|
+
/** Damping coefficient. Default 0.1. */
|
|
151
|
+
damping?: number;
|
|
152
|
+
/** Anchor point on body A (local coords). */
|
|
153
|
+
anchorA?: Vec2;
|
|
154
|
+
/** Anchor point on body B (local coords). */
|
|
155
|
+
anchorB?: Vec2;
|
|
156
|
+
/** Rest length. If omitted, computed from initial body positions. */
|
|
157
|
+
length?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Hook to create and manage a physics body imperatively.
|
|
162
|
+
*
|
|
163
|
+
* The body is added to the engine on mount and removed on unmount.
|
|
164
|
+
*
|
|
165
|
+
* @param config - Partial body configuration.
|
|
166
|
+
* @returns The created {@link PhysicsBody} (stable reference).
|
|
167
|
+
*/
|
|
168
|
+
export declare function useBody(config: Partial<PhysicsBody>): PhysicsBody;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Hook to access the nearest {@link PhysicsEngine} from context.
|
|
172
|
+
*
|
|
173
|
+
* Must be used inside a `<PhysicsScene>` provider.
|
|
174
|
+
*
|
|
175
|
+
* @throws If no PhysicsEngine is found in context.
|
|
176
|
+
*/
|
|
177
|
+
export declare function usePhysics(): PhysicsEngine;
|
|
178
|
+
|
|
179
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
var c = Object.defineProperty;
|
|
2
|
+
var h = (e, t, s) => t in e ? c(e, t, { enumerable: !0, configurable: !0, writable: !0, value: s }) : e[t] = s;
|
|
3
|
+
var o = (e, t, s) => h(e, typeof t != "symbol" ? t + "" : t, s);
|
|
4
|
+
import { P as b, S as m, v as y, a as S, b as E, c as w, d as T, e as k, f as O, g as P, w as _ } from "./svg-renderer-BOSG5i9F.js";
|
|
5
|
+
class d {
|
|
6
|
+
constructor() {
|
|
7
|
+
o(this, "canvas", null);
|
|
8
|
+
o(this, "ctx", null);
|
|
9
|
+
}
|
|
10
|
+
init(t) {
|
|
11
|
+
this.canvas = document.createElement("canvas"), this.canvas.width = t.clientWidth || 800, this.canvas.height = t.clientHeight || 600, this.ctx = this.canvas.getContext("2d"), t.appendChild(this.canvas);
|
|
12
|
+
}
|
|
13
|
+
render(t, s) {
|
|
14
|
+
if (!(!this.ctx || !this.canvas)) {
|
|
15
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
16
|
+
for (const i of t) {
|
|
17
|
+
switch (this.ctx.save(), this.ctx.translate(i.position.x, i.position.y), this.ctx.fillStyle = "#6366f1", i.shape.type) {
|
|
18
|
+
case "circle":
|
|
19
|
+
this.ctx.beginPath(), this.ctx.arc(0, 0, i.shape.radius, 0, Math.PI * 2), this.ctx.fill();
|
|
20
|
+
break;
|
|
21
|
+
case "rect":
|
|
22
|
+
this.ctx.fillRect(
|
|
23
|
+
-i.shape.width / 2,
|
|
24
|
+
-i.shape.height / 2,
|
|
25
|
+
i.shape.width,
|
|
26
|
+
i.shape.height
|
|
27
|
+
);
|
|
28
|
+
break;
|
|
29
|
+
case "polygon": {
|
|
30
|
+
const r = i.shape.vertices;
|
|
31
|
+
if (r.length < 2) break;
|
|
32
|
+
this.ctx.beginPath(), this.ctx.moveTo(r[0].x, r[0].y);
|
|
33
|
+
for (let n = 1; n < r.length; n++)
|
|
34
|
+
this.ctx.lineTo(r[n].x, r[n].y);
|
|
35
|
+
this.ctx.closePath(), this.ctx.fill();
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
this.ctx.restore();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
destroy() {
|
|
44
|
+
var t;
|
|
45
|
+
(t = this.canvas) == null || t.remove(), this.canvas = null, this.ctx = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
class a {
|
|
49
|
+
constructor(t, s) {
|
|
50
|
+
o(this, "currentState");
|
|
51
|
+
o(this, "transitions");
|
|
52
|
+
o(this, "listeners", []);
|
|
53
|
+
this.currentState = t, this.transitions = s;
|
|
54
|
+
}
|
|
55
|
+
/** Send an event to the machine. If a valid transition matches, it fires. */
|
|
56
|
+
send(t) {
|
|
57
|
+
var r;
|
|
58
|
+
const s = this.transitions.find(
|
|
59
|
+
(n) => n.from === this.currentState && n.on === t && (n.guard ? n.guard() : !0)
|
|
60
|
+
);
|
|
61
|
+
if (!s) return;
|
|
62
|
+
const i = this.currentState;
|
|
63
|
+
this.currentState = s.to, (r = s.action) == null || r.call(s);
|
|
64
|
+
for (const n of this.listeners)
|
|
65
|
+
n(i, s.to);
|
|
66
|
+
}
|
|
67
|
+
/** Get the current state. */
|
|
68
|
+
getState() {
|
|
69
|
+
return this.currentState;
|
|
70
|
+
}
|
|
71
|
+
/** Register a callback invoked on every successful transition. */
|
|
72
|
+
onTransition(t) {
|
|
73
|
+
this.listeners.push(t);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function p() {
|
|
77
|
+
const e = [
|
|
78
|
+
{ from: "idle", to: "picked-up", on: "GRAB" },
|
|
79
|
+
{ from: "picked-up", to: "moving", on: "MOVE" },
|
|
80
|
+
{ from: "moving", to: "dropping", on: "RELEASE" },
|
|
81
|
+
{ from: "dropping", to: "bouncing", on: "BOUNCE_START" },
|
|
82
|
+
{ from: "dropping", to: "settled", on: "SETTLE" },
|
|
83
|
+
{ from: "bouncing", to: "settled", on: "BOUNCE_END" },
|
|
84
|
+
// Allow re-grab from settled
|
|
85
|
+
{ from: "settled", to: "picked-up", on: "GRAB" }
|
|
86
|
+
];
|
|
87
|
+
return new a(
|
|
88
|
+
"idle",
|
|
89
|
+
e
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
function u() {
|
|
93
|
+
const e = [
|
|
94
|
+
{ from: "reserve", to: "deploying", on: "DEPLOY" },
|
|
95
|
+
{ from: "deploying", to: "landing", on: "LAND" },
|
|
96
|
+
{ from: "landing", to: "settled", on: "SETTLE" }
|
|
97
|
+
];
|
|
98
|
+
return new a("reserve", e);
|
|
99
|
+
}
|
|
100
|
+
function f(e) {
|
|
101
|
+
typeof window > "u" || (window.__PHYSICS_SVG_DEVTOOLS_HOOK__ = e);
|
|
102
|
+
}
|
|
103
|
+
class g {
|
|
104
|
+
constructor() {
|
|
105
|
+
o(this, "subscribers", []);
|
|
106
|
+
}
|
|
107
|
+
/** Emit an animation event to all current subscribers. */
|
|
108
|
+
emit(t) {
|
|
109
|
+
for (const s of this.subscribers)
|
|
110
|
+
s(t);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Subscribe to animation events.
|
|
114
|
+
*
|
|
115
|
+
* @returns An unsubscribe function.
|
|
116
|
+
*/
|
|
117
|
+
subscribe(t) {
|
|
118
|
+
return this.subscribers.push(t), () => {
|
|
119
|
+
const s = this.subscribers.indexOf(t);
|
|
120
|
+
s !== -1 && this.subscribers.splice(s, 1);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
g as AnimationEmitter,
|
|
126
|
+
d as CanvasRenderer,
|
|
127
|
+
b as PhysicsEngine,
|
|
128
|
+
a as StateMachine,
|
|
129
|
+
m as SvgRenderer,
|
|
130
|
+
y as Vec2,
|
|
131
|
+
S as applyAttraction,
|
|
132
|
+
E as applyDrag,
|
|
133
|
+
w as applyForceFields,
|
|
134
|
+
T as applyGravity,
|
|
135
|
+
k as applyWind,
|
|
136
|
+
O as createBody,
|
|
137
|
+
P as createConstraint,
|
|
138
|
+
u as createDeployMachine,
|
|
139
|
+
p as createPickupDropMachine,
|
|
140
|
+
f as installDevToolsHook,
|
|
141
|
+
_ as wakeBody
|
|
142
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("react/jsx-runtime"),t=require("react"),u=require("../svg-renderer-CiwhJO3-.cjs"),l=t.createContext(null),v={gravity:{x:0,y:980},substeps:4,velocityIterations:6};function R({config:e,width:r="100%",height:n="100%",children:s}){const d=t.useRef(null),y=t.useMemo(()=>({...v,...e}),[]),i=t.useMemo(()=>new u.PhysicsEngine(y),[y]),o=t.useRef(null);return t.useEffect(()=>{if(!d.current)return;const c=new u.SvgRenderer;return c.init(d.current),o.current=c,()=>{c.destroy(),o.current=null}},[]),t.useEffect(()=>{let c=performance.now(),f;const m=g=>{const B=Math.min((g-c)/1e3,.05);c=g,i.update(B),o.current&&(o.current.alpha=i.alpha,o.current.render(i.getBodies(),i.getConstraints())),f=requestAnimationFrame(m)};return f=requestAnimationFrame(m),()=>{cancelAnimationFrame(f)}},[i]),h.jsx(l.Provider,{value:i,children:h.jsx("div",{ref:d,style:{width:r,height:n,position:"relative"},children:s})})}function a(){const e=t.useContext(l);if(!e)throw new Error("usePhysics() must be used inside a <PhysicsScene> component.");return e}function b(e){const r=a(),n=t.useRef(null);return n.current||(n.current=u.createBody({shape:e.shape,mass:e.mass,position:e.position,velocity:e.velocity,restitution:e.restitution,friction:e.friction,isStatic:e.isStatic,userData:e.userData})),t.useEffect(()=>{const s=n.current;return r.addBody(s),()=>{r.removeBody(s.id)}},[r]),null}function P(e){const r=a(),n=t.useRef(null);return n.current||(n.current=u.createConstraint({type:"spring",bodyA:e.bodyA,bodyB:e.bodyB,stiffness:e.stiffness,damping:e.damping,anchorA:e.anchorA,anchorB:e.anchorB,length:e.length})),t.useEffect(()=>{const s=n.current;return r.addConstraint(s),()=>{r.removeConstraint(s.id)}},[r]),null}function C(e){const r=a(),n=t.useRef(null);return n.current||(n.current=u.createBody(e)),t.useEffect(()=>{const s=n.current;return r.addBody(s),()=>{r.removeBody(s.id)}},[r]),n.current}exports.Body=b;exports.PhysicsContext=l;exports.PhysicsScene=R;exports.Spring=P;exports.useBody=C;exports.usePhysics=a;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as g } from "react/jsx-runtime";
|
|
2
|
+
import { createContext as C, useRef as s, useMemo as h, useEffect as u, useContext as A } from "react";
|
|
3
|
+
import { P, S as R, f as v, g as S } from "../svg-renderer-BOSG5i9F.js";
|
|
4
|
+
const B = C(null), x = {
|
|
5
|
+
gravity: { x: 0, y: 980 },
|
|
6
|
+
substeps: 4,
|
|
7
|
+
velocityIterations: 6
|
|
8
|
+
};
|
|
9
|
+
function D({
|
|
10
|
+
config: n,
|
|
11
|
+
width: t = "100%",
|
|
12
|
+
height: e = "100%",
|
|
13
|
+
children: r
|
|
14
|
+
}) {
|
|
15
|
+
const a = s(null), f = h(
|
|
16
|
+
() => ({ ...x, ...n }),
|
|
17
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18
|
+
[]
|
|
19
|
+
), i = h(() => new P(f), [f]), c = s(null);
|
|
20
|
+
return u(() => {
|
|
21
|
+
if (!a.current) return;
|
|
22
|
+
const o = new R();
|
|
23
|
+
return o.init(a.current), c.current = o, () => {
|
|
24
|
+
o.destroy(), c.current = null;
|
|
25
|
+
};
|
|
26
|
+
}, []), u(() => {
|
|
27
|
+
let o = performance.now(), d;
|
|
28
|
+
const y = (m) => {
|
|
29
|
+
const b = Math.min((m - o) / 1e3, 0.05);
|
|
30
|
+
o = m, i.update(b), c.current && (c.current.alpha = i.alpha, c.current.render(
|
|
31
|
+
i.getBodies(),
|
|
32
|
+
i.getConstraints()
|
|
33
|
+
)), d = requestAnimationFrame(y);
|
|
34
|
+
};
|
|
35
|
+
return d = requestAnimationFrame(y), () => {
|
|
36
|
+
cancelAnimationFrame(d);
|
|
37
|
+
};
|
|
38
|
+
}, [i]), /* @__PURE__ */ g(B.Provider, { value: i, children: /* @__PURE__ */ g(
|
|
39
|
+
"div",
|
|
40
|
+
{
|
|
41
|
+
ref: a,
|
|
42
|
+
style: { width: t, height: e, position: "relative" },
|
|
43
|
+
children: r
|
|
44
|
+
}
|
|
45
|
+
) });
|
|
46
|
+
}
|
|
47
|
+
function l() {
|
|
48
|
+
const n = A(B);
|
|
49
|
+
if (!n)
|
|
50
|
+
throw new Error(
|
|
51
|
+
"usePhysics() must be used inside a <PhysicsScene> component."
|
|
52
|
+
);
|
|
53
|
+
return n;
|
|
54
|
+
}
|
|
55
|
+
function I(n) {
|
|
56
|
+
const t = l(), e = s(null);
|
|
57
|
+
return e.current || (e.current = v({
|
|
58
|
+
shape: n.shape,
|
|
59
|
+
mass: n.mass,
|
|
60
|
+
position: n.position,
|
|
61
|
+
velocity: n.velocity,
|
|
62
|
+
restitution: n.restitution,
|
|
63
|
+
friction: n.friction,
|
|
64
|
+
isStatic: n.isStatic,
|
|
65
|
+
userData: n.userData
|
|
66
|
+
})), u(() => {
|
|
67
|
+
const r = e.current;
|
|
68
|
+
return t.addBody(r), () => {
|
|
69
|
+
t.removeBody(r.id);
|
|
70
|
+
};
|
|
71
|
+
}, [t]), null;
|
|
72
|
+
}
|
|
73
|
+
function q(n) {
|
|
74
|
+
const t = l(), e = s(null);
|
|
75
|
+
return e.current || (e.current = S({
|
|
76
|
+
type: "spring",
|
|
77
|
+
bodyA: n.bodyA,
|
|
78
|
+
bodyB: n.bodyB,
|
|
79
|
+
stiffness: n.stiffness,
|
|
80
|
+
damping: n.damping,
|
|
81
|
+
anchorA: n.anchorA,
|
|
82
|
+
anchorB: n.anchorB,
|
|
83
|
+
length: n.length
|
|
84
|
+
})), u(() => {
|
|
85
|
+
const r = e.current;
|
|
86
|
+
return t.addConstraint(r), () => {
|
|
87
|
+
t.removeConstraint(r.id);
|
|
88
|
+
};
|
|
89
|
+
}, [t]), null;
|
|
90
|
+
}
|
|
91
|
+
function M(n) {
|
|
92
|
+
const t = l(), e = s(null);
|
|
93
|
+
return e.current || (e.current = v(n)), u(() => {
|
|
94
|
+
const r = e.current;
|
|
95
|
+
return t.addBody(r), () => {
|
|
96
|
+
t.removeBody(r.id);
|
|
97
|
+
};
|
|
98
|
+
}, [t]), e.current;
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
I as Body,
|
|
102
|
+
B as PhysicsContext,
|
|
103
|
+
D as PhysicsScene,
|
|
104
|
+
q as Spring,
|
|
105
|
+
M as useBody,
|
|
106
|
+
l as usePhysics
|
|
107
|
+
};
|
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
var O = Object.defineProperty;
|
|
2
|
+
var N = (t, e, i) => e in t ? O(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i;
|
|
3
|
+
var y = (t, e, i) => N(t, typeof e != "symbol" ? e + "" : e, i);
|
|
4
|
+
function L(t = 0, e = 0) {
|
|
5
|
+
return { x: t, y: e };
|
|
6
|
+
}
|
|
7
|
+
function V() {
|
|
8
|
+
return { x: 0, y: 0 };
|
|
9
|
+
}
|
|
10
|
+
function d(t, e) {
|
|
11
|
+
return { x: t.x + e.x, y: t.y + e.y };
|
|
12
|
+
}
|
|
13
|
+
function S(t, e) {
|
|
14
|
+
return { x: t.x - e.x, y: t.y - e.y };
|
|
15
|
+
}
|
|
16
|
+
function g(t, e) {
|
|
17
|
+
return { x: t.x * e, y: t.y * e };
|
|
18
|
+
}
|
|
19
|
+
function _(t) {
|
|
20
|
+
return { x: -t.x, y: -t.y };
|
|
21
|
+
}
|
|
22
|
+
function X(t, e, i) {
|
|
23
|
+
return t.x = e.x + i.x, t.y = e.y + i.y, t;
|
|
24
|
+
}
|
|
25
|
+
function j(t, e, i) {
|
|
26
|
+
return t.x = e.x - i.x, t.y = e.y - i.y, t;
|
|
27
|
+
}
|
|
28
|
+
function Y(t, e, i) {
|
|
29
|
+
return t.x = e.x * i, t.y = e.y * i, t;
|
|
30
|
+
}
|
|
31
|
+
function C(t, e) {
|
|
32
|
+
return t.x += e.x, t.y += e.y, t;
|
|
33
|
+
}
|
|
34
|
+
function U(t, e) {
|
|
35
|
+
return t.x -= e.x, t.y -= e.y, t;
|
|
36
|
+
}
|
|
37
|
+
function $(t, e) {
|
|
38
|
+
return t.x *= e, t.y *= e, t;
|
|
39
|
+
}
|
|
40
|
+
function A(t, e) {
|
|
41
|
+
return t.x * e.x + t.y * e.y;
|
|
42
|
+
}
|
|
43
|
+
function H(t, e) {
|
|
44
|
+
return t.x * e.y - t.y * e.x;
|
|
45
|
+
}
|
|
46
|
+
function z(t, e) {
|
|
47
|
+
return { x: -t * e.y, y: t * e.x };
|
|
48
|
+
}
|
|
49
|
+
function Q(t, e) {
|
|
50
|
+
return { x: e * t.y, y: -e * t.x };
|
|
51
|
+
}
|
|
52
|
+
function P(t) {
|
|
53
|
+
return t.x * t.x + t.y * t.y;
|
|
54
|
+
}
|
|
55
|
+
function m(t) {
|
|
56
|
+
return Math.sqrt(t.x * t.x + t.y * t.y);
|
|
57
|
+
}
|
|
58
|
+
function R(t, e) {
|
|
59
|
+
const i = e.x - t.x, s = e.y - t.y;
|
|
60
|
+
return i * i + s * s;
|
|
61
|
+
}
|
|
62
|
+
function W(t, e) {
|
|
63
|
+
return Math.sqrt(R(t, e));
|
|
64
|
+
}
|
|
65
|
+
function Z(t) {
|
|
66
|
+
const e = m(t);
|
|
67
|
+
return e < 1e-10 ? { x: 0, y: 0 } : { x: t.x / e, y: t.y / e };
|
|
68
|
+
}
|
|
69
|
+
function G(t) {
|
|
70
|
+
const e = m(t);
|
|
71
|
+
return e < 1e-10 ? (t.x = 0, t.y = 0) : (t.x /= e, t.y /= e), t;
|
|
72
|
+
}
|
|
73
|
+
function J(t) {
|
|
74
|
+
return { x: -t.y, y: t.x };
|
|
75
|
+
}
|
|
76
|
+
function K(t) {
|
|
77
|
+
return { x: t.y, y: -t.x };
|
|
78
|
+
}
|
|
79
|
+
function F(t, e, i) {
|
|
80
|
+
return {
|
|
81
|
+
x: t.x + (e.x - t.x) * i,
|
|
82
|
+
y: t.y + (e.y - t.y) * i
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function b(t, e) {
|
|
86
|
+
const i = P(t);
|
|
87
|
+
if (i > e * e) {
|
|
88
|
+
const s = Math.sqrt(i);
|
|
89
|
+
return { x: t.x / s * e, y: t.y / s * e };
|
|
90
|
+
}
|
|
91
|
+
return { x: t.x, y: t.y };
|
|
92
|
+
}
|
|
93
|
+
function tt(t, e) {
|
|
94
|
+
const i = A(e, e);
|
|
95
|
+
if (i < 1e-10) return { x: 0, y: 0 };
|
|
96
|
+
const s = A(t, e) / i;
|
|
97
|
+
return { x: e.x * s, y: e.y * s };
|
|
98
|
+
}
|
|
99
|
+
function et(t, e) {
|
|
100
|
+
const i = 2 * A(t, e);
|
|
101
|
+
return { x: t.x - i * e.x, y: t.y - i * e.y };
|
|
102
|
+
}
|
|
103
|
+
function it(t, e) {
|
|
104
|
+
return t.x = e.x, t.y = e.y, t;
|
|
105
|
+
}
|
|
106
|
+
function st(t) {
|
|
107
|
+
return { x: t.x, y: t.y };
|
|
108
|
+
}
|
|
109
|
+
function nt(t, e, i = 1e-6) {
|
|
110
|
+
return Math.abs(t.x - e.x) < i && Math.abs(t.y - e.y) < i;
|
|
111
|
+
}
|
|
112
|
+
function ot(t, e) {
|
|
113
|
+
const i = Math.cos(e), s = Math.sin(e);
|
|
114
|
+
return {
|
|
115
|
+
x: t.x * i - t.y * s,
|
|
116
|
+
y: t.x * s + t.y * i
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const It = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
120
|
+
__proto__: null,
|
|
121
|
+
add: d,
|
|
122
|
+
addMut: X,
|
|
123
|
+
addTo: C,
|
|
124
|
+
approxEqual: nt,
|
|
125
|
+
clampLength: b,
|
|
126
|
+
clone: st,
|
|
127
|
+
copy: it,
|
|
128
|
+
cross: H,
|
|
129
|
+
crossSV: z,
|
|
130
|
+
crossVS: Q,
|
|
131
|
+
distance: W,
|
|
132
|
+
distanceSq: R,
|
|
133
|
+
dot: A,
|
|
134
|
+
length: m,
|
|
135
|
+
lengthSq: P,
|
|
136
|
+
lerp: F,
|
|
137
|
+
negate: _,
|
|
138
|
+
normalize: Z,
|
|
139
|
+
normalizeMut: G,
|
|
140
|
+
perpL: J,
|
|
141
|
+
perpR: K,
|
|
142
|
+
project: tt,
|
|
143
|
+
reflect: et,
|
|
144
|
+
rotate: ot,
|
|
145
|
+
scale: g,
|
|
146
|
+
scaleBy: $,
|
|
147
|
+
scaleMut: Y,
|
|
148
|
+
sub: S,
|
|
149
|
+
subFrom: U,
|
|
150
|
+
subMut: j,
|
|
151
|
+
vec2: L,
|
|
152
|
+
zero: V
|
|
153
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
154
|
+
function rt(t, e) {
|
|
155
|
+
t.previousPosition.x = t.position.x, t.previousPosition.y = t.position.y, t.velocity.x += t.acceleration.x * e, t.velocity.y += t.acceleration.y * e, t.position.x += t.velocity.x * e, t.position.y += t.velocity.y * e, t.acceleration.x = 0, t.acceleration.y = 0;
|
|
156
|
+
}
|
|
157
|
+
function ct(t, e, i = 4) {
|
|
158
|
+
for (let s = 0; s < i; s++)
|
|
159
|
+
for (const r of t.values()) {
|
|
160
|
+
const o = e.get(r.bodyA), n = e.get(r.bodyB);
|
|
161
|
+
if (!(!o || !n))
|
|
162
|
+
switch (r.type) {
|
|
163
|
+
case "spring":
|
|
164
|
+
at(o, n, r);
|
|
165
|
+
break;
|
|
166
|
+
case "distance":
|
|
167
|
+
lt(o, n, r);
|
|
168
|
+
break;
|
|
169
|
+
case "pin":
|
|
170
|
+
ut(o, n, r);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function at(t, e, i) {
|
|
176
|
+
const s = d(t.position, i.anchorA), r = d(e.position, i.anchorB), o = S(r, s), n = m(o);
|
|
177
|
+
if (n < 1e-10) return;
|
|
178
|
+
const a = g(o, 1 / n), c = i.length ?? n, u = n - c, l = S(e.velocity, t.velocity), h = A(l, a), p = i.stiffness * u + i.damping * h, f = g(a, p);
|
|
179
|
+
t.isStatic || (t.acceleration.x += f.x * t.invMass, t.acceleration.y += f.y * t.invMass), e.isStatic || (e.acceleration.x -= f.x * e.invMass, e.acceleration.y -= f.y * e.invMass);
|
|
180
|
+
}
|
|
181
|
+
function lt(t, e, i) {
|
|
182
|
+
const s = d(t.position, i.anchorA), r = d(e.position, i.anchorB), o = S(r, s), n = m(o);
|
|
183
|
+
if (n < 1e-10) return;
|
|
184
|
+
const a = i.length ?? 0, c = n - a, u = g(o, 1 / n), l = t.invMass + e.invMass;
|
|
185
|
+
if (l < 1e-10) return;
|
|
186
|
+
const h = i.stiffness;
|
|
187
|
+
if (!t.isStatic) {
|
|
188
|
+
const p = c * t.invMass * h / l;
|
|
189
|
+
t.position.x += u.x * p, t.position.y += u.y * p;
|
|
190
|
+
}
|
|
191
|
+
if (!e.isStatic) {
|
|
192
|
+
const p = c * e.invMass * h / l;
|
|
193
|
+
e.position.x -= u.x * p, e.position.y -= u.y * p;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function ut(t, e, i) {
|
|
197
|
+
const s = d(e.position, i.anchorB), r = d(t.position, i.anchorA), o = S(s, r), n = g(o, i.stiffness);
|
|
198
|
+
t.isStatic || (C(t.position, n), t.velocity.x += n.x * i.damping, t.velocity.y += n.y * i.damping);
|
|
199
|
+
}
|
|
200
|
+
function k(t, e) {
|
|
201
|
+
t.acceleration.x += e.x, t.acceleration.y += e.y;
|
|
202
|
+
}
|
|
203
|
+
function D(t, e) {
|
|
204
|
+
t.invMass <= 0 || (t.acceleration.x += -e * t.velocity.x * t.invMass, t.acceleration.y += -e * t.velocity.y * t.invMass);
|
|
205
|
+
}
|
|
206
|
+
function ht(t, e, i) {
|
|
207
|
+
t.invMass <= 0 || (t.acceleration.x += e.x * i * t.invMass, t.acceleration.y += e.y * i * t.invMass);
|
|
208
|
+
}
|
|
209
|
+
function pt(t, e, i, s = "quadratic") {
|
|
210
|
+
if (t.invMass <= 0) return;
|
|
211
|
+
const r = S(e, t.position), o = P(r), a = Math.max(o, 100), c = Math.sqrt(a), u = g(r, 1 / c);
|
|
212
|
+
let l;
|
|
213
|
+
switch (s) {
|
|
214
|
+
case "none":
|
|
215
|
+
l = i;
|
|
216
|
+
break;
|
|
217
|
+
case "linear":
|
|
218
|
+
l = i / c;
|
|
219
|
+
break;
|
|
220
|
+
case "quadratic":
|
|
221
|
+
l = i / a;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
t.acceleration.x += u.x * l * t.invMass, t.acceleration.y += u.y * l * t.invMass;
|
|
225
|
+
}
|
|
226
|
+
function ft(t, e, i) {
|
|
227
|
+
for (const s of e)
|
|
228
|
+
switch (s.type) {
|
|
229
|
+
case "gravity":
|
|
230
|
+
k(t, s.vector ?? i);
|
|
231
|
+
break;
|
|
232
|
+
case "drag":
|
|
233
|
+
D(t, s.strength ?? 0.01);
|
|
234
|
+
break;
|
|
235
|
+
case "wind":
|
|
236
|
+
s.vector && ht(t, s.vector, s.strength ?? 1);
|
|
237
|
+
break;
|
|
238
|
+
case "attraction":
|
|
239
|
+
s.vector && pt(t, s.vector, s.strength ?? 100, s.falloff ?? "quadratic");
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function yt(t) {
|
|
244
|
+
const e = [];
|
|
245
|
+
for (let i = 0; i < t.length; i++)
|
|
246
|
+
for (let s = i + 1; s < t.length; s++) {
|
|
247
|
+
const r = t[i], o = t[s];
|
|
248
|
+
if (r.isStatic && o.isStatic || r.isSleeping && o.isSleeping) continue;
|
|
249
|
+
const n = xt(r, o);
|
|
250
|
+
n && e.push(n);
|
|
251
|
+
}
|
|
252
|
+
return e;
|
|
253
|
+
}
|
|
254
|
+
function xt(t, e) {
|
|
255
|
+
const i = t.shape.type, s = e.shape.type;
|
|
256
|
+
if (i === "circle" && s === "circle")
|
|
257
|
+
return dt(t, e);
|
|
258
|
+
if (i === "circle" && s === "rect")
|
|
259
|
+
return I(t, e);
|
|
260
|
+
if (i === "rect" && s === "circle") {
|
|
261
|
+
const r = I(e, t);
|
|
262
|
+
return r ? {
|
|
263
|
+
bodyA: t.id,
|
|
264
|
+
bodyB: e.id,
|
|
265
|
+
normal: _(r.normal),
|
|
266
|
+
penetration: r.penetration,
|
|
267
|
+
overlap: _(r.overlap)
|
|
268
|
+
} : null;
|
|
269
|
+
}
|
|
270
|
+
return i === "rect" && s === "rect" ? gt(t, e) : null;
|
|
271
|
+
}
|
|
272
|
+
function dt(t, e) {
|
|
273
|
+
if (t.shape.type !== "circle" || e.shape.type !== "circle") return null;
|
|
274
|
+
const i = e.position.x - t.position.x, s = e.position.y - t.position.y, r = i * i + s * s, o = t.shape.radius + e.shape.radius;
|
|
275
|
+
if (r >= o * o) return null;
|
|
276
|
+
const n = Math.sqrt(r), a = n < 1e-10 ? { x: 0, y: 1 } : { x: i / n, y: s / n }, c = o - n;
|
|
277
|
+
return {
|
|
278
|
+
bodyA: t.id,
|
|
279
|
+
bodyB: e.id,
|
|
280
|
+
normal: a,
|
|
281
|
+
penetration: c,
|
|
282
|
+
overlap: g(a, c)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function I(t, e) {
|
|
286
|
+
if (t.shape.type !== "circle" || e.shape.type !== "rect") return null;
|
|
287
|
+
const i = e.shape.width / 2, s = e.shape.height / 2, r = t.position.x - e.position.x, o = t.position.y - e.position.y, n = Math.max(-i, Math.min(i, r)), a = Math.max(-s, Math.min(s, o)), c = r - n, u = o - a, l = c * c + u * u, h = t.shape.radius;
|
|
288
|
+
if (l >= h * h) return null;
|
|
289
|
+
const p = Math.sqrt(l);
|
|
290
|
+
let f, x;
|
|
291
|
+
if (p < 1e-10) {
|
|
292
|
+
const w = i - Math.abs(r), T = s - Math.abs(o);
|
|
293
|
+
w < T ? f = { x: r > 0 ? 1 : -1, y: 0 } : f = { x: 0, y: o > 0 ? 1 : -1 }, x = h + Math.min(w, T);
|
|
294
|
+
} else
|
|
295
|
+
f = { x: c / p, y: u / p }, x = h - p;
|
|
296
|
+
return {
|
|
297
|
+
bodyA: t.id,
|
|
298
|
+
bodyB: e.id,
|
|
299
|
+
normal: f,
|
|
300
|
+
penetration: x,
|
|
301
|
+
overlap: g(f, x)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function gt(t, e) {
|
|
305
|
+
if (t.shape.type !== "rect" || e.shape.type !== "rect") return null;
|
|
306
|
+
const i = t.shape.width / 2, s = t.shape.height / 2, r = e.shape.width / 2, o = e.shape.height / 2, n = e.position.x - t.position.x, a = e.position.y - t.position.y, c = i + r - Math.abs(n);
|
|
307
|
+
if (c <= 0) return null;
|
|
308
|
+
const u = s + o - Math.abs(a);
|
|
309
|
+
if (u <= 0) return null;
|
|
310
|
+
let l, h;
|
|
311
|
+
return c < u ? (l = { x: n > 0 ? 1 : -1, y: 0 }, h = c) : (l = { x: 0, y: a > 0 ? 1 : -1 }, h = u), {
|
|
312
|
+
bodyA: t.id,
|
|
313
|
+
bodyB: e.id,
|
|
314
|
+
normal: l,
|
|
315
|
+
penetration: h,
|
|
316
|
+
overlap: g(l, h)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const vt = 0.5, Mt = 0.4, St = 0.5;
|
|
320
|
+
function mt(t, e, i) {
|
|
321
|
+
const { normal: s, penetration: r } = i, o = e.velocity.x - t.velocity.x, n = e.velocity.y - t.velocity.y, a = o * s.x + n * s.y;
|
|
322
|
+
if (a > 0) return;
|
|
323
|
+
let c = Math.min(t.restitution, e.restitution);
|
|
324
|
+
Math.abs(a) < St && (c = 0);
|
|
325
|
+
const u = t.invMass + e.invMass;
|
|
326
|
+
if (u < 1e-10) return;
|
|
327
|
+
const l = -(1 + c) * a / u;
|
|
328
|
+
t.velocity.x -= l * t.invMass * s.x, t.velocity.y -= l * t.invMass * s.y, e.velocity.x += l * e.invMass * s.x, e.velocity.y += l * e.invMass * s.y;
|
|
329
|
+
let h = o - a * s.x, p = n - a * s.y;
|
|
330
|
+
const f = Math.sqrt(h * h + p * p);
|
|
331
|
+
if (f > 1e-10) {
|
|
332
|
+
h /= f, p /= f;
|
|
333
|
+
const w = Math.sqrt(t.friction * e.friction);
|
|
334
|
+
let v = -(o * h + n * p) / u;
|
|
335
|
+
Math.abs(v) > Math.abs(l * w) && (v = l * w * Math.sign(v)), t.velocity.x -= v * t.invMass * h, t.velocity.y -= v * t.invMass * p, e.velocity.x += v * e.invMass * h, e.velocity.y += v * e.invMass * p;
|
|
336
|
+
}
|
|
337
|
+
const x = Math.max(r - vt, 0) * Mt / u;
|
|
338
|
+
t.position.x -= x * t.invMass * s.x, t.position.y -= x * t.invMass * s.y, e.position.x += x * e.invMass * s.x, e.position.y += x * e.invMass * s.y;
|
|
339
|
+
}
|
|
340
|
+
const wt = 0.5, At = 30;
|
|
341
|
+
function Et(t) {
|
|
342
|
+
if (t.isStatic) return;
|
|
343
|
+
m(t.velocity) < wt ? (t.sleepTimer++, t.sleepTimer >= At && (t.isSleeping = !0, t.velocity.x = 0, t.velocity.y = 0)) : (t.sleepTimer = 0, t.isSleeping = !1);
|
|
344
|
+
}
|
|
345
|
+
function M(t) {
|
|
346
|
+
t.isSleeping = !1, t.sleepTimer = 0;
|
|
347
|
+
}
|
|
348
|
+
function Tt(t, e) {
|
|
349
|
+
t.isSleeping && !e.isSleeping && !e.isStatic && M(t), e.isSleeping && !t.isSleeping && !t.isStatic && M(e);
|
|
350
|
+
}
|
|
351
|
+
class Bt {
|
|
352
|
+
constructor(e) {
|
|
353
|
+
y(this, "bodies", /* @__PURE__ */ new Map());
|
|
354
|
+
y(this, "constraints", /* @__PURE__ */ new Map());
|
|
355
|
+
y(this, "forces", []);
|
|
356
|
+
y(this, "config");
|
|
357
|
+
this.config = e;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Advance the world by `dt` seconds.
|
|
361
|
+
*
|
|
362
|
+
* Pipeline per substep:
|
|
363
|
+
* 1. Apply forces (gravity, drag, custom force fields)
|
|
364
|
+
* 2. Integrate positions (semi-implicit Euler)
|
|
365
|
+
* 3. Solve constraints (spring, distance, pin)
|
|
366
|
+
* 4. Detect & resolve collisions
|
|
367
|
+
* 5. Enforce world bounds
|
|
368
|
+
* 6. Update sleep states
|
|
369
|
+
*/
|
|
370
|
+
step(e) {
|
|
371
|
+
const i = e / this.config.substeps;
|
|
372
|
+
for (let s = 0; s < this.config.substeps; s++) {
|
|
373
|
+
for (const n of this.constraints.values()) {
|
|
374
|
+
const a = this.bodies.get(n.bodyA), c = this.bodies.get(n.bodyB);
|
|
375
|
+
a && !a.isStatic && a.isSleeping && M(a), c && !c.isStatic && c.isSleeping && M(c);
|
|
376
|
+
}
|
|
377
|
+
for (const n of this.bodies.values())
|
|
378
|
+
n.isStatic || n.isSleeping || (k(n, this.config.gravity), D(n, 0.01), this.forces.length > 0 && ft(n, this.forces, this.config.gravity));
|
|
379
|
+
for (const n of this.bodies.values())
|
|
380
|
+
n.isStatic || n.isSleeping || rt(n, i);
|
|
381
|
+
ct(
|
|
382
|
+
this.constraints,
|
|
383
|
+
this.bodies,
|
|
384
|
+
this.config.velocityIterations
|
|
385
|
+
);
|
|
386
|
+
const r = Array.from(this.bodies.values()), o = yt(r);
|
|
387
|
+
for (const n of o) {
|
|
388
|
+
const a = this.bodies.get(n.bodyA), c = this.bodies.get(n.bodyB);
|
|
389
|
+
a && c && (Tt(a, c), mt(a, c, n));
|
|
390
|
+
}
|
|
391
|
+
this.config.bounds && this.enforceBounds();
|
|
392
|
+
for (const n of this.bodies.values())
|
|
393
|
+
n.isStatic || Et(n);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/** Clamp bodies within configured world bounds. */
|
|
397
|
+
enforceBounds() {
|
|
398
|
+
const e = this.config.bounds;
|
|
399
|
+
if (e)
|
|
400
|
+
for (const i of this.bodies.values()) {
|
|
401
|
+
if (i.isStatic) continue;
|
|
402
|
+
let s = 0;
|
|
403
|
+
i.shape.type === "circle" ? s = i.shape.radius : i.shape.type === "rect" && (s = Math.max(i.shape.width, i.shape.height) / 2), i.position.x - s < e.min.x && (i.position.x = e.min.x + s, i.velocity.x = Math.abs(i.velocity.x) * i.restitution), i.position.x + s > e.max.x && (i.position.x = e.max.x - s, i.velocity.x = -Math.abs(i.velocity.x) * i.restitution), i.position.y - s < e.min.y && (i.position.y = e.min.y + s, i.velocity.y = Math.abs(i.velocity.y) * i.restitution), i.position.y + s > e.max.y && (i.position.y = e.max.y - s, i.velocity.y = -Math.abs(i.velocity.y) * i.restitution);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/** Add a force field. */
|
|
407
|
+
addForceField(e) {
|
|
408
|
+
this.forces.push(e);
|
|
409
|
+
}
|
|
410
|
+
/** Remove all force fields of a given type. */
|
|
411
|
+
removeForceFields(e) {
|
|
412
|
+
for (let i = this.forces.length - 1; i >= 0; i--)
|
|
413
|
+
this.forces[i].type === e && this.forces.splice(i, 1);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const E = 1 / 60, _t = 0.25;
|
|
417
|
+
class qt {
|
|
418
|
+
// interpolation factor for rendering
|
|
419
|
+
constructor(e) {
|
|
420
|
+
y(this, "world");
|
|
421
|
+
y(this, "accumulator", 0);
|
|
422
|
+
y(this, "_alpha", 0);
|
|
423
|
+
this.world = new Bt(e);
|
|
424
|
+
}
|
|
425
|
+
// ---- Simulation ----------------------------------------------------------
|
|
426
|
+
/**
|
|
427
|
+
* Advance the simulation by `frameTime` seconds.
|
|
428
|
+
*
|
|
429
|
+
* Uses Glenn Fiedler's fixed-timestep-with-accumulator pattern:
|
|
430
|
+
* - Clamp frame time to prevent spiral of death
|
|
431
|
+
* - Step physics at fixed 60Hz intervals
|
|
432
|
+
* - Store interpolation alpha for smooth rendering
|
|
433
|
+
*/
|
|
434
|
+
update(e) {
|
|
435
|
+
const i = Math.min(e, _t);
|
|
436
|
+
for (this.accumulator += i; this.accumulator >= E; )
|
|
437
|
+
this.world.step(E), this.accumulator -= E;
|
|
438
|
+
this._alpha = this.accumulator / E;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get the interpolation alpha for the current frame.
|
|
442
|
+
*
|
|
443
|
+
* Use this to blend between previousPosition and position:
|
|
444
|
+
* renderPos = lerp(body.previousPosition, body.position, alpha)
|
|
445
|
+
*/
|
|
446
|
+
get alpha() {
|
|
447
|
+
return this._alpha;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Get the interpolated render position for a body.
|
|
451
|
+
*/
|
|
452
|
+
getInterpolatedPosition(e) {
|
|
453
|
+
return F(e.previousPosition, e.position, this._alpha);
|
|
454
|
+
}
|
|
455
|
+
// ---- Body Management -----------------------------------------------------
|
|
456
|
+
/** Register a body and return its id. */
|
|
457
|
+
addBody(e) {
|
|
458
|
+
return this.world.bodies.set(e.id, e), e.id;
|
|
459
|
+
}
|
|
460
|
+
/** Remove a body by id. */
|
|
461
|
+
removeBody(e) {
|
|
462
|
+
this.world.bodies.delete(e);
|
|
463
|
+
}
|
|
464
|
+
/** Look up a single body. */
|
|
465
|
+
getBody(e) {
|
|
466
|
+
return this.world.bodies.get(e);
|
|
467
|
+
}
|
|
468
|
+
/** Return every body currently in the world. */
|
|
469
|
+
getBodies() {
|
|
470
|
+
return Array.from(this.world.bodies.values());
|
|
471
|
+
}
|
|
472
|
+
/** Apply an instantaneous impulse to a body. */
|
|
473
|
+
applyImpulse(e, i) {
|
|
474
|
+
const s = this.world.bodies.get(e);
|
|
475
|
+
!s || s.isStatic || (M(s), s.velocity.x += i.x * s.invMass, s.velocity.y += i.y * s.invMass);
|
|
476
|
+
}
|
|
477
|
+
/** Set a body's position directly (teleport). */
|
|
478
|
+
setPosition(e, i) {
|
|
479
|
+
const s = this.world.bodies.get(e);
|
|
480
|
+
s && (M(s), s.position.x = i.x, s.position.y = i.y, s.previousPosition.x = i.x, s.previousPosition.y = i.y);
|
|
481
|
+
}
|
|
482
|
+
/** Set a body's velocity directly. */
|
|
483
|
+
setVelocity(e, i) {
|
|
484
|
+
const s = this.world.bodies.get(e);
|
|
485
|
+
!s || s.isStatic || (M(s), s.velocity.x = i.x, s.velocity.y = i.y);
|
|
486
|
+
}
|
|
487
|
+
// ---- Constraint Management -----------------------------------------------
|
|
488
|
+
/** Register a constraint and return its id. */
|
|
489
|
+
addConstraint(e) {
|
|
490
|
+
return this.world.constraints.set(e.id, e), e.id;
|
|
491
|
+
}
|
|
492
|
+
/** Remove a constraint by id. */
|
|
493
|
+
removeConstraint(e) {
|
|
494
|
+
this.world.constraints.delete(e);
|
|
495
|
+
}
|
|
496
|
+
/** Return every constraint currently in the world. */
|
|
497
|
+
getConstraints() {
|
|
498
|
+
return Array.from(this.world.constraints.values());
|
|
499
|
+
}
|
|
500
|
+
// ---- Force Fields --------------------------------------------------------
|
|
501
|
+
/** Add a force field to the world. */
|
|
502
|
+
addForceField(e) {
|
|
503
|
+
this.world.addForceField(e);
|
|
504
|
+
}
|
|
505
|
+
/** Remove all force fields of a given type. */
|
|
506
|
+
removeForceFields(e) {
|
|
507
|
+
this.world.removeForceFields(e);
|
|
508
|
+
}
|
|
509
|
+
// ---- Config --------------------------------------------------------------
|
|
510
|
+
/** Access the underlying world config. */
|
|
511
|
+
getConfig() {
|
|
512
|
+
return this.world.config;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const B = { x: 0, y: 0 }, Pt = { type: "circle", radius: 10 };
|
|
516
|
+
function Ct(t = {}) {
|
|
517
|
+
const e = t.isStatic ?? !1, i = e ? 0 : t.mass ?? 1, s = t.position ?? { ...B };
|
|
518
|
+
return {
|
|
519
|
+
id: t.id ?? crypto.randomUUID(),
|
|
520
|
+
position: { ...s },
|
|
521
|
+
previousPosition: t.previousPosition ?? { ...s },
|
|
522
|
+
velocity: t.velocity ?? { ...B },
|
|
523
|
+
acceleration: t.acceleration ?? { ...B },
|
|
524
|
+
mass: i,
|
|
525
|
+
invMass: i > 0 ? 1 / i : 0,
|
|
526
|
+
restitution: t.restitution ?? 0.5,
|
|
527
|
+
friction: t.friction ?? 0.3,
|
|
528
|
+
isStatic: e,
|
|
529
|
+
isSleeping: t.isSleeping ?? !1,
|
|
530
|
+
sleepTimer: t.sleepTimer ?? 0,
|
|
531
|
+
shape: t.shape ?? { ...Pt },
|
|
532
|
+
userData: t.userData
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
const q = { x: 0, y: 0 };
|
|
536
|
+
function Rt(t) {
|
|
537
|
+
return {
|
|
538
|
+
id: t.id ?? crypto.randomUUID(),
|
|
539
|
+
type: t.type ?? "spring",
|
|
540
|
+
bodyA: t.bodyA,
|
|
541
|
+
bodyB: t.bodyB,
|
|
542
|
+
anchorA: t.anchorA ?? { ...q },
|
|
543
|
+
anchorB: t.anchorB ?? { ...q },
|
|
544
|
+
stiffness: t.stiffness ?? 0.5,
|
|
545
|
+
damping: t.damping ?? 0.1,
|
|
546
|
+
length: t.length
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
class kt {
|
|
550
|
+
constructor() {
|
|
551
|
+
y(this, "svg", null);
|
|
552
|
+
y(this, "elementMap", /* @__PURE__ */ new Map());
|
|
553
|
+
y(this, "constraintMap", /* @__PURE__ */ new Map());
|
|
554
|
+
/** Interpolation alpha (0 = previous state, 1 = current state). */
|
|
555
|
+
y(this, "alpha", 1);
|
|
556
|
+
}
|
|
557
|
+
init(e) {
|
|
558
|
+
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"), this.svg.setAttribute("width", "100%"), this.svg.setAttribute("height", "100%"), this.svg.style.overflow = "visible", e.appendChild(this.svg);
|
|
559
|
+
}
|
|
560
|
+
render(e, i) {
|
|
561
|
+
this.svg && (this.renderConstraints(i, e), this.renderBodies(e));
|
|
562
|
+
}
|
|
563
|
+
destroy() {
|
|
564
|
+
var e;
|
|
565
|
+
(e = this.svg) == null || e.remove(), this.svg = null, this.elementMap.clear(), this.constraintMap.clear();
|
|
566
|
+
}
|
|
567
|
+
// ---- Body Rendering ------------------------------------------------------
|
|
568
|
+
renderBodies(e) {
|
|
569
|
+
const i = /* @__PURE__ */ new Set();
|
|
570
|
+
for (const s of e) {
|
|
571
|
+
i.add(s.id);
|
|
572
|
+
let r = this.elementMap.get(s.id);
|
|
573
|
+
r || (r = this.createElement(s), this.elementMap.set(s.id, r), this.svg.appendChild(r));
|
|
574
|
+
const o = this.interpolate(s);
|
|
575
|
+
r.setAttribute(
|
|
576
|
+
"transform",
|
|
577
|
+
`translate(${o.x}, ${o.y})`
|
|
578
|
+
), r.setAttribute("opacity", s.isSleeping ? "0.5" : "1");
|
|
579
|
+
}
|
|
580
|
+
for (const [s, r] of this.elementMap)
|
|
581
|
+
i.has(s) || (r.remove(), this.elementMap.delete(s));
|
|
582
|
+
}
|
|
583
|
+
/** Interpolate between previous and current position. */
|
|
584
|
+
interpolate(e) {
|
|
585
|
+
return this.alpha >= 1 ? e.position : F(e.previousPosition, e.position, this.alpha);
|
|
586
|
+
}
|
|
587
|
+
/** Create the appropriate SVG element for a body's shape. */
|
|
588
|
+
createElement(e) {
|
|
589
|
+
const i = "http://www.w3.org/2000/svg";
|
|
590
|
+
switch (e.shape.type) {
|
|
591
|
+
case "circle": {
|
|
592
|
+
const s = document.createElementNS(i, "circle");
|
|
593
|
+
return s.setAttribute("r", String(e.shape.radius)), s.setAttribute("fill", "#6366f1"), s;
|
|
594
|
+
}
|
|
595
|
+
case "rect": {
|
|
596
|
+
const s = document.createElementNS(i, "rect");
|
|
597
|
+
return s.setAttribute("width", String(e.shape.width)), s.setAttribute("height", String(e.shape.height)), s.setAttribute("x", String(-e.shape.width / 2)), s.setAttribute("y", String(-e.shape.height / 2)), s.setAttribute("fill", "#6366f1"), s;
|
|
598
|
+
}
|
|
599
|
+
case "polygon": {
|
|
600
|
+
const s = document.createElementNS(i, "polygon"), r = e.shape.vertices.map((o) => `${o.x},${o.y}`).join(" ");
|
|
601
|
+
return s.setAttribute("points", r), s.setAttribute("fill", "#6366f1"), s;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// ---- Constraint Rendering ------------------------------------------------
|
|
606
|
+
renderConstraints(e, i) {
|
|
607
|
+
const s = new Map(i.map((o) => [o.id, o])), r = /* @__PURE__ */ new Set();
|
|
608
|
+
for (const o of e) {
|
|
609
|
+
const n = s.get(o.bodyA), a = s.get(o.bodyB);
|
|
610
|
+
if (!n || !a) continue;
|
|
611
|
+
r.add(o.id);
|
|
612
|
+
let c = this.constraintMap.get(o.id);
|
|
613
|
+
c || (c = document.createElementNS(
|
|
614
|
+
"http://www.w3.org/2000/svg",
|
|
615
|
+
"line"
|
|
616
|
+
), c.setAttribute("stroke", "#94a3b8"), c.setAttribute("stroke-width", "1"), c.setAttribute("stroke-dasharray", "4 2"), this.constraintMap.set(o.id, c), this.svg.insertBefore(c, this.svg.firstChild));
|
|
617
|
+
const u = this.interpolate(n), l = this.interpolate(a), h = d(u, o.anchorA), p = d(l, o.anchorB);
|
|
618
|
+
c.setAttribute("x1", String(h.x)), c.setAttribute("y1", String(h.y)), c.setAttribute("x2", String(p.x)), c.setAttribute("y2", String(p.y));
|
|
619
|
+
}
|
|
620
|
+
for (const [o, n] of this.constraintMap)
|
|
621
|
+
r.has(o) || (n.remove(), this.constraintMap.delete(o));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
export {
|
|
625
|
+
qt as P,
|
|
626
|
+
kt as S,
|
|
627
|
+
pt as a,
|
|
628
|
+
D as b,
|
|
629
|
+
ft as c,
|
|
630
|
+
k as d,
|
|
631
|
+
ht as e,
|
|
632
|
+
Ct as f,
|
|
633
|
+
Rt as g,
|
|
634
|
+
It as v,
|
|
635
|
+
M as w
|
|
636
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var V=Object.defineProperty;var X=(t,e,i)=>e in t?V(t,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[e]=i;var y=(t,e,i)=>X(t,typeof e!="symbol"?e+"":e,i);function j(t=0,e=0){return{x:t,y:e}}function Y(){return{x:0,y:0}}function d(t,e){return{x:t.x+e.x,y:t.y+e.y}}function S(t,e){return{x:t.x-e.x,y:t.y-e.y}}function g(t,e){return{x:t.x*e,y:t.y*e}}function _(t){return{x:-t.x,y:-t.y}}function U(t,e,i){return t.x=e.x+i.x,t.y=e.y+i.y,t}function $(t,e,i){return t.x=e.x-i.x,t.y=e.y-i.y,t}function H(t,e,i){return t.x=e.x*i,t.y=e.y*i,t}function k(t,e){return t.x+=e.x,t.y+=e.y,t}function z(t,e){return t.x-=e.x,t.y-=e.y,t}function W(t,e){return t.x*=e,t.y*=e,t}function A(t,e){return t.x*e.x+t.y*e.y}function G(t,e){return t.x*e.y-t.y*e.x}function Q(t,e){return{x:-t*e.y,y:t*e.x}}function Z(t,e){return{x:e*t.y,y:-e*t.x}}function F(t){return t.x*t.x+t.y*t.y}function m(t){return Math.sqrt(t.x*t.x+t.y*t.y)}function D(t,e){const i=e.x-t.x,s=e.y-t.y;return i*i+s*s}function J(t,e){return Math.sqrt(D(t,e))}function K(t){const e=m(t);return e<1e-10?{x:0,y:0}:{x:t.x/e,y:t.y/e}}function b(t){const e=m(t);return e<1e-10?(t.x=0,t.y=0):(t.x/=e,t.y/=e),t}function tt(t){return{x:-t.y,y:t.x}}function et(t){return{x:t.y,y:-t.x}}function P(t,e,i){return{x:t.x+(e.x-t.x)*i,y:t.y+(e.y-t.y)*i}}function it(t,e){const i=F(t);if(i>e*e){const s=Math.sqrt(i);return{x:t.x/s*e,y:t.y/s*e}}return{x:t.x,y:t.y}}function st(t,e){const i=A(e,e);if(i<1e-10)return{x:0,y:0};const s=A(t,e)/i;return{x:e.x*s,y:e.y*s}}function nt(t,e){const i=2*A(t,e);return{x:t.x-i*e.x,y:t.y-i*e.y}}function ot(t,e){return t.x=e.x,t.y=e.y,t}function rt(t){return{x:t.x,y:t.y}}function ct(t,e,i=1e-6){return Math.abs(t.x-e.x)<i&&Math.abs(t.y-e.y)<i}function at(t,e){const i=Math.cos(e),s=Math.sin(e);return{x:t.x*i-t.y*s,y:t.x*s+t.y*i}}const lt=Object.freeze(Object.defineProperty({__proto__:null,add:d,addMut:U,addTo:k,approxEqual:ct,clampLength:it,clone:rt,copy:ot,cross:G,crossSV:Q,crossVS:Z,distance:J,distanceSq:D,dot:A,length:m,lengthSq:F,lerp:P,negate:_,normalize:K,normalizeMut:b,perpL:tt,perpR:et,project:st,reflect:nt,rotate:at,scale:g,scaleBy:W,scaleMut:H,sub:S,subFrom:z,subMut:$,vec2:j,zero:Y},Symbol.toStringTag,{value:"Module"}));function ut(t,e){t.previousPosition.x=t.position.x,t.previousPosition.y=t.position.y,t.velocity.x+=t.acceleration.x*e,t.velocity.y+=t.acceleration.y*e,t.position.x+=t.velocity.x*e,t.position.y+=t.velocity.y*e,t.acceleration.x=0,t.acceleration.y=0}function ht(t,e,i=4){for(let s=0;s<i;s++)for(const r of t.values()){const o=e.get(r.bodyA),n=e.get(r.bodyB);if(!(!o||!n))switch(r.type){case"spring":pt(o,n,r);break;case"distance":ft(o,n,r);break;case"pin":yt(o,n,r);break}}}function pt(t,e,i){const s=d(t.position,i.anchorA),r=d(e.position,i.anchorB),o=S(r,s),n=m(o);if(n<1e-10)return;const a=g(o,1/n),c=i.length??n,u=n-c,l=S(e.velocity,t.velocity),h=A(l,a),p=i.stiffness*u+i.damping*h,f=g(a,p);t.isStatic||(t.acceleration.x+=f.x*t.invMass,t.acceleration.y+=f.y*t.invMass),e.isStatic||(e.acceleration.x-=f.x*e.invMass,e.acceleration.y-=f.y*e.invMass)}function ft(t,e,i){const s=d(t.position,i.anchorA),r=d(e.position,i.anchorB),o=S(r,s),n=m(o);if(n<1e-10)return;const a=i.length??0,c=n-a,u=g(o,1/n),l=t.invMass+e.invMass;if(l<1e-10)return;const h=i.stiffness;if(!t.isStatic){const p=c*t.invMass*h/l;t.position.x+=u.x*p,t.position.y+=u.y*p}if(!e.isStatic){const p=c*e.invMass*h/l;e.position.x-=u.x*p,e.position.y-=u.y*p}}function yt(t,e,i){const s=d(e.position,i.anchorB),r=d(t.position,i.anchorA),o=S(s,r),n=g(o,i.stiffness);t.isStatic||(k(t.position,n),t.velocity.x+=n.x*i.damping,t.velocity.y+=n.y*i.damping)}function C(t,e){t.acceleration.x+=e.x,t.acceleration.y+=e.y}function I(t,e){t.invMass<=0||(t.acceleration.x+=-e*t.velocity.x*t.invMass,t.acceleration.y+=-e*t.velocity.y*t.invMass)}function O(t,e,i){t.invMass<=0||(t.acceleration.x+=e.x*i*t.invMass,t.acceleration.y+=e.y*i*t.invMass)}function N(t,e,i,s="quadratic"){if(t.invMass<=0)return;const r=S(e,t.position),o=F(r),a=Math.max(o,100),c=Math.sqrt(a),u=g(r,1/c);let l;switch(s){case"none":l=i;break;case"linear":l=i/c;break;case"quadratic":l=i/a;break}t.acceleration.x+=u.x*l*t.invMass,t.acceleration.y+=u.y*l*t.invMass}function L(t,e,i){for(const s of e)switch(s.type){case"gravity":C(t,s.vector??i);break;case"drag":I(t,s.strength??.01);break;case"wind":s.vector&&O(t,s.vector,s.strength??1);break;case"attraction":s.vector&&N(t,s.vector,s.strength??100,s.falloff??"quadratic");break}}function xt(t){const e=[];for(let i=0;i<t.length;i++)for(let s=i+1;s<t.length;s++){const r=t[i],o=t[s];if(r.isStatic&&o.isStatic||r.isSleeping&&o.isSleeping)continue;const n=dt(r,o);n&&e.push(n)}return e}function dt(t,e){const i=t.shape.type,s=e.shape.type;if(i==="circle"&&s==="circle")return gt(t,e);if(i==="circle"&&s==="rect")return q(t,e);if(i==="rect"&&s==="circle"){const r=q(e,t);return r?{bodyA:t.id,bodyB:e.id,normal:_(r.normal),penetration:r.penetration,overlap:_(r.overlap)}:null}return i==="rect"&&s==="rect"?vt(t,e):null}function gt(t,e){if(t.shape.type!=="circle"||e.shape.type!=="circle")return null;const i=e.position.x-t.position.x,s=e.position.y-t.position.y,r=i*i+s*s,o=t.shape.radius+e.shape.radius;if(r>=o*o)return null;const n=Math.sqrt(r),a=n<1e-10?{x:0,y:1}:{x:i/n,y:s/n},c=o-n;return{bodyA:t.id,bodyB:e.id,normal:a,penetration:c,overlap:g(a,c)}}function q(t,e){if(t.shape.type!=="circle"||e.shape.type!=="rect")return null;const i=e.shape.width/2,s=e.shape.height/2,r=t.position.x-e.position.x,o=t.position.y-e.position.y,n=Math.max(-i,Math.min(i,r)),a=Math.max(-s,Math.min(s,o)),c=r-n,u=o-a,l=c*c+u*u,h=t.shape.radius;if(l>=h*h)return null;const p=Math.sqrt(l);let f,x;if(p<1e-10){const w=i-Math.abs(r),B=s-Math.abs(o);w<B?f={x:r>0?1:-1,y:0}:f={x:0,y:o>0?1:-1},x=h+Math.min(w,B)}else f={x:c/p,y:u/p},x=h-p;return{bodyA:t.id,bodyB:e.id,normal:f,penetration:x,overlap:g(f,x)}}function vt(t,e){if(t.shape.type!=="rect"||e.shape.type!=="rect")return null;const i=t.shape.width/2,s=t.shape.height/2,r=e.shape.width/2,o=e.shape.height/2,n=e.position.x-t.position.x,a=e.position.y-t.position.y,c=i+r-Math.abs(n);if(c<=0)return null;const u=s+o-Math.abs(a);if(u<=0)return null;let l,h;return c<u?(l={x:n>0?1:-1,y:0},h=c):(l={x:0,y:a>0?1:-1},h=u),{bodyA:t.id,bodyB:e.id,normal:l,penetration:h,overlap:g(l,h)}}const Mt=.5,St=.4,mt=.5;function wt(t,e,i){const{normal:s,penetration:r}=i,o=e.velocity.x-t.velocity.x,n=e.velocity.y-t.velocity.y,a=o*s.x+n*s.y;if(a>0)return;let c=Math.min(t.restitution,e.restitution);Math.abs(a)<mt&&(c=0);const u=t.invMass+e.invMass;if(u<1e-10)return;const l=-(1+c)*a/u;t.velocity.x-=l*t.invMass*s.x,t.velocity.y-=l*t.invMass*s.y,e.velocity.x+=l*e.invMass*s.x,e.velocity.y+=l*e.invMass*s.y;let h=o-a*s.x,p=n-a*s.y;const f=Math.sqrt(h*h+p*p);if(f>1e-10){h/=f,p/=f;const w=Math.sqrt(t.friction*e.friction);let M=-(o*h+n*p)/u;Math.abs(M)>Math.abs(l*w)&&(M=l*w*Math.sign(M)),t.velocity.x-=M*t.invMass*h,t.velocity.y-=M*t.invMass*p,e.velocity.x+=M*e.invMass*h,e.velocity.y+=M*e.invMass*p}const x=Math.max(r-Mt,0)*St/u;t.position.x-=x*t.invMass*s.x,t.position.y-=x*t.invMass*s.y,e.position.x+=x*e.invMass*s.x,e.position.y+=x*e.invMass*s.y}const At=.5,Et=30;function Bt(t){if(t.isStatic)return;m(t.velocity)<At?(t.sleepTimer++,t.sleepTimer>=Et&&(t.isSleeping=!0,t.velocity.x=0,t.velocity.y=0)):(t.sleepTimer=0,t.isSleeping=!1)}function v(t){t.isSleeping=!1,t.sleepTimer=0}function Tt(t,e){t.isSleeping&&!e.isSleeping&&!e.isStatic&&v(t),e.isSleeping&&!t.isSleeping&&!t.isStatic&&v(e)}class _t{constructor(e){y(this,"bodies",new Map);y(this,"constraints",new Map);y(this,"forces",[]);y(this,"config");this.config=e}step(e){const i=e/this.config.substeps;for(let s=0;s<this.config.substeps;s++){for(const n of this.constraints.values()){const a=this.bodies.get(n.bodyA),c=this.bodies.get(n.bodyB);a&&!a.isStatic&&a.isSleeping&&v(a),c&&!c.isStatic&&c.isSleeping&&v(c)}for(const n of this.bodies.values())n.isStatic||n.isSleeping||(C(n,this.config.gravity),I(n,.01),this.forces.length>0&&L(n,this.forces,this.config.gravity));for(const n of this.bodies.values())n.isStatic||n.isSleeping||ut(n,i);ht(this.constraints,this.bodies,this.config.velocityIterations);const r=Array.from(this.bodies.values()),o=xt(r);for(const n of o){const a=this.bodies.get(n.bodyA),c=this.bodies.get(n.bodyB);a&&c&&(Tt(a,c),wt(a,c,n))}this.config.bounds&&this.enforceBounds();for(const n of this.bodies.values())n.isStatic||Bt(n)}}enforceBounds(){const e=this.config.bounds;if(e)for(const i of this.bodies.values()){if(i.isStatic)continue;let s=0;i.shape.type==="circle"?s=i.shape.radius:i.shape.type==="rect"&&(s=Math.max(i.shape.width,i.shape.height)/2),i.position.x-s<e.min.x&&(i.position.x=e.min.x+s,i.velocity.x=Math.abs(i.velocity.x)*i.restitution),i.position.x+s>e.max.x&&(i.position.x=e.max.x-s,i.velocity.x=-Math.abs(i.velocity.x)*i.restitution),i.position.y-s<e.min.y&&(i.position.y=e.min.y+s,i.velocity.y=Math.abs(i.velocity.y)*i.restitution),i.position.y+s>e.max.y&&(i.position.y=e.max.y-s,i.velocity.y=-Math.abs(i.velocity.y)*i.restitution)}}addForceField(e){this.forces.push(e)}removeForceFields(e){for(let i=this.forces.length-1;i>=0;i--)this.forces[i].type===e&&this.forces.splice(i,1)}}const E=1/60,Ft=.25;class Pt{constructor(e){y(this,"world");y(this,"accumulator",0);y(this,"_alpha",0);this.world=new _t(e)}update(e){const i=Math.min(e,Ft);for(this.accumulator+=i;this.accumulator>=E;)this.world.step(E),this.accumulator-=E;this._alpha=this.accumulator/E}get alpha(){return this._alpha}getInterpolatedPosition(e){return P(e.previousPosition,e.position,this._alpha)}addBody(e){return this.world.bodies.set(e.id,e),e.id}removeBody(e){this.world.bodies.delete(e)}getBody(e){return this.world.bodies.get(e)}getBodies(){return Array.from(this.world.bodies.values())}applyImpulse(e,i){const s=this.world.bodies.get(e);!s||s.isStatic||(v(s),s.velocity.x+=i.x*s.invMass,s.velocity.y+=i.y*s.invMass)}setPosition(e,i){const s=this.world.bodies.get(e);s&&(v(s),s.position.x=i.x,s.position.y=i.y,s.previousPosition.x=i.x,s.previousPosition.y=i.y)}setVelocity(e,i){const s=this.world.bodies.get(e);!s||s.isStatic||(v(s),s.velocity.x=i.x,s.velocity.y=i.y)}addConstraint(e){return this.world.constraints.set(e.id,e),e.id}removeConstraint(e){this.world.constraints.delete(e)}getConstraints(){return Array.from(this.world.constraints.values())}addForceField(e){this.world.addForceField(e)}removeForceFields(e){this.world.removeForceFields(e)}getConfig(){return this.world.config}}const T={x:0,y:0},Ct={type:"circle",radius:10};function It(t={}){const e=t.isStatic??!1,i=e?0:t.mass??1,s=t.position??{...T};return{id:t.id??crypto.randomUUID(),position:{...s},previousPosition:t.previousPosition??{...s},velocity:t.velocity??{...T},acceleration:t.acceleration??{...T},mass:i,invMass:i>0?1/i:0,restitution:t.restitution??.5,friction:t.friction??.3,isStatic:e,isSleeping:t.isSleeping??!1,sleepTimer:t.sleepTimer??0,shape:t.shape??{...Ct},userData:t.userData}}const R={x:0,y:0};function qt(t){return{id:t.id??crypto.randomUUID(),type:t.type??"spring",bodyA:t.bodyA,bodyB:t.bodyB,anchorA:t.anchorA??{...R},anchorB:t.anchorB??{...R},stiffness:t.stiffness??.5,damping:t.damping??.1,length:t.length}}class Rt{constructor(){y(this,"svg",null);y(this,"elementMap",new Map);y(this,"constraintMap",new Map);y(this,"alpha",1)}init(e){this.svg=document.createElementNS("http://www.w3.org/2000/svg","svg"),this.svg.setAttribute("width","100%"),this.svg.setAttribute("height","100%"),this.svg.style.overflow="visible",e.appendChild(this.svg)}render(e,i){this.svg&&(this.renderConstraints(i,e),this.renderBodies(e))}destroy(){var e;(e=this.svg)==null||e.remove(),this.svg=null,this.elementMap.clear(),this.constraintMap.clear()}renderBodies(e){const i=new Set;for(const s of e){i.add(s.id);let r=this.elementMap.get(s.id);r||(r=this.createElement(s),this.elementMap.set(s.id,r),this.svg.appendChild(r));const o=this.interpolate(s);r.setAttribute("transform",`translate(${o.x}, ${o.y})`),r.setAttribute("opacity",s.isSleeping?"0.5":"1")}for(const[s,r]of this.elementMap)i.has(s)||(r.remove(),this.elementMap.delete(s))}interpolate(e){return this.alpha>=1?e.position:P(e.previousPosition,e.position,this.alpha)}createElement(e){const i="http://www.w3.org/2000/svg";switch(e.shape.type){case"circle":{const s=document.createElementNS(i,"circle");return s.setAttribute("r",String(e.shape.radius)),s.setAttribute("fill","#6366f1"),s}case"rect":{const s=document.createElementNS(i,"rect");return s.setAttribute("width",String(e.shape.width)),s.setAttribute("height",String(e.shape.height)),s.setAttribute("x",String(-e.shape.width/2)),s.setAttribute("y",String(-e.shape.height/2)),s.setAttribute("fill","#6366f1"),s}case"polygon":{const s=document.createElementNS(i,"polygon"),r=e.shape.vertices.map(o=>`${o.x},${o.y}`).join(" ");return s.setAttribute("points",r),s.setAttribute("fill","#6366f1"),s}}}renderConstraints(e,i){const s=new Map(i.map(o=>[o.id,o])),r=new Set;for(const o of e){const n=s.get(o.bodyA),a=s.get(o.bodyB);if(!n||!a)continue;r.add(o.id);let c=this.constraintMap.get(o.id);c||(c=document.createElementNS("http://www.w3.org/2000/svg","line"),c.setAttribute("stroke","#94a3b8"),c.setAttribute("stroke-width","1"),c.setAttribute("stroke-dasharray","4 2"),this.constraintMap.set(o.id,c),this.svg.insertBefore(c,this.svg.firstChild));const u=this.interpolate(n),l=this.interpolate(a),h=d(u,o.anchorA),p=d(l,o.anchorB);c.setAttribute("x1",String(h.x)),c.setAttribute("y1",String(h.y)),c.setAttribute("x2",String(p.x)),c.setAttribute("y2",String(p.y))}for(const[o,n]of this.constraintMap)r.has(o)||(n.remove(),this.constraintMap.delete(o))}}exports.PhysicsEngine=Pt;exports.SvgRenderer=Rt;exports.applyAttraction=N;exports.applyDrag=I;exports.applyForceFields=L;exports.applyGravity=C;exports.applyWind=O;exports.createBody=It;exports.createConstraint=qt;exports.vec2=lt;exports.wakeBody=v;
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcptoolshop/physics-svg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic 2D physics engine with SVG rendering for web games and simulations",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "MCP Tool Shop",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/mcp-tool-shop-org/siege-kit.git",
|
|
11
|
+
"directory": "packages/physics-svg"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/mcp-tool-shop-org/siege-kit/tree/main/packages/physics-svg#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/mcp-tool-shop-org/siege-kit/issues"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public",
|
|
19
|
+
"registry": "https://registry.npmjs.org"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.mjs",
|
|
25
|
+
"require": "./dist/index.cjs"
|
|
26
|
+
},
|
|
27
|
+
"./react": {
|
|
28
|
+
"types": "./dist/react/index.d.ts",
|
|
29
|
+
"import": "./dist/react/index.mjs",
|
|
30
|
+
"require": "./dist/react/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=18.0.0",
|
|
38
|
+
"react-dom": ">=18.0.0",
|
|
39
|
+
"gsap": ">=3.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"react": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"react-dom": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"gsap": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/react": "^19.0",
|
|
54
|
+
"@types/react-dom": "^19.0",
|
|
55
|
+
"@vitejs/plugin-react": "^4.4",
|
|
56
|
+
"react": "^19.0",
|
|
57
|
+
"react-dom": "^19.0",
|
|
58
|
+
"gsap": "^3.12",
|
|
59
|
+
"vite": "^6.2",
|
|
60
|
+
"vite-plugin-dts": "^4.5",
|
|
61
|
+
"vitest": "^3.0",
|
|
62
|
+
"typescript": "^5.7",
|
|
63
|
+
"@mcp-tool-shop/siege-types": "0.1.0",
|
|
64
|
+
"@mcp-tool-shop/tsconfig": "0.1.0"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"dev": "vite build --watch",
|
|
68
|
+
"build": "vite build",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"clean": "rm -rf dist"
|
|
72
|
+
}
|
|
73
|
+
}
|