Files
cell/source/qjs_box2d.c
John Alanbrook c014f29ce7
Some checks failed
Build and Deploy / build-macos (push) Failing after 6s
Build and Deploy / build-linux (push) Failing after 1m30s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
fill out box2d methods
2025-05-25 01:58:10 -05:00

2341 lines
76 KiB
C

#include "qjs_box2d.h"
#include "qjs_macros.h"
#include "jsffi.h"
#include "box2d/box2d.h"
#include "prosperon.h"
#include <stdlib.h>
#include <string.h>
// Forward declarations
typedef struct box2d_world box2d_world;
typedef struct box2d_body box2d_body;
typedef struct box2d_shape box2d_shape;
typedef struct box2d_joint box2d_joint;
// World wrapper
struct box2d_world {
b2WorldId id;
JSContext *js;
JSValue contact_listener;
JSValue filter_callback;
JSValue pre_solve_callback;
};
// Body wrapper
struct box2d_body {
b2BodyId id;
box2d_world *world;
};
// Shape wrapper
struct box2d_shape {
b2ShapeId id;
box2d_body *body;
};
// Joint wrapper
struct box2d_joint {
b2JointId id;
box2d_world *world;
};
// Free functions
void box2d_world_free(JSRuntime *rt, box2d_world *world)
{
if (b2World_IsValid(world->id)) {
b2DestroyWorld(world->id);
}
if (!JS_IsUndefined(world->contact_listener))
JS_FreeValueRT(rt, world->contact_listener);
if (!JS_IsUndefined(world->filter_callback))
JS_FreeValueRT(rt, world->filter_callback);
if (!JS_IsUndefined(world->pre_solve_callback))
JS_FreeValueRT(rt, world->pre_solve_callback);
free(world);
}
void box2d_body_free(JSRuntime *rt, box2d_body *body)
{
if (b2Body_IsValid(body->id)) {
b2DestroyBody(body->id);
}
free(body);
}
void box2d_shape_free(JSRuntime *rt, box2d_shape *shape)
{
if (b2Shape_IsValid(shape->id)) {
b2DestroyShape(shape->id);
}
free(shape);
}
void box2d_joint_free(JSRuntime *rt, box2d_joint *joint)
{
if (b2Joint_IsValid(joint->id)) {
b2DestroyJoint(joint->id);
}
free(joint);
}
// Define classes using QJSCLASS macro
QJSCLASS(box2d_world,)
QJSCLASS(box2d_body,)
QJSCLASS(box2d_shape,)
QJSCLASS(box2d_joint,)
// Vector conversion functions - renamed to avoid conflicts with existing js2vec2/vec22js
static b2Vec2 js2b2vec2(JSContext *js, JSValue v)
{
b2Vec2 vec = {0, 0};
if (JS_IsObject(v)) {
JSValue x_val = JS_GetPropertyStr(js, v, "x");
JSValue y_val = JS_GetPropertyStr(js, v, "y");
double x, y;
if (!JS_ToFloat64(js, &x, x_val)) vec.x = (float)x;
if (!JS_ToFloat64(js, &y, y_val)) vec.y = (float)y;
JS_FreeValue(js, x_val);
JS_FreeValue(js, y_val);
} else if (JS_IsArray(js, v)) {
JSValue x_val = JS_GetPropertyUint32(js, v, 0);
JSValue y_val = JS_GetPropertyUint32(js, v, 1);
double x, y;
if (!JS_ToFloat64(js, &x, x_val)) vec.x = (float)x;
if (!JS_ToFloat64(js, &y, y_val)) vec.y = (float)y;
JS_FreeValue(js, x_val);
JS_FreeValue(js, y_val);
}
return vec;
}
static JSValue b2vec22js(JSContext *js, b2Vec2 vec)
{
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, vec.x));
JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, vec.y));
return obj;
}
// Transform conversion
static b2Transform js2transform(JSContext *js, JSValue v)
{
b2Transform t;
t.p = (b2Vec2){0, 0};
t.q = b2Rot_identity;
if (JS_IsObject(v)) {
JSValue pos_val = JS_GetPropertyStr(js, v, "position");
JSValue angle_val = JS_GetPropertyStr(js, v, "angle");
if (!JS_IsUndefined(pos_val)) {
t.p = js2b2vec2(js, pos_val);
}
if (!JS_IsUndefined(angle_val)) {
double angle;
if (!JS_ToFloat64(js, &angle, angle_val)) {
t.q = b2MakeRot((float)angle);
}
}
JS_FreeValue(js, pos_val);
JS_FreeValue(js, angle_val);
}
return t;
}
static JSValue transform2js(JSContext *js, b2Transform t)
{
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "position", b2vec22js(js, t.p));
JS_SetPropertyStr(js, obj, "angle", JS_NewFloat64(js, b2Rot_GetAngle(t.q)));
return obj;
}
// Body type conversion
static b2BodyType js2bodytype(JSContext *js, JSValue v)
{
if (JS_IsString(v)) {
const char *str = JS_ToCString(js, v);
b2BodyType type = b2_staticBody;
if (strcmp(str, "static") == 0) {
type = b2_staticBody;
} else if (strcmp(str, "kinematic") == 0) {
type = b2_kinematicBody;
} else if (strcmp(str, "dynamic") == 0) {
type = b2_dynamicBody;
}
JS_FreeCString(js, str);
return type;
} else if (JS_IsNumber(v)) {
int type = 0;
JS_ToInt32(js, &type, v);
return (b2BodyType)type;
}
return b2_staticBody;
}
// ===== WORLD =====
// Constructor
JSC_CCALL(box2d_world_constructor,
b2WorldDef def = b2DefaultWorldDef();
// Parse options if provided
if (argc > 0 && JS_IsObject(argv[0])) {
JSValue gravity_val = JS_GetPropertyStr(js, argv[0], "gravity");
if (!JS_IsUndefined(gravity_val)) {
def.gravity = js2b2vec2(js, gravity_val);
JS_FreeValue(js, gravity_val);
}
JSValue sleep_val = JS_GetPropertyStr(js, argv[0], "enableSleep");
if (!JS_IsUndefined(sleep_val)) {
def.enableSleep = JS_ToBool(js, sleep_val);
JS_FreeValue(js, sleep_val);
}
}
box2d_world *world = malloc(sizeof(*world));
world->id = b2CreateWorld(&def);
world->js = js;
world->contact_listener = JS_UNDEFINED;
world->filter_callback = JS_UNDEFINED;
world->pre_solve_callback = JS_UNDEFINED;
return box2d_world2js(js, world);
)
// World.prototype.createBody()
JSC_CCALL(box2d_world_createBody,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
b2BodyDef def = b2DefaultBodyDef();
// Parse body definition
if (argc > 0 && JS_IsObject(argv[0])) {
JSValue type_val = JS_GetPropertyStr(js, argv[0], "type");
if (!JS_IsUndefined(type_val)) {
def.type = js2bodytype(js, type_val);
JS_FreeValue(js, type_val);
}
JSValue pos_val = JS_GetPropertyStr(js, argv[0], "position");
if (!JS_IsUndefined(pos_val)) {
def.position = js2b2vec2(js, pos_val);
JS_FreeValue(js, pos_val);
}
JSValue angle_val = JS_GetPropertyStr(js, argv[0], "angle");
if (!JS_IsUndefined(angle_val)) {
double angle;
if (!JS_ToFloat64(js, &angle, angle_val)) {
def.rotation = b2MakeRot((float)angle);
}
JS_FreeValue(js, angle_val);
}
JSValue vel_val = JS_GetPropertyStr(js, argv[0], "linearVelocity");
if (!JS_IsUndefined(vel_val)) {
def.linearVelocity = js2b2vec2(js, vel_val);
JS_FreeValue(js, vel_val);
}
JSValue angvel_val = JS_GetPropertyStr(js, argv[0], "angularVelocity");
if (!JS_IsUndefined(angvel_val)) {
double av;
if (!JS_ToFloat64(js, &av, angvel_val)) {
def.angularVelocity = (float)av;
}
JS_FreeValue(js, angvel_val);
}
JSValue awake_val = JS_GetPropertyStr(js, argv[0], "isAwake");
if (!JS_IsUndefined(awake_val)) {
def.isAwake = JS_ToBool(js, awake_val);
JS_FreeValue(js, awake_val);
}
JSValue enabled_val = JS_GetPropertyStr(js, argv[0], "isEnabled");
if (!JS_IsUndefined(enabled_val)) {
def.isEnabled = JS_ToBool(js, enabled_val);
JS_FreeValue(js, enabled_val);
}
}
box2d_body *body = malloc(sizeof(*body));
body->id = b2CreateBody(world->id, &def);
body->world = world;
return box2d_body2js(js, body);
)
// World.prototype.step()
JSC_CCALL(box2d_world_step,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
double timestep = 1.0/60.0;
int substeps = 4;
if (argc > 0) JS_ToFloat64(js, &timestep, argv[0]);
if (argc > 1) JS_ToInt32(js, &substeps, argv[1]);
b2World_Step(world->id, (float)timestep, substeps);
)
// World.prototype.rayCast()
JSC_CCALL(box2d_world_rayCast,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 2) return JS_ThrowTypeError(js, "rayCast requires origin and direction");
b2Vec2 origin = js2b2vec2(js, argv[0]);
b2Vec2 direction = js2b2vec2(js, argv[1]);
float maxFraction = 1.0f;
if (argc > 2) {
double mf;
JS_ToFloat64(js, &mf, argv[2]);
maxFraction = (float)mf;
}
b2QueryFilter filter = b2DefaultQueryFilter();
b2RayResult result = b2World_CastRayClosest(world->id, origin, direction, filter);
ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "hit", JS_NewBool(js, result.hit));
if (result.hit) {
JS_SetPropertyStr(js, ret, "point", b2vec22js(js, result.point));
JS_SetPropertyStr(js, ret, "normal", b2vec22js(js, result.normal));
JS_SetPropertyStr(js, ret, "fraction", JS_NewFloat64(js, result.fraction));
}
return ret;
)
// World.prototype.gravity getter
JSValue js_box2d_world_get_gravity(JSContext *js, JSValueConst self)
{
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
b2Vec2 gravity = b2World_GetGravity(world->id);
return b2vec22js(js, gravity);
}
// World.prototype.gravity setter
JSValue js_box2d_world_set_gravity(JSContext *js, JSValueConst self, JSValue val)
{
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
b2Vec2 gravity = js2b2vec2(js, val);
b2World_SetGravity(world->id, gravity);
return JS_UNDEFINED;
}
// ===== BODY =====
// Body.prototype.createCircleShape()
JSC_CCALL(box2d_body_createCircleShape,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2ShapeDef shape_def = b2DefaultShapeDef();
b2Circle circle = {0};
circle.center = (b2Vec2){0, 0};
circle.radius = 0.5f;
if (argc > 0 && JS_IsObject(argv[0])) {
JSValue radius_val = JS_GetPropertyStr(js, argv[0], "radius");
if (!JS_IsUndefined(radius_val)) {
double r;
if (!JS_ToFloat64(js, &r, radius_val)) {
circle.radius = (float)r;
}
JS_FreeValue(js, radius_val);
}
JSValue center_val = JS_GetPropertyStr(js, argv[0], "center");
if (!JS_IsUndefined(center_val)) {
circle.center = js2b2vec2(js, center_val);
JS_FreeValue(js, center_val);
}
JSValue density_val = JS_GetPropertyStr(js, argv[0], "density");
if (!JS_IsUndefined(density_val)) {
double d;
if (!JS_ToFloat64(js, &d, density_val)) {
shape_def.density = (float)d;
}
JS_FreeValue(js, density_val);
}
JSValue friction_val = JS_GetPropertyStr(js, argv[0], "friction");
if (!JS_IsUndefined(friction_val)) {
double f;
if (!JS_ToFloat64(js, &f, friction_val)) {
shape_def.friction = (float)f;
}
JS_FreeValue(js, friction_val);
}
JSValue restitution_val = JS_GetPropertyStr(js, argv[0], "restitution");
if (!JS_IsUndefined(restitution_val)) {
double r;
if (!JS_ToFloat64(js, &r, restitution_val)) {
shape_def.restitution = (float)r;
}
JS_FreeValue(js, restitution_val);
}
JSValue sensor_val = JS_GetPropertyStr(js, argv[0], "isSensor");
if (!JS_IsUndefined(sensor_val)) {
shape_def.isSensor = JS_ToBool(js, sensor_val);
JS_FreeValue(js, sensor_val);
}
}
box2d_shape *shape = malloc(sizeof(*shape));
shape->id = b2CreateCircleShape(body->id, &shape_def, &circle);
shape->body = body;
return box2d_shape2js(js, shape);
)
// Body.prototype.createBoxShape()
JSC_CCALL(box2d_body_createBoxShape,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2ShapeDef shape_def = b2DefaultShapeDef();
float width = 1.0f, height = 1.0f;
b2Vec2 center = {0, 0};
float angle = 0;
if (argc > 0 && JS_IsObject(argv[0])) {
JSValue width_val = JS_GetPropertyStr(js, argv[0], "width");
if (!JS_IsUndefined(width_val)) {
double w;
if (!JS_ToFloat64(js, &w, width_val)) {
width = (float)w;
}
JS_FreeValue(js, width_val);
}
JSValue height_val = JS_GetPropertyStr(js, argv[0], "height");
if (!JS_IsUndefined(height_val)) {
double h;
if (!JS_ToFloat64(js, &h, height_val)) {
height = (float)h;
}
JS_FreeValue(js, height_val);
}
JSValue center_val = JS_GetPropertyStr(js, argv[0], "center");
if (!JS_IsUndefined(center_val)) {
center = js2b2vec2(js, center_val);
JS_FreeValue(js, center_val);
}
JSValue angle_val = JS_GetPropertyStr(js, argv[0], "angle");
if (!JS_IsUndefined(angle_val)) {
double a;
if (!JS_ToFloat64(js, &a, angle_val)) {
angle = (float)a;
}
JS_FreeValue(js, angle_val);
}
// Common shape properties
JSValue density_val = JS_GetPropertyStr(js, argv[0], "density");
if (!JS_IsUndefined(density_val)) {
double d;
if (!JS_ToFloat64(js, &d, density_val)) {
shape_def.density = (float)d;
}
JS_FreeValue(js, density_val);
}
JSValue friction_val = JS_GetPropertyStr(js, argv[0], "friction");
if (!JS_IsUndefined(friction_val)) {
double f;
if (!JS_ToFloat64(js, &f, friction_val)) {
shape_def.friction = (float)f;
}
JS_FreeValue(js, friction_val);
}
JSValue restitution_val = JS_GetPropertyStr(js, argv[0], "restitution");
if (!JS_IsUndefined(restitution_val)) {
double r;
if (!JS_ToFloat64(js, &r, restitution_val)) {
shape_def.restitution = (float)r;
}
JS_FreeValue(js, restitution_val);
}
}
b2Polygon polygon = b2MakeOffsetBox(width * 0.5f, height * 0.5f, center, angle);
box2d_shape *shape = malloc(sizeof(*shape));
shape->id = b2CreatePolygonShape(body->id, &shape_def, &polygon);
shape->body = body;
return box2d_shape2js(js, shape);
)
// Body.prototype.createSegmentShape()
JSC_CCALL(box2d_body_createSegmentShape,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2ShapeDef shape_def = b2DefaultShapeDef();
b2Segment segment;
if (argc < 2) {
return JS_ThrowTypeError(js, "Segment shape requires two points");
}
segment.point1 = js2b2vec2(js, argv[0]);
segment.point2 = js2b2vec2(js, argv[1]);
if (argc > 2 && JS_IsObject(argv[2])) {
JSValue density_val = JS_GetPropertyStr(js, argv[2], "density");
if (!JS_IsUndefined(density_val)) {
double d;
if (!JS_ToFloat64(js, &d, density_val)) {
shape_def.density = (float)d;
}
JS_FreeValue(js, density_val);
}
JSValue friction_val = JS_GetPropertyStr(js, argv[2], "friction");
if (!JS_IsUndefined(friction_val)) {
double f;
if (!JS_ToFloat64(js, &f, friction_val)) {
shape_def.friction = (float)f;
}
JS_FreeValue(js, friction_val);
}
JSValue restitution_val = JS_GetPropertyStr(js, argv[2], "restitution");
if (!JS_IsUndefined(restitution_val)) {
double r;
if (!JS_ToFloat64(js, &r, restitution_val)) {
shape_def.restitution = (float)r;
}
JS_FreeValue(js, restitution_val);
}
}
box2d_shape *shape = malloc(sizeof(*shape));
shape->id = b2CreateSegmentShape(body->id, &shape_def, &segment);
shape->body = body;
return box2d_shape2js(js, shape);
)
// Body.prototype.createCapsuleShape()
JSC_CCALL(box2d_body_createCapsuleShape,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2ShapeDef shape_def = b2DefaultShapeDef();
b2Capsule capsule;
if (argc > 0 && JS_IsObject(argv[0])) {
JSValue center1_val = JS_GetPropertyStr(js, argv[0], "center1");
if (!JS_IsUndefined(center1_val)) {
capsule.center1 = js2b2vec2(js, center1_val);
JS_FreeValue(js, center1_val);
} else {
capsule.center1 = (b2Vec2){0, -0.5f};
}
JSValue center2_val = JS_GetPropertyStr(js, argv[0], "center2");
if (!JS_IsUndefined(center2_val)) {
capsule.center2 = js2b2vec2(js, center2_val);
JS_FreeValue(js, center2_val);
} else {
capsule.center2 = (b2Vec2){0, 0.5f};
}
JSValue radius_val = JS_GetPropertyStr(js, argv[0], "radius");
if (!JS_IsUndefined(radius_val)) {
double r;
if (!JS_ToFloat64(js, &r, radius_val)) {
capsule.radius = (float)r;
}
JS_FreeValue(js, radius_val);
} else {
capsule.radius = 0.5f;
}
// Common shape properties
JSValue density_val = JS_GetPropertyStr(js, argv[0], "density");
if (!JS_IsUndefined(density_val)) {
double d;
if (!JS_ToFloat64(js, &d, density_val)) {
shape_def.density = (float)d;
}
JS_FreeValue(js, density_val);
}
JSValue friction_val = JS_GetPropertyStr(js, argv[0], "friction");
if (!JS_IsUndefined(friction_val)) {
double f;
if (!JS_ToFloat64(js, &f, friction_val)) {
shape_def.friction = (float)f;
}
JS_FreeValue(js, friction_val);
}
JSValue restitution_val = JS_GetPropertyStr(js, argv[0], "restitution");
if (!JS_IsUndefined(restitution_val)) {
double r;
if (!JS_ToFloat64(js, &r, restitution_val)) {
shape_def.restitution = (float)r;
}
JS_FreeValue(js, restitution_val);
}
} else {
// Default capsule
capsule.center1 = (b2Vec2){0, -0.5f};
capsule.center2 = (b2Vec2){0, 0.5f};
capsule.radius = 0.5f;
}
box2d_shape *shape = malloc(sizeof(*shape));
shape->id = b2CreateCapsuleShape(body->id, &shape_def, &capsule);
shape->body = body;
return box2d_shape2js(js, shape);
)
// Body.prototype.createPolygonShape()
JSC_CCALL(box2d_body_createPolygonShape,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2ShapeDef shape_def = b2DefaultShapeDef();
b2Polygon polygon;
if (argc < 1 || !JS_IsArray(js, argv[0])) {
return JS_ThrowTypeError(js, "Polygon shape requires an array of points");
}
uint32_t length;
JSValue len_val = JS_GetPropertyStr(js, argv[0], "length");
if (JS_ToUint32(js, &length, len_val)) {
JS_FreeValue(js, len_val);
return JS_EXCEPTION;
}
JS_FreeValue(js, len_val);
if (length < 3 || length > b2_maxPolygonVertices) {
return JS_ThrowRangeError(js, "Polygon must have between 3 and %d vertices", b2_maxPolygonVertices);
}
b2Vec2 points[b2_maxPolygonVertices];
for (uint32_t i = 0; i < length; i++) {
JSValue point_val = JS_GetPropertyUint32(js, argv[0], i);
points[i] = js2b2vec2(js, point_val);
JS_FreeValue(js, point_val);
}
polygon = b2MakePolygon(points, (int)length);
if (argc > 1 && JS_IsObject(argv[1])) {
JSValue density_val = JS_GetPropertyStr(js, argv[1], "density");
if (!JS_IsUndefined(density_val)) {
double d;
if (!JS_ToFloat64(js, &d, density_val)) {
shape_def.density = (float)d;
}
JS_FreeValue(js, density_val);
}
JSValue friction_val = JS_GetPropertyStr(js, argv[1], "friction");
if (!JS_IsUndefined(friction_val)) {
double f;
if (!JS_ToFloat64(js, &f, friction_val)) {
shape_def.friction = (float)f;
}
JS_FreeValue(js, friction_val);
}
JSValue restitution_val = JS_GetPropertyStr(js, argv[1], "restitution");
if (!JS_IsUndefined(restitution_val)) {
double r;
if (!JS_ToFloat64(js, &r, restitution_val)) {
shape_def.restitution = (float)r;
}
JS_FreeValue(js, restitution_val);
}
}
box2d_shape *shape = malloc(sizeof(*shape));
shape->id = b2CreatePolygonShape(body->id, &shape_def, &polygon);
shape->body = body;
return box2d_shape2js(js, shape);
)
// Body.prototype.position getter
JSValue js_box2d_body_get_position(JSContext *js, JSValueConst self)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Vec2 pos = b2Body_GetPosition(body->id);
return b2vec22js(js, pos);
}
// Body.prototype.position setter
JSValue js_box2d_body_set_position(JSContext *js, JSValueConst self, JSValue val)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Vec2 pos = js2b2vec2(js, val);
b2Rot rot = b2Body_GetRotation(body->id);
b2Body_SetTransform(body->id, pos, rot);
return JS_UNDEFINED;
}
// Body.prototype.angle getter
JSValue js_box2d_body_get_angle(JSContext *js, JSValueConst self)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Rot rot = b2Body_GetRotation(body->id);
return JS_NewFloat64(js, b2Rot_GetAngle(rot));
}
// Body.prototype.angle setter
JSValue js_box2d_body_set_angle(JSContext *js, JSValueConst self, JSValue val)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
double angle;
if (JS_ToFloat64(js, &angle, val)) return JS_EXCEPTION;
b2Vec2 pos = b2Body_GetPosition(body->id);
b2Body_SetTransform(body->id, pos, b2MakeRot((float)angle));
return JS_UNDEFINED;
}
// Body.prototype.linearVelocity getter
JSValue js_box2d_body_get_linearVelocity(JSContext *js, JSValueConst self)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Vec2 vel = b2Body_GetLinearVelocity(body->id);
return b2vec22js(js, vel);
}
// Body.prototype.linearVelocity setter
JSValue js_box2d_body_set_linearVelocity(JSContext *js, JSValueConst self, JSValue val)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Vec2 vel = js2b2vec2(js, val);
b2Body_SetLinearVelocity(body->id, vel);
return JS_UNDEFINED;
}
// Body.prototype.angularVelocity getter
JSValue js_box2d_body_get_angularVelocity(JSContext *js, JSValueConst self)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
return JS_NewFloat64(js, b2Body_GetAngularVelocity(body->id));
}
// Body.prototype.angularVelocity setter
JSValue js_box2d_body_set_angularVelocity(JSContext *js, JSValueConst self, JSValue val)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
double av;
if (JS_ToFloat64(js, &av, val)) return JS_EXCEPTION;
b2Body_SetAngularVelocity(body->id, (float)av);
return JS_UNDEFINED;
}
// Body.prototype.type getter
JSValue js_box2d_body_get_type(JSContext *js, JSValueConst self)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
return JS_NewInt32(js, b2Body_GetType(body->id));
}
// Body.prototype.type setter
JSValue js_box2d_body_set_type(JSContext *js, JSValueConst self, JSValue val)
{
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Body_SetType(body->id, js2bodytype(js, val));
return JS_UNDEFINED;
}
// Body.prototype.getMass()
JSC_CCALL(box2d_body_getMass,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
return JS_NewFloat64(js, b2Body_GetMass(body->id));
)
// Body.prototype.applyForce()
JSC_CCALL(box2d_body_applyForce,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Vec2 force = js2b2vec2(js, argv[0]);
b2Vec2 point = b2Body_GetWorldCenterOfMass(body->id);
if (argc > 1) {
point = js2b2vec2(js, argv[1]);
}
b2Body_ApplyForce(body->id, force, point, true);
)
// Body.prototype.applyImpulse()
JSC_CCALL(box2d_body_applyImpulse,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Vec2 impulse = js2b2vec2(js, argv[0]);
b2Vec2 point = b2Body_GetWorldCenterOfMass(body->id);
if (argc > 1) {
point = js2b2vec2(js, argv[1]);
}
b2Body_ApplyLinearImpulse(body->id, impulse, point, true);
)
// Body.prototype.applyLinearImpulse() - alias for applyImpulse
JSC_CCALL(box2d_body_applyLinearImpulse,
box2d_body *body = js2box2d_body(js, self);
if (!b2Body_IsValid(body->id)) return JS_ThrowReferenceError(js, "Body is invalid");
b2Vec2 impulse = js2b2vec2(js, argv[0]);
b2Vec2 point = b2Body_GetWorldCenterOfMass(body->id);
if (argc > 1) {
point = js2b2vec2(js, argv[1]);
}
b2Body_ApplyLinearImpulse(body->id, impulse, point, true);
)
// ===== JOINT =====
// World.prototype.createDistanceJoint()
JSC_CCALL(box2d_world_createDistanceJoint,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Joint definition object required");
}
b2DistanceJointDef def = b2DefaultDistanceJointDef();
JSValue bodyA_val = JS_GetPropertyStr(js, argv[0], "bodyA");
JSValue bodyB_val = JS_GetPropertyStr(js, argv[0], "bodyB");
if (!JS_IsObject(bodyA_val) || !JS_IsObject(bodyB_val)) {
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
return JS_ThrowTypeError(js, "bodyA and bodyB are required");
}
box2d_body *bodyA = js2box2d_body(js, bodyA_val);
box2d_body *bodyB = js2box2d_body(js, bodyB_val);
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
if (!bodyA || !bodyB) {
return JS_ThrowTypeError(js, "Invalid body objects");
}
def.bodyIdA = bodyA->id;
def.bodyIdB = bodyB->id;
// Anchor points
JSValue anchorA_val = JS_GetPropertyStr(js, argv[0], "localAnchorA");
if (!JS_IsUndefined(anchorA_val)) {
def.localAnchorA = js2b2vec2(js, anchorA_val);
JS_FreeValue(js, anchorA_val);
}
JSValue anchorB_val = JS_GetPropertyStr(js, argv[0], "localAnchorB");
if (!JS_IsUndefined(anchorB_val)) {
def.localAnchorB = js2b2vec2(js, anchorB_val);
JS_FreeValue(js, anchorB_val);
}
// Length
JSValue length_val = JS_GetPropertyStr(js, argv[0], "length");
if (!JS_IsUndefined(length_val)) {
double len;
if (!JS_ToFloat64(js, &len, length_val)) {
def.length = (float)len;
}
JS_FreeValue(js, length_val);
}
// Spring settings
JSValue hertz_val = JS_GetPropertyStr(js, argv[0], "hertz");
if (!JS_IsUndefined(hertz_val)) {
double hz;
if (!JS_ToFloat64(js, &hz, hertz_val)) {
def.hertz = (float)hz;
}
JS_FreeValue(js, hertz_val);
}
JSValue damping_val = JS_GetPropertyStr(js, argv[0], "dampingRatio");
if (!JS_IsUndefined(damping_val)) {
double d;
if (!JS_ToFloat64(js, &d, damping_val)) {
def.dampingRatio = (float)d;
}
JS_FreeValue(js, damping_val);
}
box2d_joint *joint = malloc(sizeof(*joint));
joint->id = b2CreateDistanceJoint(world->id, &def);
joint->world = world;
return box2d_joint2js(js, joint);
)
// World.prototype.createRevoluteJoint()
JSC_CCALL(box2d_world_createRevoluteJoint,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Joint definition object required");
}
b2RevoluteJointDef def = b2DefaultRevoluteJointDef();
JSValue bodyA_val = JS_GetPropertyStr(js, argv[0], "bodyA");
JSValue bodyB_val = JS_GetPropertyStr(js, argv[0], "bodyB");
if (!JS_IsObject(bodyA_val) || !JS_IsObject(bodyB_val)) {
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
return JS_ThrowTypeError(js, "bodyA and bodyB are required");
}
box2d_body *bodyA = js2box2d_body(js, bodyA_val);
box2d_body *bodyB = js2box2d_body(js, bodyB_val);
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
if (!bodyA || !bodyB) {
return JS_ThrowTypeError(js, "Invalid body objects");
}
def.bodyIdA = bodyA->id;
def.bodyIdB = bodyB->id;
// Anchor points
JSValue anchorA_val = JS_GetPropertyStr(js, argv[0], "localAnchorA");
if (!JS_IsUndefined(anchorA_val)) {
def.localAnchorA = js2b2vec2(js, anchorA_val);
JS_FreeValue(js, anchorA_val);
}
JSValue anchorB_val = JS_GetPropertyStr(js, argv[0], "localAnchorB");
if (!JS_IsUndefined(anchorB_val)) {
def.localAnchorB = js2b2vec2(js, anchorB_val);
JS_FreeValue(js, anchorB_val);
}
// Limits
JSValue enableLimit_val = JS_GetPropertyStr(js, argv[0], "enableLimit");
if (!JS_IsUndefined(enableLimit_val)) {
def.enableLimit = JS_ToBool(js, enableLimit_val);
JS_FreeValue(js, enableLimit_val);
}
JSValue lowerAngle_val = JS_GetPropertyStr(js, argv[0], "lowerAngle");
if (!JS_IsUndefined(lowerAngle_val)) {
double angle;
if (!JS_ToFloat64(js, &angle, lowerAngle_val)) {
def.lowerAngle = (float)angle;
}
JS_FreeValue(js, lowerAngle_val);
}
JSValue upperAngle_val = JS_GetPropertyStr(js, argv[0], "upperAngle");
if (!JS_IsUndefined(upperAngle_val)) {
double angle;
if (!JS_ToFloat64(js, &angle, upperAngle_val)) {
def.upperAngle = (float)angle;
}
JS_FreeValue(js, upperAngle_val);
}
// Motor
JSValue enableMotor_val = JS_GetPropertyStr(js, argv[0], "enableMotor");
if (!JS_IsUndefined(enableMotor_val)) {
def.enableMotor = JS_ToBool(js, enableMotor_val);
JS_FreeValue(js, enableMotor_val);
}
JSValue motorSpeed_val = JS_GetPropertyStr(js, argv[0], "motorSpeed");
if (!JS_IsUndefined(motorSpeed_val)) {
double speed;
if (!JS_ToFloat64(js, &speed, motorSpeed_val)) {
def.motorSpeed = (float)speed;
}
JS_FreeValue(js, motorSpeed_val);
}
JSValue maxMotorTorque_val = JS_GetPropertyStr(js, argv[0], "maxMotorTorque");
if (!JS_IsUndefined(maxMotorTorque_val)) {
double torque;
if (!JS_ToFloat64(js, &torque, maxMotorTorque_val)) {
def.maxMotorTorque = (float)torque;
}
JS_FreeValue(js, maxMotorTorque_val);
}
box2d_joint *joint = malloc(sizeof(*joint));
joint->id = b2CreateRevoluteJoint(world->id, &def);
joint->world = world;
return box2d_joint2js(js, joint);
)
// World.prototype.createMotorJoint()
JSC_CCALL(box2d_world_createMotorJoint,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Joint definition object required");
}
b2MotorJointDef def = b2DefaultMotorJointDef();
JSValue bodyA_val = JS_GetPropertyStr(js, argv[0], "bodyA");
JSValue bodyB_val = JS_GetPropertyStr(js, argv[0], "bodyB");
if (!JS_IsObject(bodyA_val) || !JS_IsObject(bodyB_val)) {
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
return JS_ThrowTypeError(js, "bodyA and bodyB are required");
}
box2d_body *bodyA = js2box2d_body(js, bodyA_val);
box2d_body *bodyB = js2box2d_body(js, bodyB_val);
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
if (!bodyA || !bodyB) {
return JS_ThrowTypeError(js, "Invalid body objects");
}
def.bodyIdA = bodyA->id;
def.bodyIdB = bodyB->id;
JSValue linearOffset_val = JS_GetPropertyStr(js, argv[0], "linearOffset");
if (!JS_IsUndefined(linearOffset_val)) {
def.linearOffset = js2b2vec2(js, linearOffset_val);
JS_FreeValue(js, linearOffset_val);
}
JSValue angularOffset_val = JS_GetPropertyStr(js, argv[0], "angularOffset");
if (!JS_IsUndefined(angularOffset_val)) {
double angle;
if (!JS_ToFloat64(js, &angle, angularOffset_val)) {
def.angularOffset = (float)angle;
}
JS_FreeValue(js, angularOffset_val);
}
JSValue maxForce_val = JS_GetPropertyStr(js, argv[0], "maxForce");
if (!JS_IsUndefined(maxForce_val)) {
double force;
if (!JS_ToFloat64(js, &force, maxForce_val)) {
def.maxForce = (float)force;
}
JS_FreeValue(js, maxForce_val);
}
JSValue maxTorque_val = JS_GetPropertyStr(js, argv[0], "maxTorque");
if (!JS_IsUndefined(maxTorque_val)) {
double torque;
if (!JS_ToFloat64(js, &torque, maxTorque_val)) {
def.maxTorque = (float)torque;
}
JS_FreeValue(js, maxTorque_val);
}
JSValue correctionFactor_val = JS_GetPropertyStr(js, argv[0], "correctionFactor");
if (!JS_IsUndefined(correctionFactor_val)) {
double factor;
if (!JS_ToFloat64(js, &factor, correctionFactor_val)) {
def.correctionFactor = (float)factor;
}
JS_FreeValue(js, correctionFactor_val);
}
box2d_joint *joint = malloc(sizeof(*joint));
joint->id = b2CreateMotorJoint(world->id, &def);
joint->world = world;
return box2d_joint2js(js, joint);
)
// World.prototype.createMouseJoint()
JSC_CCALL(box2d_world_createMouseJoint,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Joint definition object required");
}
b2MouseJointDef def = b2DefaultMouseJointDef();
JSValue bodyA_val = JS_GetPropertyStr(js, argv[0], "bodyA");
JSValue bodyB_val = JS_GetPropertyStr(js, argv[0], "bodyB");
if (!JS_IsObject(bodyA_val) || !JS_IsObject(bodyB_val)) {
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
return JS_ThrowTypeError(js, "bodyA and bodyB are required");
}
box2d_body *bodyA = js2box2d_body(js, bodyA_val);
box2d_body *bodyB = js2box2d_body(js, bodyB_val);
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
if (!bodyA || !bodyB) {
return JS_ThrowTypeError(js, "Invalid body objects");
}
def.bodyIdA = bodyA->id;
def.bodyIdB = bodyB->id;
JSValue target_val = JS_GetPropertyStr(js, argv[0], "target");
if (!JS_IsUndefined(target_val)) {
def.target = js2b2vec2(js, target_val);
JS_FreeValue(js, target_val);
}
JSValue hertz_val = JS_GetPropertyStr(js, argv[0], "hertz");
if (!JS_IsUndefined(hertz_val)) {
double hz;
if (!JS_ToFloat64(js, &hz, hertz_val)) {
def.hertz = (float)hz;
}
JS_FreeValue(js, hertz_val);
}
JSValue dampingRatio_val = JS_GetPropertyStr(js, argv[0], "dampingRatio");
if (!JS_IsUndefined(dampingRatio_val)) {
double d;
if (!JS_ToFloat64(js, &d, dampingRatio_val)) {
def.dampingRatio = (float)d;
}
JS_FreeValue(js, dampingRatio_val);
}
JSValue maxForce_val = JS_GetPropertyStr(js, argv[0], "maxForce");
if (!JS_IsUndefined(maxForce_val)) {
double force;
if (!JS_ToFloat64(js, &force, maxForce_val)) {
def.maxForce = (float)force;
}
JS_FreeValue(js, maxForce_val);
}
box2d_joint *joint = malloc(sizeof(*joint));
joint->id = b2CreateMouseJoint(world->id, &def);
joint->world = world;
return box2d_joint2js(js, joint);
)
// World.prototype.createPrismaticJoint()
JSC_CCALL(box2d_world_createPrismaticJoint,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Joint definition object required");
}
b2PrismaticJointDef def = b2DefaultPrismaticJointDef();
JSValue bodyA_val = JS_GetPropertyStr(js, argv[0], "bodyA");
JSValue bodyB_val = JS_GetPropertyStr(js, argv[0], "bodyB");
if (!JS_IsObject(bodyA_val) || !JS_IsObject(bodyB_val)) {
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
return JS_ThrowTypeError(js, "bodyA and bodyB are required");
}
box2d_body *bodyA = js2box2d_body(js, bodyA_val);
box2d_body *bodyB = js2box2d_body(js, bodyB_val);
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
if (!bodyA || !bodyB) {
return JS_ThrowTypeError(js, "Invalid body objects");
}
def.bodyIdA = bodyA->id;
def.bodyIdB = bodyB->id;
JSValue localAnchorA_val = JS_GetPropertyStr(js, argv[0], "localAnchorA");
if (!JS_IsUndefined(localAnchorA_val)) {
def.localAnchorA = js2b2vec2(js, localAnchorA_val);
JS_FreeValue(js, localAnchorA_val);
}
JSValue localAnchorB_val = JS_GetPropertyStr(js, argv[0], "localAnchorB");
if (!JS_IsUndefined(localAnchorB_val)) {
def.localAnchorB = js2b2vec2(js, localAnchorB_val);
JS_FreeValue(js, localAnchorB_val);
}
JSValue localAxisA_val = JS_GetPropertyStr(js, argv[0], "localAxisA");
if (!JS_IsUndefined(localAxisA_val)) {
def.localAxisA = js2b2vec2(js, localAxisA_val);
JS_FreeValue(js, localAxisA_val);
}
JSValue enableLimit_val = JS_GetPropertyStr(js, argv[0], "enableLimit");
if (!JS_IsUndefined(enableLimit_val)) {
def.enableLimit = JS_ToBool(js, enableLimit_val);
JS_FreeValue(js, enableLimit_val);
}
JSValue lowerTranslation_val = JS_GetPropertyStr(js, argv[0], "lowerTranslation");
if (!JS_IsUndefined(lowerTranslation_val)) {
double trans;
if (!JS_ToFloat64(js, &trans, lowerTranslation_val)) {
def.lowerTranslation = (float)trans;
}
JS_FreeValue(js, lowerTranslation_val);
}
JSValue upperTranslation_val = JS_GetPropertyStr(js, argv[0], "upperTranslation");
if (!JS_IsUndefined(upperTranslation_val)) {
double trans;
if (!JS_ToFloat64(js, &trans, upperTranslation_val)) {
def.upperTranslation = (float)trans;
}
JS_FreeValue(js, upperTranslation_val);
}
JSValue enableMotor_val = JS_GetPropertyStr(js, argv[0], "enableMotor");
if (!JS_IsUndefined(enableMotor_val)) {
def.enableMotor = JS_ToBool(js, enableMotor_val);
JS_FreeValue(js, enableMotor_val);
}
JSValue motorSpeed_val = JS_GetPropertyStr(js, argv[0], "motorSpeed");
if (!JS_IsUndefined(motorSpeed_val)) {
double speed;
if (!JS_ToFloat64(js, &speed, motorSpeed_val)) {
def.motorSpeed = (float)speed;
}
JS_FreeValue(js, motorSpeed_val);
}
JSValue maxMotorForce_val = JS_GetPropertyStr(js, argv[0], "maxMotorForce");
if (!JS_IsUndefined(maxMotorForce_val)) {
double force;
if (!JS_ToFloat64(js, &force, maxMotorForce_val)) {
def.maxMotorForce = (float)force;
}
JS_FreeValue(js, maxMotorForce_val);
}
box2d_joint *joint = malloc(sizeof(*joint));
joint->id = b2CreatePrismaticJoint(world->id, &def);
joint->world = world;
return box2d_joint2js(js, joint);
)
// World.prototype.createWeldJoint()
JSC_CCALL(box2d_world_createWeldJoint,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Joint definition object required");
}
b2WeldJointDef def = b2DefaultWeldJointDef();
JSValue bodyA_val = JS_GetPropertyStr(js, argv[0], "bodyA");
JSValue bodyB_val = JS_GetPropertyStr(js, argv[0], "bodyB");
if (!JS_IsObject(bodyA_val) || !JS_IsObject(bodyB_val)) {
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
return JS_ThrowTypeError(js, "bodyA and bodyB are required");
}
box2d_body *bodyA = js2box2d_body(js, bodyA_val);
box2d_body *bodyB = js2box2d_body(js, bodyB_val);
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
if (!bodyA || !bodyB) {
return JS_ThrowTypeError(js, "Invalid body objects");
}
def.bodyIdA = bodyA->id;
def.bodyIdB = bodyB->id;
JSValue localAnchorA_val = JS_GetPropertyStr(js, argv[0], "localAnchorA");
if (!JS_IsUndefined(localAnchorA_val)) {
def.localAnchorA = js2b2vec2(js, localAnchorA_val);
JS_FreeValue(js, localAnchorA_val);
}
JSValue localAnchorB_val = JS_GetPropertyStr(js, argv[0], "localAnchorB");
if (!JS_IsUndefined(localAnchorB_val)) {
def.localAnchorB = js2b2vec2(js, localAnchorB_val);
JS_FreeValue(js, localAnchorB_val);
}
JSValue referenceAngle_val = JS_GetPropertyStr(js, argv[0], "referenceAngle");
if (!JS_IsUndefined(referenceAngle_val)) {
double angle;
if (!JS_ToFloat64(js, &angle, referenceAngle_val)) {
def.referenceAngle = (float)angle;
}
JS_FreeValue(js, referenceAngle_val);
}
JSValue linearHertz_val = JS_GetPropertyStr(js, argv[0], "linearHertz");
if (!JS_IsUndefined(linearHertz_val)) {
double hz;
if (!JS_ToFloat64(js, &hz, linearHertz_val)) {
def.linearHertz = (float)hz;
}
JS_FreeValue(js, linearHertz_val);
}
JSValue angularHertz_val = JS_GetPropertyStr(js, argv[0], "angularHertz");
if (!JS_IsUndefined(angularHertz_val)) {
double hz;
if (!JS_ToFloat64(js, &hz, angularHertz_val)) {
def.angularHertz = (float)hz;
}
JS_FreeValue(js, angularHertz_val);
}
JSValue linearDampingRatio_val = JS_GetPropertyStr(js, argv[0], "linearDampingRatio");
if (!JS_IsUndefined(linearDampingRatio_val)) {
double d;
if (!JS_ToFloat64(js, &d, linearDampingRatio_val)) {
def.linearDampingRatio = (float)d;
}
JS_FreeValue(js, linearDampingRatio_val);
}
JSValue angularDampingRatio_val = JS_GetPropertyStr(js, argv[0], "angularDampingRatio");
if (!JS_IsUndefined(angularDampingRatio_val)) {
double d;
if (!JS_ToFloat64(js, &d, angularDampingRatio_val)) {
def.angularDampingRatio = (float)d;
}
JS_FreeValue(js, angularDampingRatio_val);
}
box2d_joint *joint = malloc(sizeof(*joint));
joint->id = b2CreateWeldJoint(world->id, &def);
joint->world = world;
return box2d_joint2js(js, joint);
)
// World.prototype.createWheelJoint()
JSC_CCALL(box2d_world_createWheelJoint,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Joint definition object required");
}
b2WheelJointDef def = b2DefaultWheelJointDef();
JSValue bodyA_val = JS_GetPropertyStr(js, argv[0], "bodyA");
JSValue bodyB_val = JS_GetPropertyStr(js, argv[0], "bodyB");
if (!JS_IsObject(bodyA_val) || !JS_IsObject(bodyB_val)) {
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
return JS_ThrowTypeError(js, "bodyA and bodyB are required");
}
box2d_body *bodyA = js2box2d_body(js, bodyA_val);
box2d_body *bodyB = js2box2d_body(js, bodyB_val);
JS_FreeValue(js, bodyA_val);
JS_FreeValue(js, bodyB_val);
if (!bodyA || !bodyB) {
return JS_ThrowTypeError(js, "Invalid body objects");
}
def.bodyIdA = bodyA->id;
def.bodyIdB = bodyB->id;
JSValue localAnchorA_val = JS_GetPropertyStr(js, argv[0], "localAnchorA");
if (!JS_IsUndefined(localAnchorA_val)) {
def.localAnchorA = js2b2vec2(js, localAnchorA_val);
JS_FreeValue(js, localAnchorA_val);
}
JSValue localAnchorB_val = JS_GetPropertyStr(js, argv[0], "localAnchorB");
if (!JS_IsUndefined(localAnchorB_val)) {
def.localAnchorB = js2b2vec2(js, localAnchorB_val);
JS_FreeValue(js, localAnchorB_val);
}
JSValue localAxisA_val = JS_GetPropertyStr(js, argv[0], "localAxisA");
if (!JS_IsUndefined(localAxisA_val)) {
def.localAxisA = js2b2vec2(js, localAxisA_val);
JS_FreeValue(js, localAxisA_val);
}
JSValue enableLimit_val = JS_GetPropertyStr(js, argv[0], "enableLimit");
if (!JS_IsUndefined(enableLimit_val)) {
def.enableLimit = JS_ToBool(js, enableLimit_val);
JS_FreeValue(js, enableLimit_val);
}
JSValue lowerTranslation_val = JS_GetPropertyStr(js, argv[0], "lowerTranslation");
if (!JS_IsUndefined(lowerTranslation_val)) {
double trans;
if (!JS_ToFloat64(js, &trans, lowerTranslation_val)) {
def.lowerTranslation = (float)trans;
}
JS_FreeValue(js, lowerTranslation_val);
}
JSValue upperTranslation_val = JS_GetPropertyStr(js, argv[0], "upperTranslation");
if (!JS_IsUndefined(upperTranslation_val)) {
double trans;
if (!JS_ToFloat64(js, &trans, upperTranslation_val)) {
def.upperTranslation = (float)trans;
}
JS_FreeValue(js, upperTranslation_val);
}
JSValue enableMotor_val = JS_GetPropertyStr(js, argv[0], "enableMotor");
if (!JS_IsUndefined(enableMotor_val)) {
def.enableMotor = JS_ToBool(js, enableMotor_val);
JS_FreeValue(js, enableMotor_val);
}
JSValue motorSpeed_val = JS_GetPropertyStr(js, argv[0], "motorSpeed");
if (!JS_IsUndefined(motorSpeed_val)) {
double speed;
if (!JS_ToFloat64(js, &speed, motorSpeed_val)) {
def.motorSpeed = (float)speed;
}
JS_FreeValue(js, motorSpeed_val);
}
JSValue maxMotorTorque_val = JS_GetPropertyStr(js, argv[0], "maxMotorTorque");
if (!JS_IsUndefined(maxMotorTorque_val)) {
double torque;
if (!JS_ToFloat64(js, &torque, maxMotorTorque_val)) {
def.maxMotorTorque = (float)torque;
}
JS_FreeValue(js, maxMotorTorque_val);
}
JSValue hertz_val = JS_GetPropertyStr(js, argv[0], "hertz");
if (!JS_IsUndefined(hertz_val)) {
double hz;
if (!JS_ToFloat64(js, &hz, hertz_val)) {
def.hertz = (float)hz;
}
JS_FreeValue(js, hertz_val);
}
JSValue dampingRatio_val = JS_GetPropertyStr(js, argv[0], "dampingRatio");
if (!JS_IsUndefined(dampingRatio_val)) {
double d;
if (!JS_ToFloat64(js, &d, dampingRatio_val)) {
def.dampingRatio = (float)d;
}
JS_FreeValue(js, dampingRatio_val);
}
box2d_joint *joint = malloc(sizeof(*joint));
joint->id = b2CreateWheelJoint(world->id, &def);
joint->world = world;
return box2d_joint2js(js, joint);
)
// World.prototype.setContactCallback()
JSC_CCALL(box2d_world_setContactCallback,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc > 0 && JS_IsFunction(js, argv[0])) {
// Store the callback
JS_FreeValue(js, world->contact_listener);
world->contact_listener = JS_DupValue(js, argv[0]);
} else {
// Clear the callback
JS_FreeValue(js, world->contact_listener);
world->contact_listener = JS_NULL;
}
return JS_UNDEFINED;
)
// World.prototype.setFilterCallback()
JSC_CCALL(box2d_world_setFilterCallback,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc > 0 && JS_IsFunction(js, argv[0])) {
// Store the callback
JS_FreeValue(js, world->filter_callback);
world->filter_callback = JS_DupValue(js, argv[0]);
} else {
// Clear the callback
JS_FreeValue(js, world->filter_callback);
world->filter_callback = JS_NULL;
}
return JS_UNDEFINED;
)
// World.prototype.setPreSolveCallback()
JSC_CCALL(box2d_world_setPreSolveCallback,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc > 0 && JS_IsFunction(js, argv[0])) {
// Store the callback
JS_FreeValue(js, world->pre_solve_callback);
world->pre_solve_callback = JS_DupValue(js, argv[0]);
} else {
// Clear the callback
JS_FreeValue(js, world->pre_solve_callback);
world->pre_solve_callback = JS_NULL;
}
return JS_UNDEFINED;
)
// World.prototype.setRestitutionThreshold()
JSC_CCALL(box2d_world_setRestitutionThreshold,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1) return JS_ThrowTypeError(js, "Restitution threshold value required");
double threshold;
if (JS_ToFloat64(js, &threshold, argv[0])) return JS_EXCEPTION;
b2World_SetRestitutionThreshold(world->id, (float)threshold);
return JS_UNDEFINED;
)
// World.prototype.setHitEventThreshold()
JSC_CCALL(box2d_world_setHitEventThreshold,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 1) return JS_ThrowTypeError(js, "Hit event threshold value required");
double threshold;
if (JS_ToFloat64(js, &threshold, argv[0])) return JS_EXCEPTION;
b2World_SetHitEventThreshold(world->id, (float)threshold);
return JS_UNDEFINED;
)
// World.prototype.setContactTuning()
JSC_CCALL(box2d_world_setContactTuning,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 3) return JS_ThrowTypeError(js, "Contact tuning requires hertz, damping ratio, and push velocity");
double hertz, dampingRatio, pushVelocity;
if (JS_ToFloat64(js, &hertz, argv[0])) return JS_EXCEPTION;
if (JS_ToFloat64(js, &dampingRatio, argv[1])) return JS_EXCEPTION;
if (JS_ToFloat64(js, &pushVelocity, argv[2])) return JS_EXCEPTION;
b2World_SetContactTuning(world->id, (float)hertz, (float)dampingRatio, (float)pushVelocity);
return JS_UNDEFINED;
)
// World.prototype.getProfile()
JSC_CCALL(box2d_world_getProfile,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
b2Profile profile = b2World_GetProfile(world->id);
JSValue profile_obj = JS_NewObject(js);
JS_SetPropertyStr(js, profile_obj, "step", JS_NewFloat64(js, profile.step));
JS_SetPropertyStr(js, profile_obj, "pairs", JS_NewFloat64(js, profile.pairs));
JS_SetPropertyStr(js, profile_obj, "collide", JS_NewFloat64(js, profile.collide));
JS_SetPropertyStr(js, profile_obj, "solve", JS_NewFloat64(js, profile.solve));
JS_SetPropertyStr(js, profile_obj, "buildIslands", JS_NewFloat64(js, profile.buildIslands));
JS_SetPropertyStr(js, profile_obj, "solveConstraints", JS_NewFloat64(js, profile.solveConstraints));
JS_SetPropertyStr(js, profile_obj, "prepareTasks", JS_NewFloat64(js, profile.prepareTasks));
JS_SetPropertyStr(js, profile_obj, "solverTasks", JS_NewFloat64(js, profile.solverTasks));
JS_SetPropertyStr(js, profile_obj, "prepareConstraints", JS_NewFloat64(js, profile.prepareConstraints));
JS_SetPropertyStr(js, profile_obj, "integrateVelocities", JS_NewFloat64(js, profile.integrateVelocities));
JS_SetPropertyStr(js, profile_obj, "warmStart", JS_NewFloat64(js, profile.warmStart));
JS_SetPropertyStr(js, profile_obj, "solveVelocities", JS_NewFloat64(js, profile.solveVelocities));
JS_SetPropertyStr(js, profile_obj, "integratePositions", JS_NewFloat64(js, profile.integratePositions));
JS_SetPropertyStr(js, profile_obj, "relaxVelocities", JS_NewFloat64(js, profile.relaxVelocities));
JS_SetPropertyStr(js, profile_obj, "applyRestitution", JS_NewFloat64(js, profile.applyRestitution));
JS_SetPropertyStr(js, profile_obj, "storeImpulses", JS_NewFloat64(js, profile.storeImpulses));
JS_SetPropertyStr(js, profile_obj, "finalizeBodies", JS_NewFloat64(js, profile.finalizeBodies));
JS_SetPropertyStr(js, profile_obj, "splitIslands", JS_NewFloat64(js, profile.splitIslands));
JS_SetPropertyStr(js, profile_obj, "sleepIslands", JS_NewFloat64(js, profile.sleepIslands));
JS_SetPropertyStr(js, profile_obj, "hitEvents", JS_NewFloat64(js, profile.hitEvents));
JS_SetPropertyStr(js, profile_obj, "broadphase", JS_NewFloat64(js, profile.broadphase));
JS_SetPropertyStr(js, profile_obj, "continuous", JS_NewFloat64(js, profile.continuous));
return profile_obj;
)
// World.prototype.getCounters()
JSC_CCALL(box2d_world_getCounters,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
b2Counters counters = b2World_GetCounters(world->id);
JSValue counters_obj = JS_NewObject(js);
JS_SetPropertyStr(js, counters_obj, "staticBodyCount", JS_NewInt32(js, counters.staticBodyCount));
JS_SetPropertyStr(js, counters_obj, "bodyCount", JS_NewInt32(js, counters.bodyCount));
JS_SetPropertyStr(js, counters_obj, "shapeCount", JS_NewInt32(js, counters.shapeCount));
JS_SetPropertyStr(js, counters_obj, "contactCount", JS_NewInt32(js, counters.contactCount));
JS_SetPropertyStr(js, counters_obj, "jointCount", JS_NewInt32(js, counters.jointCount));
JS_SetPropertyStr(js, counters_obj, "islandCount", JS_NewInt32(js, counters.islandCount));
JS_SetPropertyStr(js, counters_obj, "stackUsed", JS_NewInt32(js, counters.stackUsed));
JS_SetPropertyStr(js, counters_obj, "staticTreeHeight", JS_NewInt32(js, counters.staticTreeHeight));
JS_SetPropertyStr(js, counters_obj, "treeHeight", JS_NewInt32(js, counters.treeHeight));
JS_SetPropertyStr(js, counters_obj, "byteCount", JS_NewInt32(js, counters.byteCount));
JS_SetPropertyStr(js, counters_obj, "taskCount", JS_NewInt32(js, counters.taskCount));
JS_SetPropertyStr(js, counters_obj, "colorCounts", JS_NewArray(js));
JSValue colorArray = JS_GetPropertyStr(js, counters_obj, "colorCounts");
for (int i = 0; i < b2_graphColorCount; i++) {
JS_SetPropertyUint32(js, colorArray, i, JS_NewInt32(js, counters.colorCounts[i]));
}
JS_FreeValue(js, colorArray);
return counters_obj;
)
// World.prototype.getContactEvents()
JSC_CCALL(box2d_world_getContactEvents,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
b2ContactEvents events = b2World_GetContactEvents(world->id);
JSValue events_obj = JS_NewObject(js);
// Begin events
JSValue beginArray = JS_NewArray(js);
for (int i = 0; i < events.beginCount; i++) {
JSValue event = JS_NewObject(js);
JS_SetPropertyStr(js, event, "shapeIdA", JS_NewInt32(js, events.beginEvents[i].shapeIdA.index1));
JS_SetPropertyStr(js, event, "shapeIdB", JS_NewInt32(js, events.beginEvents[i].shapeIdB.index1));
JS_SetPropertyUint32(js, beginArray, i, event);
}
JS_SetPropertyStr(js, events_obj, "beginEvents", beginArray);
// End events
JSValue endArray = JS_NewArray(js);
for (int i = 0; i < events.endCount; i++) {
JSValue event = JS_NewObject(js);
JS_SetPropertyStr(js, event, "shapeIdA", JS_NewInt32(js, events.endEvents[i].shapeIdA.index1));
JS_SetPropertyStr(js, event, "shapeIdB", JS_NewInt32(js, events.endEvents[i].shapeIdB.index1));
JS_SetPropertyUint32(js, endArray, i, event);
}
JS_SetPropertyStr(js, events_obj, "endEvents", endArray);
// Hit events
JSValue hitArray = JS_NewArray(js);
for (int i = 0; i < events.hitCount; i++) {
JSValue event = JS_NewObject(js);
JS_SetPropertyStr(js, event, "shapeIdA", JS_NewInt32(js, events.hitEvents[i].shapeIdA.index1));
JS_SetPropertyStr(js, event, "shapeIdB", JS_NewInt32(js, events.hitEvents[i].shapeIdB.index1));
JS_SetPropertyStr(js, event, "point", b2vec22js(js, events.hitEvents[i].point));
JS_SetPropertyStr(js, event, "normal", b2vec22js(js, events.hitEvents[i].normal));
JS_SetPropertyStr(js, event, "approachSpeed", JS_NewFloat64(js, events.hitEvents[i].approachSpeed));
JS_SetPropertyUint32(js, hitArray, i, event);
}
JS_SetPropertyStr(js, events_obj, "hitEvents", hitArray);
return events_obj;
)
// World.prototype.getSensorEvents()
JSC_CCALL(box2d_world_getSensorEvents,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
b2SensorEvents events = b2World_GetSensorEvents(world->id);
JSValue events_obj = JS_NewObject(js);
// Begin events
JSValue beginArray = JS_NewArray(js);
for (int i = 0; i < events.beginCount; i++) {
JSValue event = JS_NewObject(js);
JS_SetPropertyStr(js, event, "sensorShapeId", JS_NewInt32(js, events.beginEvents[i].sensorShapeId.index1));
JS_SetPropertyStr(js, event, "visitorShapeId", JS_NewInt32(js, events.beginEvents[i].visitorShapeId.index1));
JS_SetPropertyUint32(js, beginArray, i, event);
}
JS_SetPropertyStr(js, events_obj, "beginEvents", beginArray);
// End events
JSValue endArray = JS_NewArray(js);
for (int i = 0; i < events.endCount; i++) {
JSValue event = JS_NewObject(js);
JS_SetPropertyStr(js, event, "sensorShapeId", JS_NewInt32(js, events.endEvents[i].sensorShapeId.index1));
JS_SetPropertyStr(js, event, "visitorShapeId", JS_NewInt32(js, events.endEvents[i].visitorShapeId.index1));
JS_SetPropertyUint32(js, endArray, i, event);
}
JS_SetPropertyStr(js, events_obj, "endEvents", endArray);
return events_obj;
)
// World.prototype.overlapAABB()
JSC_CCALL(box2d_world_overlapAABB,
box2d_world *world = js2box2d_world(js, self);
if (!b2World_IsValid(world->id)) return JS_ThrowReferenceError(js, "World is invalid");
if (argc < 2) return JS_ThrowTypeError(js, "AABB and callback function required");
if (!JS_IsObject(argv[0])) return JS_ThrowTypeError(js, "AABB object required");
if (!JS_IsFunction(js, argv[1])) return JS_ThrowTypeError(js, "Callback function required");
// Parse AABB
b2AABB aabb;
JSValue lowerBound = JS_GetPropertyStr(js, argv[0], "lowerBound");
JSValue upperBound = JS_GetPropertyStr(js, argv[0], "upperBound");
if (JS_IsUndefined(lowerBound) || JS_IsUndefined(upperBound)) {
JS_FreeValue(js, lowerBound);
JS_FreeValue(js, upperBound);
return JS_ThrowTypeError(js, "AABB must have lowerBound and upperBound");
}
aabb.lowerBound = js2b2vec2(js, lowerBound);
aabb.upperBound = js2b2vec2(js, upperBound);
JS_FreeValue(js, lowerBound);
JS_FreeValue(js, upperBound);
// TODO: Implement overlap callback - this requires C callback bridge
// For now, return undefined
return JS_UNDEFINED;
)
// Function lists
static const JSCFunctionListEntry js_box2d_world_funcs[] = {
// Creation methods
MIST_FUNC_DEF(box2d_world, createBody, 1),
MIST_FUNC_DEF(box2d_world, createDistanceJoint, 1),
MIST_FUNC_DEF(box2d_world, createRevoluteJoint, 1),
MIST_FUNC_DEF(box2d_world, createMotorJoint, 1),
MIST_FUNC_DEF(box2d_world, createMouseJoint, 1),
MIST_FUNC_DEF(box2d_world, createPrismaticJoint, 1),
MIST_FUNC_DEF(box2d_world, createWeldJoint, 1),
MIST_FUNC_DEF(box2d_world, createWheelJoint, 1),
// Simulation methods
MIST_FUNC_DEF(box2d_world, step, 2),
MIST_FUNC_DEF(box2d_world, rayCast, 3),
// Callback setters
MIST_FUNC_DEF(box2d_world, setContactCallback, 1),
MIST_FUNC_DEF(box2d_world, setFilterCallback, 1),
MIST_FUNC_DEF(box2d_world, setPreSolveCallback, 1),
// Configuration methods
MIST_FUNC_DEF(box2d_world, setRestitutionThreshold, 1),
MIST_FUNC_DEF(box2d_world, setHitEventThreshold, 1),
MIST_FUNC_DEF(box2d_world, setContactTuning, 3),
// Query methods
MIST_FUNC_DEF(box2d_world, getProfile, 0),
MIST_FUNC_DEF(box2d_world, getCounters, 0),
MIST_FUNC_DEF(box2d_world, getContactEvents, 0),
MIST_FUNC_DEF(box2d_world, getSensorEvents, 0),
MIST_FUNC_DEF(box2d_world, overlapAABB, 2),
// Properties
JS_CGETSET_DEF("gravity", js_box2d_world_get_gravity, js_box2d_world_set_gravity),
};
static const JSCFunctionListEntry js_box2d_body_funcs[] = {
MIST_FUNC_DEF(box2d_body, createCircleShape, 1),
MIST_FUNC_DEF(box2d_body, createBoxShape, 1),
MIST_FUNC_DEF(box2d_body, createSegmentShape, 3),
MIST_FUNC_DEF(box2d_body, createCapsuleShape, 1),
MIST_FUNC_DEF(box2d_body, createPolygonShape, 2),
MIST_FUNC_DEF(box2d_body, applyForce, 2),
MIST_FUNC_DEF(box2d_body, applyImpulse, 2),
MIST_FUNC_DEF(box2d_body, applyLinearImpulse, 2),
MIST_FUNC_DEF(box2d_body, getMass, 0),
JS_CGETSET_DEF("position", js_box2d_body_get_position, js_box2d_body_set_position),
JS_CGETSET_DEF("angle", js_box2d_body_get_angle, js_box2d_body_set_angle),
JS_CGETSET_DEF("linearVelocity", js_box2d_body_get_linearVelocity, js_box2d_body_set_linearVelocity),
JS_CGETSET_DEF("angularVelocity", js_box2d_body_get_angularVelocity, js_box2d_body_set_angularVelocity),
JS_CGETSET_DEF("type", js_box2d_body_get_type, js_box2d_body_set_type),
};
// Shape.prototype.density getter
JSValue js_box2d_shape_get_density(JSContext *js, JSValueConst self)
{
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
float density = b2Shape_GetDensity(shape->id);
return JS_NewFloat64(js, density);
}
// Shape.prototype.density setter
JSValue js_box2d_shape_set_density(JSContext *js, JSValueConst self, JSValue val)
{
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
double density;
if (JS_ToFloat64(js, &density, val)) return JS_EXCEPTION;
b2Shape_SetDensity(shape->id, (float)density);
return JS_UNDEFINED;
}
// Shape.prototype.friction getter
JSValue js_box2d_shape_get_friction(JSContext *js, JSValueConst self)
{
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
float friction = b2Shape_GetFriction(shape->id);
return JS_NewFloat64(js, friction);
}
// Shape.prototype.friction setter
JSValue js_box2d_shape_set_friction(JSContext *js, JSValueConst self, JSValue val)
{
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
double friction;
if (JS_ToFloat64(js, &friction, val)) return JS_EXCEPTION;
b2Shape_SetFriction(shape->id, (float)friction);
return JS_UNDEFINED;
}
// Shape.prototype.restitution getter
JSValue js_box2d_shape_get_restitution(JSContext *js, JSValueConst self)
{
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
float restitution = b2Shape_GetRestitution(shape->id);
return JS_NewFloat64(js, restitution);
}
// Shape.prototype.restitution setter
JSValue js_box2d_shape_set_restitution(JSContext *js, JSValueConst self, JSValue val)
{
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
double restitution;
if (JS_ToFloat64(js, &restitution, val)) return JS_EXCEPTION;
b2Shape_SetRestitution(shape->id, (float)restitution);
return JS_UNDEFINED;
}
// Shape.prototype.isSensor getter
JSValue js_box2d_shape_get_isSensor(JSContext *js, JSValueConst self)
{
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
bool isSensor = b2Shape_IsSensor(shape->id);
return JS_NewBool(js, isSensor);
}
// Shape.prototype.getType()
JSC_CCALL(box2d_shape_getType,
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
b2ShapeType type = b2Shape_GetType(shape->id);
switch (type) {
case b2_circleShape: return JS_NewString(js, "circle");
case b2_polygonShape: return JS_NewString(js, "polygon");
case b2_segmentShape: return JS_NewString(js, "segment");
case b2_capsuleShape: return JS_NewString(js, "capsule");
case b2_smoothSegmentShape: return JS_NewString(js, "smoothSegment");
default: return JS_NewString(js, "unknown");
}
)
// Shape.prototype.enableSensorEvents()
JSC_CCALL(box2d_shape_enableSensorEvents,
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
bool enable = true;
if (argc > 0) {
enable = JS_ToBool(js, argv[0]);
}
b2Shape_EnableSensorEvents(shape->id, enable);
return JS_UNDEFINED;
)
// Shape.prototype.enableContactEvents()
JSC_CCALL(box2d_shape_enableContactEvents,
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
bool enable = true;
if (argc > 0) {
enable = JS_ToBool(js, argv[0]);
}
b2Shape_EnableContactEvents(shape->id, enable);
return JS_UNDEFINED;
)
// Shape.prototype.setFilter()
JSC_CCALL(box2d_shape_setFilter,
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "Filter object required");
}
b2Filter filter = b2DefaultFilter();
JSValue categoryBits_val = JS_GetPropertyStr(js, argv[0], "categoryBits");
if (!JS_IsUndefined(categoryBits_val)) {
uint32_t bits;
if (!JS_ToUint32(js, &bits, categoryBits_val)) {
filter.categoryBits = bits;
}
JS_FreeValue(js, categoryBits_val);
}
JSValue maskBits_val = JS_GetPropertyStr(js, argv[0], "maskBits");
if (!JS_IsUndefined(maskBits_val)) {
uint32_t bits;
if (!JS_ToUint32(js, &bits, maskBits_val)) {
filter.maskBits = bits;
}
JS_FreeValue(js, maskBits_val);
}
JSValue groupIndex_val = JS_GetPropertyStr(js, argv[0], "groupIndex");
if (!JS_IsUndefined(groupIndex_val)) {
int32_t index;
if (!JS_ToInt32(js, &index, groupIndex_val)) {
filter.groupIndex = index;
}
JS_FreeValue(js, groupIndex_val);
}
b2Shape_SetFilter(shape->id, filter);
return JS_UNDEFINED;
)
// Shape.prototype.getFilter()
JSC_CCALL(box2d_shape_getFilter,
box2d_shape *shape = js2box2d_shape(js, self);
if (!b2Shape_IsValid(shape->id)) return JS_ThrowReferenceError(js, "Shape is invalid");
b2Filter filter = b2Shape_GetFilter(shape->id);
JSValue filter_obj = JS_NewObject(js);
JS_SetPropertyStr(js, filter_obj, "categoryBits", JS_NewUint32(js, filter.categoryBits));
JS_SetPropertyStr(js, filter_obj, "maskBits", JS_NewUint32(js, filter.maskBits));
JS_SetPropertyStr(js, filter_obj, "groupIndex", JS_NewInt32(js, filter.groupIndex));
return filter_obj;
)
static const JSCFunctionListEntry js_box2d_shape_funcs[] = {
MIST_FUNC_DEF(box2d_shape, getType, 0),
MIST_FUNC_DEF(box2d_shape, enableSensorEvents, 1),
MIST_FUNC_DEF(box2d_shape, enableContactEvents, 1),
MIST_FUNC_DEF(box2d_shape, setFilter, 1),
MIST_FUNC_DEF(box2d_shape, getFilter, 0),
JS_CGETSET_DEF("density", js_box2d_shape_get_density, js_box2d_shape_set_density),
JS_CGETSET_DEF("friction", js_box2d_shape_get_friction, js_box2d_shape_set_friction),
JS_CGETSET_DEF("restitution", js_box2d_shape_get_restitution, js_box2d_shape_set_restitution),
JS_CGETSET_DEF("isSensor", js_box2d_shape_get_isSensor, NULL),
};
// Joint.prototype.getType()
JSC_CCALL(box2d_joint_getType,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
b2JointType type = b2Joint_GetType(joint->id);
switch (type) {
case b2_distanceJoint: return JS_NewString(js, "distance");
case b2_motorJoint: return JS_NewString(js, "motor");
case b2_mouseJoint: return JS_NewString(js, "mouse");
case b2_prismaticJoint: return JS_NewString(js, "prismatic");
case b2_revoluteJoint: return JS_NewString(js, "revolute");
case b2_weldJoint: return JS_NewString(js, "weld");
case b2_wheelJoint: return JS_NewString(js, "wheel");
default: return JS_NewString(js, "unknown");
}
)
// Joint.prototype.getBodyA()
JSC_CCALL(box2d_joint_getBodyA,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
b2BodyId bodyId = b2Joint_GetBodyA(joint->id);
// Find the body wrapper
// Note: This is a simplified approach - in a full implementation you'd maintain a lookup table
box2d_body *body = malloc(sizeof(*body));
body->id = bodyId;
body->world = joint->world;
return box2d_body2js(js, body);
)
// Joint.prototype.getBodyB()
JSC_CCALL(box2d_joint_getBodyB,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
b2BodyId bodyId = b2Joint_GetBodyB(joint->id);
// Find the body wrapper
box2d_body *body = malloc(sizeof(*body));
body->id = bodyId;
body->world = joint->world;
return box2d_body2js(js, body);
)
// Joint.prototype.getLocalAnchorA()
JSC_CCALL(box2d_joint_getLocalAnchorA,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
b2Vec2 anchor = b2Joint_GetLocalAnchorA(joint->id);
return b2vec22js(js, anchor);
)
// Joint.prototype.getLocalAnchorB()
JSC_CCALL(box2d_joint_getLocalAnchorB,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
b2Vec2 anchor = b2Joint_GetLocalAnchorB(joint->id);
return b2vec22js(js, anchor);
)
// Joint.prototype.getConstraintForce()
JSC_CCALL(box2d_joint_getConstraintForce,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
b2Vec2 force = b2Joint_GetConstraintForce(joint->id);
return b2vec22js(js, force);
)
// Joint.prototype.getConstraintTorque()
JSC_CCALL(box2d_joint_getConstraintTorque,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
float torque = b2Joint_GetConstraintTorque(joint->id);
return JS_NewFloat64(js, torque);
)
// Joint.prototype.setCollideConnected()
JSC_CCALL(box2d_joint_setCollideConnected,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
bool shouldCollide = true;
if (argc > 0) {
shouldCollide = JS_ToBool(js, argv[0]);
}
b2Joint_SetCollideConnected(joint->id, shouldCollide);
return JS_UNDEFINED;
)
// Joint.prototype.getCollideConnected()
JSC_CCALL(box2d_joint_getCollideConnected,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
bool collideConnected = b2Joint_GetCollideConnected(joint->id);
return JS_NewBool(js, collideConnected);
)
// Joint.prototype.wakeBodies()
JSC_CCALL(box2d_joint_wakeBodies,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
b2Joint_WakeBodies(joint->id);
return JS_UNDEFINED;
)
// Distance Joint specific methods
// Joint.prototype.setLength() - for distance joints
JSC_CCALL(box2d_joint_setLength,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_distanceJoint) {
return JS_ThrowTypeError(js, "setLength only applies to distance joints");
}
if (argc < 1) return JS_ThrowTypeError(js, "Length value required");
double length;
if (JS_ToFloat64(js, &length, argv[0])) return JS_EXCEPTION;
b2DistanceJoint_SetLength(joint->id, (float)length);
return JS_UNDEFINED;
)
// Joint.prototype.getLength() - for distance joints
JSC_CCALL(box2d_joint_getLength,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_distanceJoint) {
return JS_ThrowTypeError(js, "getLength only applies to distance joints");
}
float length = b2DistanceJoint_GetLength(joint->id);
return JS_NewFloat64(js, length);
)
// Motor Joint specific methods
// Joint.prototype.setLinearOffset() - for motor joints
JSC_CCALL(box2d_joint_setLinearOffset,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_motorJoint) {
return JS_ThrowTypeError(js, "setLinearOffset only applies to motor joints");
}
if (argc < 1) return JS_ThrowTypeError(js, "Linear offset vector required");
b2Vec2 offset = js2b2vec2(js, argv[0]);
b2MotorJoint_SetLinearOffset(joint->id, offset);
return JS_UNDEFINED;
)
// Joint.prototype.getLinearOffset() - for motor joints
JSC_CCALL(box2d_joint_getLinearOffset,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_motorJoint) {
return JS_ThrowTypeError(js, "getLinearOffset only applies to motor joints");
}
b2Vec2 offset = b2MotorJoint_GetLinearOffset(joint->id);
return b2vec22js(js, offset);
)
// Joint.prototype.setAngularOffset() - for motor joints
JSC_CCALL(box2d_joint_setAngularOffset,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_motorJoint) {
return JS_ThrowTypeError(js, "setAngularOffset only applies to motor joints");
}
if (argc < 1) return JS_ThrowTypeError(js, "Angular offset value required");
double offset;
if (JS_ToFloat64(js, &offset, argv[0])) return JS_EXCEPTION;
b2MotorJoint_SetAngularOffset(joint->id, (float)offset);
return JS_UNDEFINED;
)
// Joint.prototype.getAngularOffset() - for motor joints
JSC_CCALL(box2d_joint_getAngularOffset,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_motorJoint) {
return JS_ThrowTypeError(js, "getAngularOffset only applies to motor joints");
}
float offset = b2MotorJoint_GetAngularOffset(joint->id);
return JS_NewFloat64(js, offset);
)
// Joint.prototype.setMaxForce() - for motor joints
JSC_CCALL(box2d_joint_setMaxForce,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_motorJoint) {
return JS_ThrowTypeError(js, "setMaxForce only applies to motor joints");
}
if (argc < 1) return JS_ThrowTypeError(js, "Max force value required");
double force;
if (JS_ToFloat64(js, &force, argv[0])) return JS_EXCEPTION;
b2MotorJoint_SetMaxForce(joint->id, (float)force);
return JS_UNDEFINED;
)
// Joint.prototype.getMaxForce() - for motor joints
JSC_CCALL(box2d_joint_getMaxForce,
box2d_joint *joint = js2box2d_joint(js, self);
if (!b2Joint_IsValid(joint->id)) return JS_ThrowReferenceError(js, "Joint is invalid");
if (b2Joint_GetType(joint->id) != b2_motorJoint) {
return JS_ThrowTypeError(js, "getMaxForce only applies to motor joints");
}
float force = b2MotorJoint_GetMaxForce(joint->id);
return JS_NewFloat64(js, force);
)
static const JSCFunctionListEntry js_box2d_joint_funcs[] = {
MIST_FUNC_DEF(box2d_joint, getType, 0),
MIST_FUNC_DEF(box2d_joint, getBodyA, 0),
MIST_FUNC_DEF(box2d_joint, getBodyB, 0),
MIST_FUNC_DEF(box2d_joint, getLocalAnchorA, 0),
MIST_FUNC_DEF(box2d_joint, getLocalAnchorB, 0),
MIST_FUNC_DEF(box2d_joint, getConstraintForce, 0),
MIST_FUNC_DEF(box2d_joint, getConstraintTorque, 0),
MIST_FUNC_DEF(box2d_joint, setCollideConnected, 1),
MIST_FUNC_DEF(box2d_joint, getCollideConnected, 0),
MIST_FUNC_DEF(box2d_joint, wakeBodies, 0),
// Distance Joint specific
MIST_FUNC_DEF(box2d_joint, setLength, 1),
MIST_FUNC_DEF(box2d_joint, getLength, 0),
// Motor Joint specific
MIST_FUNC_DEF(box2d_joint, setLinearOffset, 1),
MIST_FUNC_DEF(box2d_joint, getLinearOffset, 0),
MIST_FUNC_DEF(box2d_joint, setAngularOffset, 1),
MIST_FUNC_DEF(box2d_joint, getAngularOffset, 0),
MIST_FUNC_DEF(box2d_joint, setMaxForce, 1),
MIST_FUNC_DEF(box2d_joint, getMaxForce, 0),
};
// Module initialization
JSValue js_box2d_use(JSContext *js)
{
// Register classes
QJSCLASSPREP_FUNCS(box2d_world);
QJSCLASSPREP_FUNCS(box2d_body);
QJSCLASSPREP_FUNCS(box2d_shape);
QJSCLASSPREP_FUNCS(box2d_joint);
// Create module object
JSValue module = JS_NewObject(js);
// World constructor
JSValue world_ctor = JS_NewCFunction2(js, js_box2d_world_constructor, "World", 1, JS_CFUNC_constructor, 0);
JSValue world_proto = JS_GetClassProto(js, js_box2d_world_id);
JS_SetConstructor(js, world_ctor, world_proto);
JS_FreeValue(js, world_proto);
JS_SetPropertyStr(js, module, "World", world_ctor);
// Body type constants
JS_SetPropertyStr(js, module, "STATIC_BODY", JS_NewInt32(js, b2_staticBody));
JS_SetPropertyStr(js, module, "KINEMATIC_BODY", JS_NewInt32(js, b2_kinematicBody));
JS_SetPropertyStr(js, module, "DYNAMIC_BODY", JS_NewInt32(js, b2_dynamicBody));
return module;
}
static int js_box2d_init(JSContext *js, JSModuleDef *m)
{
JSValue module = js_box2d_use(js);
JS_SetModuleExport(js, m, "default", module);
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_box2d
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *module_name)
{
JSModuleDef *m = JS_NewCModule(js, module_name, js_box2d_init);
if (!m) return NULL;
JS_AddModuleExport(js, m, "default");
return m;
}