Compare commits
3 Commits
js-rm-weak
...
chipmunk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad4f3d3f58 | ||
|
|
b23dca6934 | ||
|
|
8fba19c820 |
20
meson.build
20
meson.build
@@ -72,6 +72,18 @@ sdl3_opts.add_cmake_defines({
|
|||||||
'CMAKE_BUILD_TYPE': 'Release'
|
'CMAKE_BUILD_TYPE': 'Release'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
chipmunk_opts = cmake.subproject_options()
|
||||||
|
chipmunk_opts.add_cmake_defines({
|
||||||
|
'BUILD_DEMOS': 'OFF',
|
||||||
|
'BUILD_SHARED': 'OFF',
|
||||||
|
'BUILD_STATIC': 'ON',
|
||||||
|
'CMAKE_BUILD_TYPE': 'Release',
|
||||||
|
# uncomment to use floats instead of doubles
|
||||||
|
# 'CP_USE_DOUBLES': 'OFF',
|
||||||
|
})
|
||||||
|
chipmunk_proj = cmake.subproject('chipmunk', options: chipmunk_opts)
|
||||||
|
deps += chipmunk_proj.dependency('chipmunk_static')
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
if host_machine.system() == 'darwin'
|
if host_machine.system() == 'darwin'
|
||||||
@@ -131,14 +143,11 @@ deps += dependency('physfs', static:true)
|
|||||||
#deps += cc.find_library('opencv')
|
#deps += cc.find_library('opencv')
|
||||||
|
|
||||||
deps += dependency('threads')
|
deps += dependency('threads')
|
||||||
deps += dependency('chipmunk', static:true)
|
|
||||||
deps += dependency('enet', static:true)
|
deps += dependency('enet', static:true)
|
||||||
deps += dependency('soloud', static:true)
|
deps += dependency('soloud', static:true)
|
||||||
|
|
||||||
#deps += dependency('qjs-chipmunk', static:false)
|
|
||||||
|
|
||||||
sources = []
|
sources = []
|
||||||
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c', 'qjs_enet.c', 'qjs_soloud.c']
|
src += ['anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','prosperon.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_dmon.c', 'qjs_nota.c', 'qjs_enet.c', 'qjs_soloud.c', 'qjs_chipmunk.c']
|
||||||
|
|
||||||
imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp', 'imgui_impl_sdlgpu3.cpp']
|
imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp', 'imgui_impl_sdlgpu3.cpp']
|
||||||
|
|
||||||
@@ -231,7 +240,8 @@ tests = [
|
|||||||
'spawn_actor',
|
'spawn_actor',
|
||||||
'empty',
|
'empty',
|
||||||
'nota',
|
'nota',
|
||||||
'enet'
|
'enet',
|
||||||
|
'chipmunk2d'
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach file : tests
|
foreach file : tests
|
||||||
|
|||||||
564
scripts/modules/chipmunk2d.js
Normal file
564
scripts/modules/chipmunk2d.js
Normal file
@@ -0,0 +1,564 @@
|
|||||||
|
var chipmunk = this;
|
||||||
|
return chipmunk
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Top-level Chipmunk2D functions
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
chipmunk.make_space[prosperon.DOC] = `
|
||||||
|
Create and return a new Chipmunk2D cpSpace instance. By default, this space has
|
||||||
|
no bodies or shapes. You can add bodies via space.add_body() and step the simulation
|
||||||
|
with space.step().
|
||||||
|
|
||||||
|
:return: A newly created Space object.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Space methods and properties
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
var cpSpace = prosperon.c_types.cpSpace;
|
||||||
|
|
||||||
|
cpSpace.step[prosperon.DOC] = `
|
||||||
|
Advance the physics simulation by the specified timestep.
|
||||||
|
|
||||||
|
:param dt: A number representing the time step to simulate (e.g., 1/60 for 60 FPS).
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.add_body[prosperon.DOC] = `
|
||||||
|
Create and add a new dynamic body to this space. Returns the newly created cpBody
|
||||||
|
object, which you can then configure (mass, moment, etc.) or attach shapes to.
|
||||||
|
|
||||||
|
:return: A cpBody object representing the new body.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.eachBody[prosperon.DOC] = `
|
||||||
|
Iterate over every cpBody in this space, calling the provided callback for each one.
|
||||||
|
Useful for enumerating or modifying all bodies.
|
||||||
|
|
||||||
|
:param callback: A function(body) called for each cpBody in this space.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.eachShape[prosperon.DOC] = `
|
||||||
|
Iterate over every cpShape in this space, calling the provided callback for each one.
|
||||||
|
|
||||||
|
:param callback: A function(shape) called for each cpShape in this space.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.eachConstraint[prosperon.DOC] = `
|
||||||
|
Iterate over every cpConstraint in this space, calling the provided callback for each.
|
||||||
|
|
||||||
|
:param callback: A function(constraint) called for each cpConstraint in this space.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.gravity[prosperon.DOC] = `
|
||||||
|
The gravity vector for this space, typically something like { x: 0, y: -9.8 } for
|
||||||
|
Earth-like gravity in 2D. You can read or write this property to change gravity.
|
||||||
|
|
||||||
|
:return: An object { x, y } for read. Assign a similar object to set.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.iterations[prosperon.DOC] = `
|
||||||
|
The number of solver iterations that Chipmunk2D performs each time step. Higher values
|
||||||
|
improve stability at a performance cost. You can read or write this property.
|
||||||
|
|
||||||
|
:return: Number of iterations.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.idle_speed[prosperon.DOC] = `
|
||||||
|
Bodies with a speed (velocity magnitude) lower than idle_speed for a certain amount of
|
||||||
|
time can enter sleep mode, which saves CPU. Read or set this threshold.
|
||||||
|
|
||||||
|
:return: A number indicating the idle speed threshold.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.sleep_time[prosperon.DOC] = `
|
||||||
|
A duration threshold (in seconds) for which a body must remain below idle_speed before it
|
||||||
|
can sleep. Read or set this property.
|
||||||
|
|
||||||
|
:return: Number of seconds for the sleep threshold.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.collision_slop[prosperon.DOC] = `
|
||||||
|
Extra distance allowed to mitigate floating-point issues, preventing shapes from
|
||||||
|
interpenetrating excessively. Can be read or set.
|
||||||
|
|
||||||
|
:return: A number representing the collision slop distance.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.collision_bias[prosperon.DOC] = `
|
||||||
|
Rate at which interpenetration errors are corrected each step. Usually a number close to
|
||||||
|
1.0 (a bit less). Can be read or set.
|
||||||
|
|
||||||
|
:return: A number controlling bias for shape collision resolution.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.collision_persistence[prosperon.DOC] = `
|
||||||
|
How many steps a contact persists if the two shapes are still overlapping. Read or set.
|
||||||
|
|
||||||
|
:return: An integer count of persistence steps.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Space joint-creation methods
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
cpSpace.pin[prosperon.DOC] = `
|
||||||
|
Create a cpPinJoint between two bodies (argv[0] and argv[1]) and add it to this space.
|
||||||
|
If called without arguments, returns the PinJoint prototype object.
|
||||||
|
|
||||||
|
:return: A cpPinJoint constraint object if arguments are provided, or the prototype if none.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.pivot[prosperon.DOC] = `
|
||||||
|
Create a cpPivotJoint between two bodies at a given pivot point. If called without
|
||||||
|
arguments, returns the PivotJoint prototype object.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param pivotPoint: An object { x, y } for the pivot location in world coordinates.
|
||||||
|
:return: A cpPivotJoint constraint object or the prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.gear[prosperon.DOC] = `
|
||||||
|
Create a cpGearJoint between two bodies, controlling their relative angular motion.
|
||||||
|
If called without arguments, returns the GearJoint prototype object.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param phase: Initial angular offset in radians.
|
||||||
|
:param ratio: Gear ratio linking rotations of bodyA and bodyB.
|
||||||
|
:return: A cpGearJoint constraint object or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.rotary[prosperon.DOC] = `
|
||||||
|
Create a cpRotaryLimitJoint that constrains rotation between two bodies to a min and max
|
||||||
|
angle. Without arguments, returns the RotaryLimitJoint prototype.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param minAngle: Minimum relative angle in radians.
|
||||||
|
:param maxAngle: Maximum relative angle in radians.
|
||||||
|
:return: A cpRotaryLimitJoint constraint object or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.damped_rotary[prosperon.DOC] = `
|
||||||
|
Create a cpDampedRotarySpring that uses a spring-damper system to keep two bodies at a
|
||||||
|
relative rest angle. Without arguments, returns the prototype.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param restAngle: Rest angle between the bodies.
|
||||||
|
:param stiffness: Spring stiffness.
|
||||||
|
:param damping: Damping factor.
|
||||||
|
:return: A cpDampedRotarySpring object or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.damped_spring[prosperon.DOC] = `
|
||||||
|
Create a cpDampedSpring between two bodies, each with an anchor point and a rest length.
|
||||||
|
Without arguments, returns the prototype.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param anchorA: { x, y } anchor relative to bodyA.
|
||||||
|
:param anchorB: { x, y } anchor relative to bodyB.
|
||||||
|
:param restLength: The spring's natural rest length.
|
||||||
|
:param stiffness: The spring stiffness.
|
||||||
|
:param damping: The damping factor.
|
||||||
|
:return: A cpDampedSpring constraint or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.groove[prosperon.DOC] = `
|
||||||
|
Create a cpGrooveJoint, which allows one body’s anchor to slide along a groove defined
|
||||||
|
in the other body. Without arguments, returns the prototype.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param grooveA: { x, y } start of the groove in bodyA.
|
||||||
|
:param grooveB: { x, y } end of the groove in bodyA.
|
||||||
|
:param anchorB: { x, y } anchor point on bodyB.
|
||||||
|
:return: A cpGrooveJoint object or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.slide[prosperon.DOC] = `
|
||||||
|
Create a cpSlideJoint that limits the distance between two anchor points on two bodies
|
||||||
|
to a min and max. Without arguments, returns the prototype.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param anchorA: { x, y } anchor relative to bodyA.
|
||||||
|
:param anchorB: { x, y } anchor relative to bodyB.
|
||||||
|
:param minDistance: Minimum distance allowed.
|
||||||
|
:param maxDistance: Maximum distance allowed.
|
||||||
|
:return: A cpSlideJoint object or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.ratchet[prosperon.DOC] = `
|
||||||
|
Create a cpRatchetJoint, allowing relative rotation in steps (like a ratchet). Without
|
||||||
|
arguments, returns the prototype.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param phase: Initial angular offset.
|
||||||
|
:param ratchet: The angle increment for each "click" of the ratchet.
|
||||||
|
:return: A cpRatchetJoint object or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSpace.motor[prosperon.DOC] = `
|
||||||
|
Create a cpSimpleMotor that enforces a relative angular velocity between two bodies.
|
||||||
|
Without arguments, returns the prototype.
|
||||||
|
|
||||||
|
:param bodyA: The first cpBody.
|
||||||
|
:param bodyB: The second cpBody.
|
||||||
|
:param rate: The desired relative angular speed (radians per second).
|
||||||
|
:return: A cpSimpleMotor object or prototype if no arguments.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Body methods and properties
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
var cpBody = prosperon.c_types.cpBody;
|
||||||
|
|
||||||
|
cpBody.position[prosperon.DOC] = `
|
||||||
|
The current position (x, y) of this body in world coordinates. Read or assign { x, y }.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.angle[prosperon.DOC] = `
|
||||||
|
The body's rotation in radians. 0 is "no rotation". Read or set this property.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.velocity[prosperon.DOC] = `
|
||||||
|
The linear velocity of this body (x, y). Typically updated each time step, but you
|
||||||
|
can also set it directly.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.angularVelocity[prosperon.DOC] = `
|
||||||
|
The body's angular velocity (radians per second).
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.moment[prosperon.DOC] = `
|
||||||
|
The moment of inertia (rotational inertia) for this body. Must not be changed if
|
||||||
|
the body is static or kinematic.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.torque[prosperon.DOC] = `
|
||||||
|
Accumulated torque on this body. Setting this directly allows you to apply a net
|
||||||
|
torque each frame.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.mass[prosperon.DOC] = `
|
||||||
|
Mass of the body. Must not be changed if the body is static or kinematic.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.centerOfGravity[prosperon.DOC] = `
|
||||||
|
Offset of the center of gravity (relative to the body's local origin). Typically
|
||||||
|
set before shapes are attached.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.force[prosperon.DOC] = `
|
||||||
|
Accumulated force on this body. Setting this is another way to apply a direct force
|
||||||
|
each frame.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.type[prosperon.DOC] = `
|
||||||
|
The body's type. 0 = CP_BODY_TYPE_DYNAMIC, 1 = CP_BODY_TYPE_KINEMATIC, 2 = CP_BODY_TYPE_STATIC.
|
||||||
|
Changing the type for a body can move it in or out of the simulation.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.isSleeping[prosperon.DOC] = `
|
||||||
|
Returns true if this body is currently sleeping. Sleep is managed automatically.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.activate[prosperon.DOC] = `
|
||||||
|
Wake up this body if it is sleeping, making it active again. Typically needed if
|
||||||
|
you manually move a sleeping body.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.sleep[prosperon.DOC] = `
|
||||||
|
Force this body to sleep immediately. Useful if you know it won't move for a while.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.activateStatic[prosperon.DOC] = `
|
||||||
|
Wake up any dynamic bodies touching this static/kinematic body. Optionally pass a shape
|
||||||
|
to wake only bodies that touch that shape.
|
||||||
|
:param shape: (optional) A cpShape that is part of this body.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.sleepWithGroup[prosperon.DOC] = `
|
||||||
|
Put this body to sleep as though it were in the same group as another sleeping body.
|
||||||
|
Allows linking bodies for group sleep.
|
||||||
|
|
||||||
|
:param otherBody: Another body that is already sleeping.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.applyForceAtWorldPoint[prosperon.DOC] = `
|
||||||
|
Apply a force to this body at a specific world space point. This can move or rotate the
|
||||||
|
body depending on the offset from its center of gravity.
|
||||||
|
|
||||||
|
:param force: { x, y } force vector in world coordinates.
|
||||||
|
:param point: { x, y } point in world coordinates.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.applyForceAtLocalPoint[prosperon.DOC] = `
|
||||||
|
Apply a force at a local point on the body (local coords). The body’s transform is used
|
||||||
|
to find its world effect.
|
||||||
|
|
||||||
|
:param force: { x, y } force vector in body local coordinates.
|
||||||
|
:param point: { x, y } local point relative to the body's origin.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.applyImpulseAtWorldPoint[prosperon.DOC] = `
|
||||||
|
Apply an instantaneous impulse (like a collision) at a point in world coordinates.
|
||||||
|
|
||||||
|
:param impulse: { x, y } impulse vector in world coordinates.
|
||||||
|
:param point: { x, y } where to apply the impulse, in world coordinates.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.applyImpulseAtLocalPoint[prosperon.DOC] = `
|
||||||
|
Apply an instantaneous impulse at a local point. Similar to applyForceAtLocalPoint, but
|
||||||
|
impulse is used instead of a continuous force.
|
||||||
|
|
||||||
|
:param impulse: { x, y } impulse vector in local coordinates.
|
||||||
|
:param point: { x, y } local point on the body.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.eachShape[prosperon.DOC] = `
|
||||||
|
Iterate over all cpShape objects attached to this body, calling the provided callback.
|
||||||
|
|
||||||
|
:param callback: A function(shape) that is called once per shape.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.eachConstraint[prosperon.DOC] = `
|
||||||
|
Iterate over all cpConstraint objects attached to this body, calling the provided
|
||||||
|
callback.
|
||||||
|
|
||||||
|
:param callback: A function(constraint) that is called once per constraint.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.eachArbiter[prosperon.DOC] = `
|
||||||
|
Iterate over all cpArbiters (contact pairs) for this body. Not currently implemented
|
||||||
|
for JavaScript, so it’s a no-op in this binding.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.add_circle_shape[prosperon.DOC] = `
|
||||||
|
Attach a circle shape to this body. Does not automatically add the shape to the space.
|
||||||
|
You may need to space.addShape(...) or store it.
|
||||||
|
|
||||||
|
:param radius: The radius of the circle shape.
|
||||||
|
:param offset: { x, y } local offset of the circle center from the body’s origin.
|
||||||
|
:return: A cpShape representing the circle.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.add_segment_shape[prosperon.DOC] = `
|
||||||
|
Attach a line segment shape to this body. Does not automatically add it to the space.
|
||||||
|
Useful for edges or walls.
|
||||||
|
|
||||||
|
:param a: { x, y } start point of the segment in local coords.
|
||||||
|
:param b: { x, y } end point of the segment in local coords.
|
||||||
|
:param radius: Thickness radius for the segment.
|
||||||
|
:return: A cpShape representing the segment.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpBody.add_poly_shape[prosperon.DOC] = `
|
||||||
|
Attach a polygon shape to this body. Currently a stub that uses a placeholder for verts.
|
||||||
|
Does not automatically add the shape to the space.
|
||||||
|
|
||||||
|
:param count: Number of vertices. (Actual vertex data is not fully implemented in the stub.)
|
||||||
|
:return: A cpShape representing the polygon.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Shape methods and properties
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
var cpShape = prosperon.c_types.cpShape;
|
||||||
|
|
||||||
|
cpShape.getBB[prosperon.DOC] = `
|
||||||
|
Retrieve the bounding box of this shape. Returns an object with x, y, width, and height
|
||||||
|
corresponding to the bounding box in world coordinates.
|
||||||
|
|
||||||
|
:return: An object { x, y, width, height }.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpShape.collisionType[prosperon.DOC] = `
|
||||||
|
An integer used to identify the collision type of this shape. Used with collision handlers
|
||||||
|
if you have custom collision code. Assign any int to set.
|
||||||
|
|
||||||
|
:return: The collision type integer.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpShape.sensor[prosperon.DOC] = `
|
||||||
|
If true, the shape is a sensor that detects collisions without generating contact forces.
|
||||||
|
Set to false for normal collisions.
|
||||||
|
|
||||||
|
:return: Boolean indicating sensor status.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpShape.elasticity[prosperon.DOC] = `
|
||||||
|
Coefficient of restitution (bounciness). Ranges from 0 (inelastic) to 1 (fully elastic),
|
||||||
|
though values above 1 are possible (super bouncy).
|
||||||
|
|
||||||
|
:return: A number for shape elasticity.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpShape.friction[prosperon.DOC] = `
|
||||||
|
Coefficient of friction for this shape. Typically 0 for no friction to 1 or more for
|
||||||
|
high friction.
|
||||||
|
|
||||||
|
:return: A number for friction.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpShape.surfaceVelocity[prosperon.DOC] = `
|
||||||
|
Relative velocity of the shape’s surface, useful for conveyors. Typically { x:0, y:0 }
|
||||||
|
for normal shapes.
|
||||||
|
|
||||||
|
:return: { x, y } velocity vector.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpShape.filter[prosperon.DOC] = `
|
||||||
|
Collision filtering parameters. An object { categories, mask, group } controlling which
|
||||||
|
objects this shape collides with. E.g., shape.filter = { categories: 1, mask: 0xFFFFFFFF, group: 0 }
|
||||||
|
|
||||||
|
:return: Object with fields categories, mask, and group.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Circle shape (subtype of cpShape)
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
var cpCircleShape = prosperon.c_types.cpCircleShape;
|
||||||
|
|
||||||
|
cpCircleShape.radius[prosperon.DOC] = `
|
||||||
|
The radius of this circle shape.
|
||||||
|
|
||||||
|
:return: A number representing circle radius.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpCircleShape.offset[prosperon.DOC] = `
|
||||||
|
A local offset of the circle center relative to the body's origin. Typically { x: 0, y: 0 }.
|
||||||
|
|
||||||
|
:return: { x, y } local offset.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Segment shape (subtype of cpShape)
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
var cpSegmentShape = prosperon.c_types.cpSegmentShape;
|
||||||
|
|
||||||
|
cpSegmentShape.setEndpoints[prosperon.DOC] = `
|
||||||
|
Change the endpoints of this line segment. Each endpoint is specified as an { x, y }
|
||||||
|
vector in the body's local coordinates.
|
||||||
|
|
||||||
|
:param startPoint: { x, y } local coordinates for the segment start.
|
||||||
|
:param endPoint: { x, y } local coordinates for the segment end.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpSegmentShape.radius[prosperon.DOC] = `
|
||||||
|
Thickness radius of this segment shape.
|
||||||
|
|
||||||
|
:return: Number representing the segment's thickness radius.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Poly shape (subtype of cpShape)
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
var cpPolyShape = prosperon.c_types.cpPolyShape;
|
||||||
|
|
||||||
|
cpPolyShape.setVerts[prosperon.DOC] = `
|
||||||
|
Set the vertices of this polygon shape. Currently a stub: in reality you’d pass an array
|
||||||
|
of { x, y } points to define the polygon outline.
|
||||||
|
|
||||||
|
:param count: Number of vertices (integer).
|
||||||
|
:param verts: (stub) Array of { x, y } points in local coords.
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpPolyShape.radius[prosperon.DOC] = `
|
||||||
|
Radius used to give the polygon a beveled edge. 0 for a sharp polygon, >0 for smoothing
|
||||||
|
corners.
|
||||||
|
|
||||||
|
:return: Number representing the bevel radius.
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Constraint methods and properties
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
var cpConstraint = prosperon.c_types.cpConstraint;
|
||||||
|
|
||||||
|
cpConstraint.bodyA[prosperon.DOC] = `
|
||||||
|
Return the first body attached to this constraint.
|
||||||
|
|
||||||
|
:return: The cpBody object for body A.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpConstraint.bodyB[prosperon.DOC] = `
|
||||||
|
Return the second body attached to this constraint.
|
||||||
|
|
||||||
|
:return: The cpBody object for body B.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpConstraint.max_force[prosperon.DOC] = `
|
||||||
|
The maximum force the constraint can apply. If the constraint would need more than this
|
||||||
|
force to maintain its constraints, it won't hold them fully.
|
||||||
|
|
||||||
|
:return: Number for max force.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpConstraint.max_bias[prosperon.DOC] = `
|
||||||
|
Limits how quickly the constraint can correct errors each step. Setting a maxBias too low
|
||||||
|
can cause "soft" constraints.
|
||||||
|
|
||||||
|
:return: Number controlling maximum correction speed.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpConstraint.error_bias[prosperon.DOC] = `
|
||||||
|
Bias factor controlling how quickly overlap/penetration is corrected. Typically close to 1.0.
|
||||||
|
|
||||||
|
:return: Number (0..1 range).
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpConstraint.collide_bodies[prosperon.DOC] = `
|
||||||
|
If true, the connected bodies can still collide with each other. If false, the bodies
|
||||||
|
won't collide. Usually false for "connected" objects.
|
||||||
|
|
||||||
|
:return: Boolean indicating if bodies collide.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpConstraint.broken[prosperon.DOC] = `
|
||||||
|
Check if the constraint is still in the space. Returns true if the space still contains
|
||||||
|
this constraint, false otherwise.
|
||||||
|
|
||||||
|
:return: Boolean indicating whether the constraint remains in the space.
|
||||||
|
`;
|
||||||
|
|
||||||
|
cpConstraint.break[prosperon.DOC] = `
|
||||||
|
Remove this constraint from the space immediately, effectively "breaking" it.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
`;
|
||||||
|
|
||||||
|
//------------------------------------------------
|
||||||
|
// Return the chipmunk object
|
||||||
|
//------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
return chipmunk;
|
||||||
@@ -7585,6 +7585,7 @@ JSValue js_imgui_use(JSContext *js);
|
|||||||
#include "qjs_nota.h"
|
#include "qjs_nota.h"
|
||||||
#include "qjs_enet.h"
|
#include "qjs_enet.h"
|
||||||
#include "qjs_soloud.h"
|
#include "qjs_soloud.h"
|
||||||
|
#include "qjs_chipmunk.h"
|
||||||
|
|
||||||
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
|
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
|
||||||
|
|
||||||
@@ -7610,6 +7611,7 @@ void ffi_load(JSContext *js, int argc, char **argv) {
|
|||||||
arrput(module_registry, MISTLINE(dmon));
|
arrput(module_registry, MISTLINE(dmon));
|
||||||
arrput(module_registry, MISTLINE(nota));
|
arrput(module_registry, MISTLINE(nota));
|
||||||
arrput(module_registry, MISTLINE(enet));
|
arrput(module_registry, MISTLINE(enet));
|
||||||
|
arrput(module_registry, MISTLINE(chipmunk2d));
|
||||||
|
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
arrput(module_registry, MISTLINE(tracy));
|
arrput(module_registry, MISTLINE(tracy));
|
||||||
|
|||||||
1278
source/qjs_chipmunk.c
Normal file
1278
source/qjs_chipmunk.c
Normal file
File diff suppressed because it is too large
Load Diff
8
source/qjs_chipmunk.h
Normal file
8
source/qjs_chipmunk.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef QJS_CHIPMUNK_H
|
||||||
|
#define QJS_CHIPMUNK_H
|
||||||
|
|
||||||
|
#include "quickjs.h"
|
||||||
|
|
||||||
|
JSValue js_chipmunk2d_use(JSContext*);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
[wrap-file]
|
[wrap-git]
|
||||||
directory = Chipmunk2D-Chipmunk-7.0.3
|
url = https://github.com/slembcke/Chipmunk2D.git
|
||||||
source_url = https://github.com/slembcke/Chipmunk2D/archive/Chipmunk-7.0.3.tar.gz
|
revision = Chipmunk-7.0.3
|
||||||
source_filename = Chipmunk2D-Chipmunk-7.0.3.tar.gz
|
depth = 1
|
||||||
source_hash = 1e6f093812d6130e45bdf4cb80280cb3c93d1e1833d8cf989d554d7963b7899a
|
|
||||||
patch_filename = chipmunk_7.0.3-1_patch.zip
|
|
||||||
patch_url = https://wrapdb.mesonbuild.com/v2/chipmunk_7.0.3-1/get_patch
|
|
||||||
patch_hash = 90e5e1be7712812cd303974910c37e7c4f2d2c8060312521b3a7995daa54f66a
|
|
||||||
wrapdb_version = 7.0.3-1
|
|
||||||
|
|
||||||
[provide]
|
|
||||||
chipmunk = chipmunk_dep
|
|
||||||
|
|
||||||
|
|||||||
290
tests/chipmunk2d.js
Normal file
290
tests/chipmunk2d.js
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
// chipmunk_test.js
|
||||||
|
var chipmunk = use('chipmunk2d');
|
||||||
|
var os = use('os');
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
var EPSILON = 1e-12; // Tolerance for floating-point comparisons
|
||||||
|
|
||||||
|
// Helper function to create a vector
|
||||||
|
function vec(x, y) {
|
||||||
|
return { x: x, y: y };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep comparison function for objects and physics properties
|
||||||
|
function deepCompare(expected, actual, path = '') {
|
||||||
|
if (expected === actual) return { passed: true, messages: [] };
|
||||||
|
|
||||||
|
// Handle vector comparison
|
||||||
|
if (expected && expected.x !== undefined && expected.y !== undefined &&
|
||||||
|
actual && actual.x !== undefined && actual.y !== undefined) {
|
||||||
|
const dx = Math.abs(expected.x - actual.x);
|
||||||
|
const dy = Math.abs(expected.y - actual.y);
|
||||||
|
if (dx <= EPSILON && dy <= EPSILON) {
|
||||||
|
return { passed: true, messages: [] };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
passed: false,
|
||||||
|
messages: [
|
||||||
|
`Vector mismatch at ${path}: expected (${expected.x}, ${expected.y}), got (${actual.x}, ${actual.y})`,
|
||||||
|
`Differences: x=${dx}, y=${dy} exceed tolerance ${EPSILON}`
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number comparison with tolerance
|
||||||
|
if (typeof expected === 'number' && typeof actual === 'number') {
|
||||||
|
const diff = Math.abs(expected - actual);
|
||||||
|
if (diff <= EPSILON) {
|
||||||
|
return { passed: true, messages: [] };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
passed: false,
|
||||||
|
messages: [
|
||||||
|
`Number mismatch at ${path}: expected ${expected}, got ${actual}`,
|
||||||
|
`Difference ${diff} exceeds tolerance ${EPSILON}`
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array comparison
|
||||||
|
if (Array.isArray(expected) && Array.isArray(actual)) {
|
||||||
|
if (expected.length !== actual.length) {
|
||||||
|
return {
|
||||||
|
passed: false,
|
||||||
|
messages: [`Array length mismatch at ${path}: expected ${expected.length}, got ${actual.length}`]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let messages = [];
|
||||||
|
for (let i = 0; i < expected.length; i++) {
|
||||||
|
const result = deepCompare(expected[i], actual[i], `${path}[${i}]`);
|
||||||
|
if (!result.passed) messages.push(...result.messages);
|
||||||
|
}
|
||||||
|
return { passed: messages.length === 0, messages };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object comparison
|
||||||
|
if (typeof expected === 'object' && expected !== null &&
|
||||||
|
typeof actual === 'object' && actual !== null) {
|
||||||
|
const expKeys = Object.keys(expected).sort();
|
||||||
|
const actKeys = Object.keys(actual).sort();
|
||||||
|
if (JSON.stringify(expKeys) !== JSON.stringify(actKeys)) {
|
||||||
|
return {
|
||||||
|
passed: false,
|
||||||
|
messages: [`Object keys mismatch at ${path}: expected ${expKeys}, got ${actKeys}`]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let messages = [];
|
||||||
|
for (let key of expKeys) {
|
||||||
|
const result = deepCompare(expected[key], actual[key], `${path}.${key}`);
|
||||||
|
if (!result.passed) messages.push(...result.messages);
|
||||||
|
}
|
||||||
|
return { passed: messages.length === 0, messages };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: false,
|
||||||
|
messages: [`Value mismatch at ${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test cases for Chipmunk functionality
|
||||||
|
var testCases = [
|
||||||
|
// Space tests
|
||||||
|
{
|
||||||
|
name: "Space creation and gravity",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
space.gravity = vec(0, -9.81);
|
||||||
|
return deepCompare(vec(0, -9.81), space.gravity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Space iterations",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
space.iterations = 5;
|
||||||
|
return deepCompare(5, space.iterations);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Body tests
|
||||||
|
{
|
||||||
|
name: "Body creation and position",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body = space.body();
|
||||||
|
body.position = vec(10, 20);
|
||||||
|
return deepCompare(vec(10, 20), body.position);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Body mass and moment",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body = space.body();
|
||||||
|
body.type = 0
|
||||||
|
body.mass = 10;
|
||||||
|
body.moment = 100;
|
||||||
|
return {
|
||||||
|
passed: deepCompare(10, body.mass).passed && deepCompare(100, body.moment).passed,
|
||||||
|
messages: [
|
||||||
|
...deepCompare(10, body.mass).messages,
|
||||||
|
...deepCompare(100, body.moment).messages
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Body velocity",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body = space.body();
|
||||||
|
body.velocity = vec(5, -5);
|
||||||
|
return deepCompare(vec(5, -5), body.velocity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Body force application",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body = space.body();
|
||||||
|
body.type = 0
|
||||||
|
body.mass = 1;
|
||||||
|
body.moment = 1
|
||||||
|
body.applyForceAtWorldPoint(vec(10, 0), vec(0, 0));
|
||||||
|
space.step(1/60); // One frame at 60 FPS
|
||||||
|
const expectedVelocity = vec(10/60, 0); // F = ma, v = at
|
||||||
|
return deepCompare(expectedVelocity, body.velocity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Shape tests
|
||||||
|
{
|
||||||
|
name: "Circle shape creation",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body = space.body();
|
||||||
|
const shape = body.circle({radius:5, offset:vec(0, 0)});
|
||||||
|
const radiusResult = deepCompare(5, shape.radius);
|
||||||
|
const offsetResult = deepCompare(vec(0, 0), shape.offset);
|
||||||
|
return {
|
||||||
|
passed: radiusResult.passed && offsetResult.passed,
|
||||||
|
messages: [...radiusResult.messages, ...offsetResult.messages]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Segment shape creation",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body = space.body();
|
||||||
|
const shape = body.add_segment_shape(vec(0, 0), vec(10, 10), 2);
|
||||||
|
for (var i in shape) console.log(i)
|
||||||
|
shape.setEndpoints(vec(1, 1), vec(11, 11));
|
||||||
|
const radiusResult = deepCompare(2, shape.radius);
|
||||||
|
// Note: Chipmunk doesn't provide endpoint getters, so we test indirectly
|
||||||
|
return radiusResult;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Constraint tests
|
||||||
|
{
|
||||||
|
name: "Pin joint",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body1 = space.body();
|
||||||
|
const body2 = space.body();
|
||||||
|
body1.position = vec(0, 0);
|
||||||
|
body2.position = vec(10, 0);
|
||||||
|
const joint = space.pin(body1, body2);
|
||||||
|
joint.distance = 10;
|
||||||
|
return deepCompare(10, joint.distance);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pivot joint",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
const body1 = space.body();
|
||||||
|
const body2 = space.body();
|
||||||
|
const joint = space.pivot(body1, body2, vec(5, 5));
|
||||||
|
const anchorAResult = deepCompare(vec(5, 5), joint.anchor_a);
|
||||||
|
const anchorBResult = deepCompare(vec(5, 5), joint.anchor_b);
|
||||||
|
return {
|
||||||
|
passed: anchorAResult.passed && anchorBResult.passed,
|
||||||
|
messages: [...anchorAResult.messages, ...anchorBResult.messages]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Simulation test
|
||||||
|
{
|
||||||
|
name: "Basic gravity simulation",
|
||||||
|
run: () => {
|
||||||
|
const space = chipmunk.make_space();
|
||||||
|
console.log(space.gravity)
|
||||||
|
space.gravity = vec(0, -9.81);
|
||||||
|
console.log(space.gravity)
|
||||||
|
const body = space.body();
|
||||||
|
body.circle(5);
|
||||||
|
body.mass = 1;
|
||||||
|
body.moment = 1;
|
||||||
|
body.position = vec(0, 100);
|
||||||
|
for (var i = 0; i < 61; i++) space.step(1/60)
|
||||||
|
const expectedPos = vec(0, 100 - (9.81/2)); // s = ut + (1/2)at^2
|
||||||
|
return deepCompare(expectedPos, body.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Run tests and collect results
|
||||||
|
let results = [];
|
||||||
|
let testCount = 0;
|
||||||
|
|
||||||
|
for (let test of testCases) {
|
||||||
|
testCount++;
|
||||||
|
let testName = `Test ${testCount}: ${test.name}`;
|
||||||
|
try {
|
||||||
|
const result = test.run();
|
||||||
|
results.push({
|
||||||
|
testName,
|
||||||
|
passed: result.passed,
|
||||||
|
messages: result.messages
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.passed) {
|
||||||
|
console.log(`\nDetailed Failure Report for ${testName}:`);
|
||||||
|
console.log(result.messages.join("\n"));
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
results.push({
|
||||||
|
testName,
|
||||||
|
passed: false,
|
||||||
|
messages: [`Test threw exception: ${e.message}`]
|
||||||
|
});
|
||||||
|
console.log(`\nException in ${testName}:`);
|
||||||
|
console.log(e.stack || e.message);
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
console.log("\nTest Summary:");
|
||||||
|
results.forEach(result => {
|
||||||
|
console.log(`${result.testName} - ${result.passed ? "Passed" : "Failed"}`);
|
||||||
|
if (!result.passed) {
|
||||||
|
console.log(result.messages.map(m => " " + m).join("\n"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let passedCount = results.filter(r => r.passed).length;
|
||||||
|
console.log(`\nResult: ${passedCount}/${testCount} tests passed`);
|
||||||
|
|
||||||
|
if (passedCount < testCount) {
|
||||||
|
console.log("Overall: FAILED");
|
||||||
|
os.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log("Overall: PASSED");
|
||||||
|
os.exit(0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user