Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad4f3d3f58 | ||
|
|
b23dca6934 | ||
|
|
8fba19c820 |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -153,6 +153,7 @@ jobs:
|
||||
|
||||
deploy-itch:
|
||||
needs: [package-dist]
|
||||
if: ${{ false }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# dmon
|
||||
|
||||
### watch() <sub>function</sub>
|
||||
|
||||
Start watching the root directory, recursively.
|
||||
|
||||
This function begins monitoring the specified directory and its subdirectories recursively for events such as file creation, deletion, modification, or movement. Events are queued and can be retrieved by calling poll.
|
||||
|
||||
:throws: An error if dmon is already watching.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### unwatch() <sub>function</sub>
|
||||
|
||||
Stop watching the currently monitored directory.
|
||||
|
||||
This function halts filesystem monitoring for the directory previously set by watch. It clears the watch state, allowing a new watch to be started.
|
||||
|
||||
:throws: An error if no directory is currently being watched.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### poll(callback) <sub>function</sub>
|
||||
|
||||
Retrieve and process queued filesystem events.
|
||||
|
||||
This function dequeues all pending filesystem events and invokes the provided callback for each one. The callback receives an event object with properties: 'action' (string: "create", "delete", "modify", or "move"), 'root' (string: watched directory), 'file' (string: affected file path), and 'old' (string: previous file path for move events, empty if not applicable).
|
||||
|
||||
|
||||
|
||||
**callback**: A function to call for each event, receiving an event object as its argument.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# enet
|
||||
|
||||
### initialize() <sub>function</sub>
|
||||
|
||||
|
||||
Initialize the ENet library. Must be called before using any ENet functionality.
|
||||
Throws an error if initialization fails.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### deinitialize() <sub>function</sub>
|
||||
|
||||
|
||||
Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
|
||||
need any ENet functionality.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### create_host(address) <sub>function</sub>
|
||||
|
||||
|
||||
Create an ENet host for either a client-like unbound host or a server bound to a specific
|
||||
address and port:
|
||||
|
||||
- If no argument is provided, creates an unbound "client-like" host with default settings
|
||||
(maximum 32 peers, 2 channels, unlimited bandwidth).
|
||||
- If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
|
||||
that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
|
||||
|
||||
Throws an error if host creation fails for any reason.
|
||||
|
||||
omit to create an unbound client-like host.
|
||||
|
||||
|
||||
**address**: (optional) A string in 'ip:port' format to bind the host (server), or
|
||||
|
||||
|
||||
**Returns**: An ENetHost object.
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# nota
|
||||
|
||||
### encode(value) <sub>function</sub>
|
||||
|
||||
Convert a JavaScript value into a NOTA-encoded ArrayBuffer.
|
||||
|
||||
This function serializes JavaScript values (such as numbers, strings, booleans, arrays, objects, or ArrayBuffers) into the NOTA binary format. The resulting ArrayBuffer can be stored or transmitted and later decoded back into a JavaScript value.
|
||||
|
||||
:throws: An error if no argument is provided.
|
||||
|
||||
|
||||
**value**: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
|
||||
|
||||
|
||||
**Returns**: An ArrayBuffer containing the NOTA-encoded data.
|
||||
|
||||
|
||||
### decode(buffer) <sub>function</sub>
|
||||
|
||||
Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
|
||||
|
||||
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns undefined.
|
||||
|
||||
|
||||
|
||||
**buffer**: An ArrayBuffer containing NOTA-encoded data to decode.
|
||||
|
||||
|
||||
**Returns**: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# enet_host
|
||||
|
||||
### service(callback, timeout) <sub>function</sub>
|
||||
|
||||
|
||||
Poll for and process any available network events (connect, receive, disconnect, or none)
|
||||
from this host, calling the provided callback for each event. This function loops until
|
||||
no more events are available in the current timeframe.
|
||||
|
||||
Event object properties:
|
||||
- type: String, one of "connect", "receive", "disconnect", or "none".
|
||||
- peer: (present if type = "connect") The ENetPeer object for the new connection.
|
||||
- channelID: (present if type = "receive") The channel on which the data was received.
|
||||
- data: (present if type = "receive") The received data as a *plain JavaScript object*.
|
||||
If the JSON parse fails or the data isn't an object, a JavaScript error is thrown.
|
||||
|
||||
object as its single argument.
|
||||
|
||||
|
||||
**callback**: A function called once for each available event, receiving an event
|
||||
|
||||
**timeout**: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### connect(host, port) <sub>function</sub>
|
||||
|
||||
|
||||
Initiate a connection from this host to a remote server. Throws an error if the
|
||||
connection cannot be started.
|
||||
|
||||
|
||||
|
||||
**host**: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
|
||||
|
||||
**port**: The port number to connect to.
|
||||
|
||||
|
||||
**Returns**: An ENetPeer object representing the connection.
|
||||
|
||||
|
||||
### flush() <sub>function</sub>
|
||||
|
||||
|
||||
Flush all pending outgoing packets for this host immediately.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### broadcast(data) <sub>function</sub>
|
||||
|
||||
|
||||
Broadcast a JavaScript object to all connected peers on channel 0. The object is
|
||||
serialized to JSON, and the packet is sent reliably. Throws an error if serialization fails.
|
||||
|
||||
|
||||
|
||||
**data**: A JavaScript object to broadcast to all peers.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# enet_peer
|
||||
|
||||
### send(data) <sub>function</sub>
|
||||
|
||||
|
||||
Send a JavaScript object to this peer on channel 0. The object is serialized to JSON and
|
||||
sent reliably. Throws an error if serialization fails.
|
||||
|
||||
|
||||
|
||||
**data**: A JavaScript object to send.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect() <sub>function</sub>
|
||||
|
||||
|
||||
Request a graceful disconnection from this peer. The connection will close after
|
||||
pending data is sent.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect_now() <sub>function</sub>
|
||||
|
||||
|
||||
Immediately terminate the connection to this peer, discarding any pending data.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### disconnect_later() <sub>function</sub>
|
||||
|
||||
|
||||
Request a disconnection from this peer after all queued packets are sent.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### reset() <sub>function</sub>
|
||||
|
||||
|
||||
Reset this peer's connection, immediately dropping it and clearing its internal state.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### ping() <sub>function</sub>
|
||||
|
||||
|
||||
Send a ping request to this peer to measure latency.
|
||||
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### throttle_configure(interval, acceleration, deceleration) <sub>function</sub>
|
||||
|
||||
|
||||
Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
|
||||
rate based on packet loss or congestion.
|
||||
|
||||
|
||||
|
||||
**interval**: The interval (ms) between throttle adjustments.
|
||||
|
||||
**acceleration**: The factor to increase sending speed when conditions improve.
|
||||
|
||||
**deceleration**: The factor to decrease sending speed when conditions worsen.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
|
||||
### timeout(timeout_limit, timeout_min, timeout_max) <sub>function</sub>
|
||||
|
||||
|
||||
Set timeout parameters for this peer, determining how long ENet waits before considering
|
||||
the connection lost.
|
||||
|
||||
|
||||
|
||||
**timeout_limit**: The total time (ms) before the peer is disconnected.
|
||||
|
||||
**timeout_min**: The minimum timeout (ms) used for each timeout attempt.
|
||||
|
||||
**timeout_max**: The maximum timeout (ms) used for each timeout attempt.
|
||||
|
||||
|
||||
**Returns**: None
|
||||
|
||||
22
meson.build
22
meson.build
@@ -1,5 +1,5 @@
|
||||
project('prosperon', ['c', 'cpp'],
|
||||
version: '0.9.3',
|
||||
version: '0.9.2',
|
||||
meson_version: '>=1.4',
|
||||
default_options : [ 'cpp_std=c++11'])
|
||||
|
||||
@@ -72,6 +72,18 @@ sdl3_opts.add_cmake_defines({
|
||||
'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')
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
@@ -131,14 +143,11 @@ deps += dependency('physfs', static:true)
|
||||
#deps += cc.find_library('opencv')
|
||||
|
||||
deps += dependency('threads')
|
||||
deps += dependency('chipmunk', static:true)
|
||||
deps += dependency('enet', static:true)
|
||||
deps += dependency('soloud', static:true)
|
||||
|
||||
#deps += dependency('qjs-chipmunk', static:false)
|
||||
|
||||
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']
|
||||
|
||||
@@ -231,7 +240,8 @@ tests = [
|
||||
'spawn_actor',
|
||||
'empty',
|
||||
'nota',
|
||||
'enet'
|
||||
'enet',
|
||||
'chipmunk2d'
|
||||
]
|
||||
|
||||
foreach file : tests
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
site_name: Prosperon Documentation
|
||||
edit_uri: edit/master/doc/docs
|
||||
|
||||
plugins:
|
||||
- search
|
||||
|
||||
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;
|
||||
@@ -1,6 +1,7 @@
|
||||
var io = use('io')
|
||||
var util = use('util')
|
||||
|
||||
|
||||
var dumpfolder = ".prosperon";
|
||||
|
||||
io.mkdir(dumpfolder)
|
||||
@@ -30,12 +31,20 @@ Cmdline.register_order(
|
||||
|
||||
var gs = ['console', 'prosperon', 'actor', 'use']
|
||||
|
||||
Object.getOwnPropertyDescriptor(prosperon.c_types.transform, 'pos')[prosperon.DOC] = 'TEST DOC'
|
||||
|
||||
console.log(Object.getOwnPropertyDescriptor(prosperon.c_types.transform,'pos')[prosperon.DOC])
|
||||
|
||||
for (var g of gs)
|
||||
io.slurpwrite(`.src/docs/api/${g}.md`, doc.writeDocFile(globalThis[g], g))
|
||||
|
||||
var coredocs = io.enumerate("scripts/modules", 0)
|
||||
coredocs = coredocs.filter(x => io.match("**/*.js", x)).map(x => x.name())
|
||||
|
||||
var TYPEPATH = '.src/docs/api/types/'
|
||||
for (var c in prosperon.c_types)
|
||||
io.slurpwrite(`${TYPEPATH}${c}.md`, doc.writeDocFile(prosperon.c_types[c], c))
|
||||
|
||||
var APIPATH = '.src/docs/api/modules/'
|
||||
|
||||
for (var m of coredocs) {
|
||||
@@ -43,10 +52,6 @@ Cmdline.register_order(
|
||||
var path = `${APIPATH}${m}.md`
|
||||
io.slurpwrite(path, doc.writeDocFile(u, m))
|
||||
}
|
||||
|
||||
var TYPEPATH = '.src/docs/api/types/'
|
||||
for (var c in prosperon.c_types)
|
||||
io.slurpwrite(`${TYPEPATH}${c}.md`, doc.writeDocFile(prosperon.c_types[c], c))
|
||||
|
||||
var DULLPATH = '.src/docs/dull/'
|
||||
var mixins = ['Object', 'String', 'Array', 'Map', 'WeakMap', 'Symbol','Set', 'WeakSet', 'ArrayBuffer', 'Function']
|
||||
|
||||
@@ -7585,6 +7585,7 @@ JSValue js_imgui_use(JSContext *js);
|
||||
#include "qjs_nota.h"
|
||||
#include "qjs_enet.h"
|
||||
#include "qjs_soloud.h"
|
||||
#include "qjs_chipmunk.h"
|
||||
|
||||
#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(nota));
|
||||
arrput(module_registry, MISTLINE(enet));
|
||||
arrput(module_registry, MISTLINE(chipmunk2d));
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
arrput(module_registry, MISTLINE(tracy));
|
||||
|
||||
171
source/nota.h
171
source/nota.h
@@ -362,177 +362,6 @@ void nota_write_record(NotaBuffer *nb, unsigned long long count)
|
||||
nb->size -= (10 - used);
|
||||
}
|
||||
|
||||
void nota_write_number_str(NotaBuffer *nb, const char *str)
|
||||
{
|
||||
/* -------------------------------------------
|
||||
1) Parse sign
|
||||
------------------------------------------- */
|
||||
int negative = 0;
|
||||
if (*str == '+') {
|
||||
str++;
|
||||
}
|
||||
else if (*str == '-') {
|
||||
negative = 1;
|
||||
str++;
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
2) Parse integer part
|
||||
------------------------------------------- */
|
||||
long long coefficient = 0;
|
||||
int got_digits = 0;
|
||||
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
got_digits = 1;
|
||||
int d = (*str - '0');
|
||||
str++;
|
||||
|
||||
// Basic overflow check (very naive):
|
||||
if (coefficient <= (LLONG_MAX - d) / 10) {
|
||||
coefficient = coefficient * 10 + d;
|
||||
} else {
|
||||
// If you want to handle overflow by switching to float, do that here.
|
||||
// For simplicity, let's just keep wrapping. In production, be careful!
|
||||
coefficient = coefficient * 10 + d;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
3) Check for decimal part
|
||||
------------------------------------------- */
|
||||
int has_decimal_point = 0;
|
||||
int fraction_digits = 0;
|
||||
|
||||
if (*str == '.') {
|
||||
has_decimal_point = 1;
|
||||
str++;
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
got_digits = 1;
|
||||
int d = (*str - '0');
|
||||
str++;
|
||||
fraction_digits++;
|
||||
if (coefficient <= (LLONG_MAX - d) / 10) {
|
||||
coefficient = coefficient * 10 + d;
|
||||
} else {
|
||||
// Same naive overflow comment
|
||||
coefficient = coefficient * 10 + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
4) Check for exponent part
|
||||
------------------------------------------- */
|
||||
int exponent_negative = 0;
|
||||
long long exponent_val = 0;
|
||||
|
||||
if (*str == 'e' || *str == 'E') {
|
||||
str++;
|
||||
if (*str == '+') {
|
||||
str++;
|
||||
}
|
||||
else if (*str == '-') {
|
||||
exponent_negative = 1;
|
||||
str++;
|
||||
}
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
int d = (*str - '0');
|
||||
str++;
|
||||
if (exponent_val <= (LLONG_MAX - d) / 10) {
|
||||
exponent_val = exponent_val * 10 + d;
|
||||
} else {
|
||||
// Again, naive overflow handling
|
||||
exponent_val = exponent_val * 10 + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
5) If there were no valid digits at all,
|
||||
store 0 and return. (simple fallback)
|
||||
------------------------------------------- */
|
||||
if (!got_digits) {
|
||||
nota_write_int_buf(nb, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
6) Combine fraction digits into exponent
|
||||
final_exponent = exponent_val - fraction_digits
|
||||
(apply exponent sign if any)
|
||||
------------------------------------------- */
|
||||
if (exponent_negative) {
|
||||
exponent_val = -exponent_val;
|
||||
}
|
||||
long long final_exponent = exponent_val - fraction_digits;
|
||||
|
||||
/* -------------------------------------------
|
||||
7) Decide if we are storing an integer
|
||||
or a float in Nota format.
|
||||
-------------------------------------------
|
||||
Rule used here:
|
||||
- If there's no decimal point AND final_exponent == 0,
|
||||
=> integer
|
||||
- If we do have a decimal point, but fraction_digits == 0
|
||||
and exponent_val == 0, then the user typed something
|
||||
like "123." or "100.0". That is effectively an integer,
|
||||
so store it as an integer if you want a purely numeric approach.
|
||||
- Otherwise store as float.
|
||||
------------------------------------------- */
|
||||
|
||||
// If "no decimal" => definitely integer:
|
||||
// or decimal present but fraction_digits=0 & exponent_val=0 => integer
|
||||
int treat_as_integer = 0;
|
||||
if (!has_decimal_point && final_exponent == 0) {
|
||||
treat_as_integer = 1;
|
||||
}
|
||||
else if (has_decimal_point && fraction_digits == 0 && exponent_val == 0) {
|
||||
// Means "123." or "123.0"
|
||||
treat_as_integer = 1;
|
||||
}
|
||||
|
||||
if (treat_as_integer) {
|
||||
// If negative => flip the sign in the stored value
|
||||
if (negative) {
|
||||
coefficient = -coefficient;
|
||||
}
|
||||
// Write the integer in Nota format (varint with sign bit)
|
||||
nota_write_int_buf(nb, coefficient);
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------
|
||||
8) Write as float in Nota format
|
||||
We do basically the same approach as
|
||||
nota_write_float_buf does:
|
||||
- NOTA_FLOAT nibble
|
||||
- sign bit if negative
|
||||
- exponent sign bit if final_exponent < 0
|
||||
- varint of |final_exponent|
|
||||
- varint of |coefficient|
|
||||
------------------------------------------- */
|
||||
{
|
||||
char *p = nota_buffer_alloc(nb, 21); // Up to ~21 bytes worst-case
|
||||
p[0] = NOTA_FLOAT;
|
||||
if (negative) {
|
||||
p[0] |= (1 << 3); // Mantissa sign bit
|
||||
}
|
||||
if (final_exponent < 0) {
|
||||
p[0] |= (1 << 4); // Exponent sign bit
|
||||
final_exponent = -final_exponent;
|
||||
}
|
||||
// Write exponent as varint (with 3 bits used in the first byte)
|
||||
char *c = nota_continue_num(final_exponent, p, 3);
|
||||
// Write the absolute coefficient (7 bits used in the first byte)
|
||||
char *end = nota_continue_num(coefficient, c, 7);
|
||||
|
||||
// Adjust the buffer size to the actual used length
|
||||
size_t used = (size_t)(end - p);
|
||||
nb->size -= (21 - used);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void nota_write_number(NotaBuffer *nb, double n)
|
||||
{
|
||||
nota_write_int_or_float_buf(nb, n);
|
||||
|
||||
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
|
||||
@@ -150,19 +150,14 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val)
|
||||
int tag = JS_VALUE_GET_TAG(val);
|
||||
|
||||
switch (tag) {
|
||||
case JS_TAG_INT: {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, val);
|
||||
nota_write_number(&enc->nb, d);
|
||||
return;
|
||||
}
|
||||
case JS_TAG_INT:
|
||||
case JS_TAG_BIG_INT:
|
||||
case JS_TAG_FLOAT64:
|
||||
case JS_TAG_BIG_DECIMAL:
|
||||
case JS_TAG_BIG_FLOAT: {
|
||||
const char *str = JS_ToCString(ctx, val);
|
||||
nota_write_number_str(&enc->nb, str);
|
||||
JS_FreeCString(ctx, str);
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, val);
|
||||
nota_write_number(&enc->nb, d);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
[wrap-file]
|
||||
directory = Chipmunk2D-Chipmunk-7.0.3
|
||||
source_url = https://github.com/slembcke/Chipmunk2D/archive/Chipmunk-7.0.3.tar.gz
|
||||
source_filename = Chipmunk2D-Chipmunk-7.0.3.tar.gz
|
||||
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
|
||||
[wrap-git]
|
||||
url = https://github.com/slembcke/Chipmunk2D.git
|
||||
revision = Chipmunk-7.0.3
|
||||
depth = 1
|
||||
|
||||
|
||||
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