Skip to main content

arcane_engine/scripting/
physics_ops.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use deno_core::OpState;
5
6use crate::physics::types::*;
7use crate::physics::world::PhysicsWorld;
8
9/// Wrapper for physics state in OpState.
10pub struct PhysicsState(pub Option<PhysicsWorld>);
11
12#[deno_core::op2(fast)]
13fn op_create_physics_world(state: &mut OpState, gravity_x: f64, gravity_y: f64) {
14    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
15    physics.borrow_mut().0 = Some(PhysicsWorld::new(gravity_x as f32, gravity_y as f32));
16}
17
18#[deno_core::op2(fast)]
19fn op_destroy_physics_world(state: &mut OpState) {
20    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
21    physics.borrow_mut().0 = None;
22}
23
24#[deno_core::op2(fast)]
25fn op_physics_step(state: &mut OpState, dt: f64) {
26    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
27    if let Some(world) = physics.borrow_mut().0.as_mut() {
28        world.step(dt as f32);
29    }
30}
31
32/// Create a body. shape_type: 0=circle, 1=aabb. body_type: 0=static, 1=dynamic, 2=kinematic.
33/// For circle: shape_p1=radius, shape_p2 unused.
34/// For AABB: shape_p1=half_w, shape_p2=half_h.
35#[deno_core::op2(fast)]
36fn op_create_body(
37    state: &mut OpState,
38    body_type: u32,
39    shape_type: u32,
40    shape_p1: f64,
41    shape_p2: f64,
42    x: f64,
43    y: f64,
44    mass: f64,
45    restitution: f64,
46    friction: f64,
47    layer: u32,
48    mask: u32,
49) -> u32 {
50    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
51    let mut ps = physics.borrow_mut();
52    let world = match ps.0.as_mut() {
53        Some(w) => w,
54        None => return u32::MAX,
55    };
56
57    let bt = match body_type {
58        0 => BodyType::Static,
59        1 => BodyType::Dynamic,
60        2 => BodyType::Kinematic,
61        _ => return u32::MAX,
62    };
63
64    let shape = match shape_type {
65        0 => Shape::Circle {
66            radius: shape_p1 as f32,
67        },
68        1 => Shape::AABB {
69            half_w: shape_p1 as f32,
70            half_h: shape_p2 as f32,
71        },
72        _ => return u32::MAX,
73    };
74
75    let material = Material {
76        restitution: restitution as f32,
77        friction: friction as f32,
78    };
79
80    world.add_body(bt, shape, x as f32, y as f32, mass as f32, material, layer as u16, mask as u16)
81}
82
83#[deno_core::op2(fast)]
84fn op_remove_body(state: &mut OpState, id: u32) {
85    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
86    let mut ps = physics.borrow_mut();
87    if let Some(world) = ps.0.as_mut() {
88        world.remove_body(id);
89    }
90}
91
92/// Returns [x, y, angle, vx, vy, angular_velocity] or empty vec.
93#[deno_core::op2]
94#[serde]
95fn op_get_body_state(state: &mut OpState, id: u32) -> Vec<f64> {
96    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
97    let ps = physics.borrow();
98    match ps.0.as_ref().and_then(|w| w.get_body(id)) {
99        Some(body) => vec![
100            body.x as f64,
101            body.y as f64,
102            body.angle as f64,
103            body.vx as f64,
104            body.vy as f64,
105            body.angular_velocity as f64,
106            if body.sleeping { 1.0 } else { 0.0 },
107        ],
108        None => vec![],
109    }
110}
111
112#[deno_core::op2(fast)]
113fn op_set_body_velocity(state: &mut OpState, id: u32, vx: f64, vy: f64) {
114    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
115    let mut ps = physics.borrow_mut();
116    if let Some(world) = ps.0.as_mut() {
117        world.set_velocity(id, vx as f32, vy as f32);
118    }
119}
120
121#[deno_core::op2(fast)]
122fn op_set_body_angular_velocity(state: &mut OpState, id: u32, av: f64) {
123    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
124    let mut ps = physics.borrow_mut();
125    if let Some(world) = ps.0.as_mut() {
126        world.set_angular_velocity(id, av as f32);
127    }
128}
129
130#[deno_core::op2(fast)]
131fn op_apply_force(state: &mut OpState, id: u32, fx: f64, fy: f64) {
132    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
133    let mut ps = physics.borrow_mut();
134    if let Some(world) = ps.0.as_mut() {
135        world.apply_force(id, fx as f32, fy as f32);
136    }
137}
138
139#[deno_core::op2(fast)]
140fn op_apply_impulse(state: &mut OpState, id: u32, ix: f64, iy: f64) {
141    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
142    let mut ps = physics.borrow_mut();
143    if let Some(world) = ps.0.as_mut() {
144        world.apply_impulse(id, ix as f32, iy as f32);
145    }
146}
147
148#[deno_core::op2(fast)]
149fn op_set_body_position(state: &mut OpState, id: u32, x: f64, y: f64) {
150    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
151    let mut ps = physics.borrow_mut();
152    if let Some(world) = ps.0.as_mut() {
153        world.set_position(id, x as f32, y as f32);
154    }
155}
156
157#[deno_core::op2(fast)]
158fn op_set_collision_layers(state: &mut OpState, id: u32, layer: u32, mask: u32) {
159    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
160    let mut ps = physics.borrow_mut();
161    if let Some(world) = ps.0.as_mut() {
162        world.set_collision_layers(id, layer as u16, mask as u16);
163    }
164}
165
166#[deno_core::op2(fast)]
167fn op_create_distance_joint(
168    state: &mut OpState,
169    body_a: u32,
170    body_b: u32,
171    distance: f64,
172) -> u32 {
173    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
174    let mut ps = physics.borrow_mut();
175    match ps.0.as_mut() {
176        Some(world) => world.add_constraint(Constraint::Distance {
177            id: 0,
178            body_a,
179            body_b,
180            distance: distance as f32,
181            anchor_a: (0.0, 0.0),
182            anchor_b: (0.0, 0.0),
183        }),
184        None => u32::MAX,
185    }
186}
187
188#[deno_core::op2(fast)]
189fn op_create_revolute_joint(
190    state: &mut OpState,
191    body_a: u32,
192    body_b: u32,
193    pivot_x: f64,
194    pivot_y: f64,
195) -> u32 {
196    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
197    let mut ps = physics.borrow_mut();
198    match ps.0.as_mut() {
199        Some(world) => world.add_constraint(Constraint::Revolute {
200            id: 0,
201            body_a,
202            body_b,
203            pivot: (pivot_x as f32, pivot_y as f32),
204        }),
205        None => u32::MAX,
206    }
207}
208
209#[deno_core::op2(fast)]
210fn op_remove_constraint(state: &mut OpState, id: u32) {
211    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
212    let mut ps = physics.borrow_mut();
213    if let Some(world) = ps.0.as_mut() {
214        world.remove_constraint(id);
215    }
216}
217
218/// Returns body IDs overlapping the query rectangle.
219#[deno_core::op2]
220#[serde]
221fn op_query_aabb(
222    state: &mut OpState,
223    min_x: f64,
224    min_y: f64,
225    max_x: f64,
226    max_y: f64,
227) -> Vec<u32> {
228    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
229    let ps = physics.borrow();
230    match ps.0.as_ref() {
231        Some(world) => world.query_aabb(min_x as f32, min_y as f32, max_x as f32, max_y as f32),
232        None => vec![],
233    }
234}
235
236/// Returns [] for no hit, [body_id, hit_x, hit_y, distance] for hit.
237#[deno_core::op2]
238#[serde]
239fn op_raycast(
240    state: &mut OpState,
241    origin_x: f64,
242    origin_y: f64,
243    dir_x: f64,
244    dir_y: f64,
245    max_dist: f64,
246) -> Vec<f64> {
247    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
248    let ps = physics.borrow();
249    match ps.0.as_ref() {
250        Some(world) => {
251            match world.raycast(
252                origin_x as f32,
253                origin_y as f32,
254                dir_x as f32,
255                dir_y as f32,
256                max_dist as f32,
257            ) {
258                Some((id, hx, hy, dist)) => {
259                    vec![id as f64, hx as f64, hy as f64, dist as f64]
260                }
261                None => vec![],
262            }
263        }
264        None => vec![],
265    }
266}
267
268/// Returns flattened contacts: [bodyA, bodyB, nx, ny, penetration, contactX, contactY, ...].
269#[deno_core::op2]
270#[serde]
271fn op_get_contacts(state: &mut OpState) -> Vec<f64> {
272    let physics = state.borrow_mut::<Rc<RefCell<PhysicsState>>>();
273    let ps = physics.borrow();
274    match ps.0.as_ref() {
275        Some(world) => {
276            let contacts = world.get_contacts();
277            let mut result = Vec::with_capacity(contacts.len() * 7);
278            for c in contacts {
279                result.push(c.body_a as f64);
280                result.push(c.body_b as f64);
281                result.push(c.normal.0 as f64);
282                result.push(c.normal.1 as f64);
283                result.push(c.penetration as f64);
284                result.push(c.contact_point.0 as f64);
285                result.push(c.contact_point.1 as f64);
286            }
287            result
288        }
289        None => vec![],
290    }
291}
292
293deno_core::extension!(
294    physics_ext,
295    ops = [
296        op_create_physics_world,
297        op_destroy_physics_world,
298        op_physics_step,
299        op_create_body,
300        op_remove_body,
301        op_get_body_state,
302        op_set_body_velocity,
303        op_set_body_angular_velocity,
304        op_apply_force,
305        op_apply_impulse,
306        op_set_body_position,
307        op_set_collision_layers,
308        op_create_distance_joint,
309        op_create_revolute_joint,
310        op_remove_constraint,
311        op_query_aabb,
312        op_raycast,
313        op_get_contacts,
314    ],
315);