1use std::cell::RefCell;
2use std::path::PathBuf;
3use std::rc::Rc;
4
5use deno_core::OpState;
6
7use crate::renderer::SpriteCommand;
8use crate::renderer::TilemapStore;
9use crate::renderer::PointLight;
10use crate::renderer::camera::CameraBounds;
11use crate::renderer::msdf::MsdfFontStore;
12
13#[derive(Clone, Debug)]
15pub enum BridgeAudioCommand {
16 LoadSound { id: u32, path: String },
17 PlaySound { id: u32, volume: f32, looping: bool },
18 StopSound { id: u32 },
19 StopAll,
20 SetMasterVolume { volume: f32 },
21
22 PlaySoundEx {
24 sound_id: u32,
25 instance_id: u64,
26 volume: f32,
27 looping: bool,
28 bus: u32,
29 pan: f32,
30 pitch: f32,
31 low_pass_freq: u32,
32 reverb_mix: f32,
33 reverb_delay_ms: u32,
34 },
35 PlaySoundSpatial {
36 sound_id: u32,
37 instance_id: u64,
38 volume: f32,
39 looping: bool,
40 bus: u32,
41 pitch: f32,
42 source_x: f32,
43 source_y: f32,
44 listener_x: f32,
45 listener_y: f32,
46 },
47 StopInstance { instance_id: u64 },
48 SetInstanceVolume { instance_id: u64, volume: f32 },
49 SetInstancePitch { instance_id: u64, pitch: f32 },
50 UpdateSpatialPositions {
51 updates: Vec<(u64, f32, f32)>, listener_x: f32,
53 listener_y: f32,
54 },
55 SetBusVolume { bus: u32, volume: f32 },
56}
57
58#[derive(Clone)]
61pub struct RenderBridgeState {
62 pub sprite_commands: Vec<SpriteCommand>,
63 pub camera_x: f32,
64 pub camera_y: f32,
65 pub camera_zoom: f32,
66 pub camera_dirty: bool,
68 pub delta_time: f64,
69 pub elapsed_time: f64,
71 pub keys_down: std::collections::HashSet<String>,
73 pub keys_pressed: std::collections::HashSet<String>,
74 pub mouse_x: f32,
75 pub mouse_y: f32,
76 pub mouse_buttons_down: std::collections::HashSet<u8>,
77 pub mouse_buttons_pressed: std::collections::HashSet<u8>,
78 pub gamepad_buttons_down: std::collections::HashSet<String>,
80 pub gamepad_buttons_pressed: std::collections::HashSet<String>,
82 pub gamepad_axes: std::collections::HashMap<String, f32>,
84 pub gamepad_count: u32,
86 pub gamepad_name: String,
88 pub touch_points: Vec<(u64, f32, f32)>,
90 pub touch_count: u32,
92 pub texture_load_queue: Vec<(String, u32)>,
94 pub texture_load_queue_linear: Vec<(String, u32)>,
96 pub base_dir: PathBuf,
98 pub next_texture_id: u32,
100 pub texture_path_to_id: std::collections::HashMap<String, u32>,
102 pub tilemaps: TilemapStore,
104 pub ambient_light: [f32; 3],
106 pub point_lights: Vec<PointLight>,
108 pub audio_commands: Vec<BridgeAudioCommand>,
110 pub next_sound_id: u32,
112 pub sound_path_to_id: std::collections::HashMap<String, u32>,
114 pub font_texture_queue: Vec<u32>,
116 pub viewport_width: f32,
118 pub viewport_height: f32,
119 pub scale_factor: f32,
121 pub clear_color: [f32; 4],
123 pub save_dir: PathBuf,
125 pub shader_create_queue: Vec<(u32, String, String)>,
127 pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
129 pub next_shader_id: u32,
131 pub effect_create_queue: Vec<(u32, String)>,
133 pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
135 pub effect_remove_queue: Vec<u32>,
137 pub effect_clear: bool,
139 pub next_effect_id: u32,
141 pub camera_bounds: Option<CameraBounds>,
143 pub gi_enabled: bool,
145 pub gi_intensity: f32,
147 pub gi_probe_spacing: Option<f32>,
149 pub gi_interval: Option<f32>,
151 pub gi_cascade_count: Option<u32>,
153 pub emissives: Vec<[f32; 8]>,
155 pub occluders: Vec<[f32; 4]>,
157 pub directional_lights: Vec<[f32; 5]>,
159 pub spot_lights: Vec<[f32; 9]>,
161 pub msdf_fonts: MsdfFontStore,
163 pub msdf_builtin_queue: Vec<(u32, u32)>,
165 pub msdf_shader_queue: Vec<(u32, String)>,
167 pub msdf_shader_pool: Vec<u32>,
169 pub msdf_texture_load_queue: Vec<(String, u32)>,
171 pub raw_texture_upload_queue: Vec<(u32, u32, u32, Vec<u8>)>,
173 pub frame_time_ms: f64,
175 pub draw_call_count: usize,
177}
178
179impl RenderBridgeState {
180 pub fn new(base_dir: PathBuf) -> Self {
181 let save_dir = base_dir.join(".arcane").join("saves");
182 Self {
183 sprite_commands: Vec::new(),
184 camera_x: 0.0,
185 camera_y: 0.0,
186 camera_zoom: 1.0,
187 camera_dirty: false,
188 delta_time: 0.0,
189 elapsed_time: 0.0,
190 keys_down: std::collections::HashSet::new(),
191 keys_pressed: std::collections::HashSet::new(),
192 mouse_x: 0.0,
193 mouse_y: 0.0,
194 mouse_buttons_down: std::collections::HashSet::new(),
195 mouse_buttons_pressed: std::collections::HashSet::new(),
196 gamepad_buttons_down: std::collections::HashSet::new(),
197 gamepad_buttons_pressed: std::collections::HashSet::new(),
198 gamepad_axes: std::collections::HashMap::new(),
199 gamepad_count: 0,
200 gamepad_name: String::new(),
201 touch_points: Vec::new(),
202 touch_count: 0,
203 texture_load_queue: Vec::new(),
204 texture_load_queue_linear: Vec::new(),
205 base_dir,
206 next_texture_id: 1,
207 texture_path_to_id: std::collections::HashMap::new(),
208 tilemaps: TilemapStore::new(),
209 ambient_light: [1.0, 1.0, 1.0],
210 point_lights: Vec::new(),
211 audio_commands: Vec::new(),
212 next_sound_id: 1,
213 sound_path_to_id: std::collections::HashMap::new(),
214 font_texture_queue: Vec::new(),
215 viewport_width: 800.0,
216 viewport_height: 600.0,
217 scale_factor: 1.0,
218 clear_color: [0.1, 0.1, 0.15, 1.0],
219 save_dir,
220 shader_create_queue: Vec::new(),
221 shader_param_queue: Vec::new(),
222 next_shader_id: 1,
223 effect_create_queue: Vec::new(),
224 effect_param_queue: Vec::new(),
225 effect_remove_queue: Vec::new(),
226 effect_clear: false,
227 next_effect_id: 1,
228 camera_bounds: None,
229 gi_enabled: false,
230 gi_intensity: 1.0,
231 gi_probe_spacing: None,
232 gi_interval: None,
233 gi_cascade_count: None,
234 emissives: Vec::new(),
235 occluders: Vec::new(),
236 directional_lights: Vec::new(),
237 spot_lights: Vec::new(),
238 msdf_fonts: MsdfFontStore::new(),
239 msdf_builtin_queue: Vec::new(),
240 msdf_shader_queue: Vec::new(),
241 msdf_shader_pool: Vec::new(),
242 msdf_texture_load_queue: Vec::new(),
243 raw_texture_upload_queue: Vec::new(),
244 frame_time_ms: 0.0,
245 draw_call_count: 0,
246 }
247 }
248}
249
250#[deno_core::op2(fast)]
253pub fn op_draw_sprite(
254 state: &mut OpState,
255 texture_id: u32,
256 x: f64,
257 y: f64,
258 w: f64,
259 h: f64,
260 layer: i32,
261 uv_x: f64,
262 uv_y: f64,
263 uv_w: f64,
264 uv_h: f64,
265 tint_r: f64,
266 tint_g: f64,
267 tint_b: f64,
268 tint_a: f64,
269 rotation: f64,
270 origin_x: f64,
271 origin_y: f64,
272 flip_x: f64,
273 flip_y: f64,
274 opacity: f64,
275 blend_mode: f64,
276 shader_id: f64,
277) {
278 let cmd = SpriteCommand {
279 texture_id,
280 x: x as f32,
281 y: y as f32,
282 w: w as f32,
283 h: h as f32,
284 layer,
285 uv_x: uv_x as f32,
286 uv_y: uv_y as f32,
287 uv_w: uv_w as f32,
288 uv_h: uv_h as f32,
289 tint_r: tint_r as f32,
290 tint_g: tint_g as f32,
291 tint_b: tint_b as f32,
292 tint_a: tint_a as f32,
293 rotation: rotation as f32,
294 origin_x: origin_x as f32,
295 origin_y: origin_y as f32,
296 flip_x: flip_x != 0.0,
297 flip_y: flip_y != 0.0,
298 opacity: opacity as f32,
299 blend_mode: (blend_mode as u8).min(3),
300 shader_id: shader_id as u32,
301 };
302 let active_target = {
304 use super::target_ops::TargetState;
305 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
306 ts.borrow().active_target
307 };
308 if let Some(target_id) = active_target {
309 use super::target_ops::TargetState;
310 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
311 ts.borrow_mut()
312 .target_sprite_queues
313 .entry(target_id)
314 .or_default()
315 .push(cmd);
316 } else {
317 let bridge = state.borrow::<Rc<RefCell<RenderBridgeState>>>();
318 bridge.borrow_mut().sprite_commands.push(cmd);
319 }
320}
321
322#[deno_core::op2(fast)]
324pub fn op_clear_sprites(state: &mut OpState) {
325 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
326 bridge.borrow_mut().sprite_commands.clear();
327}
328
329pub const SPRITE_STRIDE: usize = 22;
334
335#[deno_core::op2(fast)]
339pub fn op_submit_sprite_batch(state: &mut OpState, #[buffer] data: &[u8]) {
340 let floats: &[f32] = bytemuck::cast_slice(data);
341 let sprite_count = floats.len() / SPRITE_STRIDE;
342
343 let active_target = {
345 use super::target_ops::TargetState;
346 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
347 ts.borrow().active_target
348 };
349
350 let parse_cmd = |s: &[f32]| SpriteCommand {
351 texture_id: s[0].to_bits(),
352 x: s[1],
353 y: s[2],
354 w: s[3],
355 h: s[4],
356 layer: s[5].to_bits() as i32,
357 uv_x: s[6],
358 uv_y: s[7],
359 uv_w: s[8],
360 uv_h: s[9],
361 tint_r: s[10],
362 tint_g: s[11],
363 tint_b: s[12],
364 tint_a: s[13],
365 rotation: s[14],
366 origin_x: s[15],
367 origin_y: s[16],
368 flip_x: s[17] != 0.0,
369 flip_y: s[18] != 0.0,
370 opacity: s[19],
371 blend_mode: (s[20] as u8).min(3),
372 shader_id: s[21].to_bits(),
373 };
374
375 if let Some(target_id) = active_target {
376 use super::target_ops::TargetState;
377 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
378 let mut ts = ts.borrow_mut();
379 let queue = ts.target_sprite_queues.entry(target_id).or_default();
380 queue.reserve(sprite_count);
381 for i in 0..sprite_count {
382 let base = i * SPRITE_STRIDE;
383 queue.push(parse_cmd(&floats[base..base + SPRITE_STRIDE]));
384 }
385 } else {
386 let bridge = state.borrow::<Rc<RefCell<RenderBridgeState>>>();
387 let mut b = bridge.borrow_mut();
388 b.sprite_commands.reserve(sprite_count);
389 for i in 0..sprite_count {
390 let base = i * SPRITE_STRIDE;
391 b.sprite_commands.push(parse_cmd(&floats[base..base + SPRITE_STRIDE]));
392 }
393 }
394}
395
396#[deno_core::op2(fast)]
399pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
400 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
401 let mut b = bridge.borrow_mut();
402 b.camera_x = x as f32;
403 b.camera_y = y as f32;
404 b.camera_zoom = zoom as f32;
405 b.camera_dirty = true;
406}
407
408#[deno_core::op2]
410#[serde]
411pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
412 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
413 let b = bridge.borrow();
414 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
415}
416
417#[deno_core::op2(fast)]
420pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
421 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
422 let mut b = bridge.borrow_mut();
423
424 let resolved = if std::path::Path::new(path).is_absolute() {
426 path.to_string()
427 } else {
428 b.base_dir.join(path).to_string_lossy().to_string()
429 };
430
431 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
433 return id;
434 }
435
436 let id = b.next_texture_id;
437 b.next_texture_id += 1;
438 b.texture_path_to_id.insert(resolved.clone(), id);
439 b.texture_load_queue.push((resolved, id));
440 id
441}
442
443#[deno_core::op2(fast)]
447pub fn op_load_texture_linear(state: &mut OpState, #[string] path: &str) -> u32 {
448 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
449 let mut b = bridge.borrow_mut();
450
451 let resolved = if std::path::Path::new(path).is_absolute() {
453 path.to_string()
454 } else {
455 b.base_dir.join(path).to_string_lossy().to_string()
456 };
457
458 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
460 return id;
461 }
462
463 let id = b.next_texture_id;
464 b.next_texture_id += 1;
465 b.texture_path_to_id.insert(resolved.clone(), id);
466 b.texture_load_queue_linear.push((resolved, id));
467 id
468}
469
470#[deno_core::op2(fast)]
472pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
473 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
474 bridge.borrow().keys_down.contains(key)
475}
476
477#[deno_core::op2(fast)]
479pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
480 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
481 bridge.borrow().keys_pressed.contains(key)
482}
483
484#[deno_core::op2]
486#[serde]
487pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
488 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
489 let b = bridge.borrow();
490 vec![b.mouse_x as f64, b.mouse_y as f64]
491}
492
493#[deno_core::op2(fast)]
496pub fn op_is_mouse_button_down(state: &mut OpState, button: u8) -> bool {
497 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
498 bridge.borrow().mouse_buttons_down.contains(&button)
499}
500
501#[deno_core::op2(fast)]
504pub fn op_is_mouse_button_pressed(state: &mut OpState, button: u8) -> bool {
505 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
506 bridge.borrow().mouse_buttons_pressed.contains(&button)
507}
508
509#[deno_core::op2(fast)]
511pub fn op_get_delta_time(state: &mut OpState) -> f64 {
512 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
513 bridge.borrow().delta_time
514}
515
516#[deno_core::op2(fast)]
519pub fn op_create_solid_texture(
520 state: &mut OpState,
521 #[string] name: &str,
522 r: u32,
523 g: u32,
524 b: u32,
525 a: u32,
526) -> u32 {
527 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
528 let mut br = bridge.borrow_mut();
529
530 let key = format!("__solid__{name}");
531 if let Some(&id) = br.texture_path_to_id.get(&key) {
532 return id;
533 }
534
535 let id = br.next_texture_id;
536 br.next_texture_id += 1;
537 br.texture_path_to_id.insert(key.clone(), id);
538 br.texture_load_queue
540 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
541 id
542}
543
544#[deno_core::op2(fast)]
547pub fn op_upload_rgba_texture(
548 state: &mut OpState,
549 #[string] name: &str,
550 width: f64,
551 height: f64,
552 #[buffer] pixels: &[u8],
553) -> u32 {
554 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
555 let mut b = bridge.borrow_mut();
556
557 let key = format!("__raw__:{name}");
558 if let Some(&id) = b.texture_path_to_id.get(&key) {
559 return id;
560 }
561
562 let id = b.next_texture_id;
563 b.next_texture_id += 1;
564 b.texture_path_to_id.insert(key, id);
565 b.raw_texture_upload_queue.push((
566 id,
567 width as u32,
568 height as u32,
569 pixels.to_vec(),
570 ));
571 id
572}
573
574#[deno_core::op2(fast)]
576pub fn op_create_tilemap(
577 state: &mut OpState,
578 texture_id: u32,
579 width: u32,
580 height: u32,
581 tile_size: f64,
582 atlas_columns: u32,
583 atlas_rows: u32,
584) -> u32 {
585 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
586 bridge
587 .borrow_mut()
588 .tilemaps
589 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
590}
591
592#[deno_core::op2(fast)]
594pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
595 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
596 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
597 tm.set_tile(gx, gy, tile_id as u16);
598 }
599}
600
601#[deno_core::op2(fast)]
603pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
604 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
605 bridge
606 .borrow()
607 .tilemaps
608 .get(tilemap_id)
609 .map(|tm| tm.get_tile(gx, gy) as u32)
610 .unwrap_or(0)
611}
612
613#[deno_core::op2(fast)]
616pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
617 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
618 let mut b = bridge.borrow_mut();
619 let cam_x = b.camera_x;
620 let cam_y = b.camera_y;
621 let cam_zoom = b.camera_zoom;
622 let vp_w = 800.0;
624 let vp_h = 600.0;
625
626 if let Some(tm) = b.tilemaps.get(tilemap_id) {
627 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
628 b.sprite_commands.extend(cmds);
629 }
630}
631
632#[deno_core::op2(fast)]
637pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
638 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
639 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
640}
641
642#[deno_core::op2(fast)]
645pub fn op_add_point_light(
646 state: &mut OpState,
647 x: f64,
648 y: f64,
649 radius: f64,
650 r: f64,
651 g: f64,
652 b: f64,
653 intensity: f64,
654) {
655 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
656 bridge.borrow_mut().point_lights.push(PointLight {
657 x: x as f32,
658 y: y as f32,
659 radius: radius as f32,
660 r: r as f32,
661 g: g as f32,
662 b: b as f32,
663 intensity: intensity as f32,
664 });
665}
666
667#[deno_core::op2(fast)]
669pub fn op_clear_lights(state: &mut OpState) {
670 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
671 bridge.borrow_mut().point_lights.clear();
672}
673
674#[deno_core::op2(fast)]
678pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
679 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
680 let mut b = bridge.borrow_mut();
681
682 let resolved = if std::path::Path::new(path).is_absolute() {
683 path.to_string()
684 } else {
685 b.base_dir.join(path).to_string_lossy().to_string()
686 };
687
688 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
689 return id;
690 }
691
692 let id = b.next_sound_id;
693 b.next_sound_id += 1;
694 b.sound_path_to_id.insert(resolved.clone(), id);
695 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
696 id
697}
698
699#[deno_core::op2(fast)]
702pub fn op_play_sound(state: &mut OpState, id: u32, volume: f64, looping: bool) {
703 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
704 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySound { id, volume: volume as f32, looping });
705}
706
707#[deno_core::op2(fast)]
709pub fn op_stop_sound(state: &mut OpState, id: u32) {
710 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
711 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopSound { id });
712}
713
714#[deno_core::op2(fast)]
716pub fn op_stop_all_sounds(state: &mut OpState) {
717 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
718 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
719}
720
721#[deno_core::op2(fast)]
724pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
725 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
726 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
727}
728
729#[deno_core::op2(fast)]
733pub fn op_create_font_texture(state: &mut OpState) -> u32 {
734 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
735 let mut b = bridge.borrow_mut();
736
737 let key = "__builtin_font__".to_string();
738 if let Some(&id) = b.texture_path_to_id.get(&key) {
739 return id;
740 }
741
742 let id = b.next_texture_id;
743 b.next_texture_id += 1;
744 b.texture_path_to_id.insert(key, id);
745 b.font_texture_queue.push(id);
746 id
747}
748
749#[deno_core::op2]
753#[serde]
754pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
755 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
756 let b = bridge.borrow();
757 vec![b.viewport_width as f64, b.viewport_height as f64]
758}
759
760#[deno_core::op2(fast)]
762pub fn op_get_scale_factor(state: &mut OpState) -> f64 {
763 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
764 bridge.borrow().scale_factor as f64
765}
766
767#[deno_core::op2(fast)]
769pub fn op_set_background_color(state: &mut OpState, r: f64, g: f64, b: f64) {
770 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
771 let mut br = bridge.borrow_mut();
772 br.clear_color = [r as f32, g as f32, b as f32, 1.0];
773}
774
775#[deno_core::op2(fast)]
779pub fn op_save_file(state: &mut OpState, #[string] key: &str, #[string] value: &str) -> bool {
780 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
781 let save_dir = bridge.borrow().save_dir.clone();
782
783 if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
785 return false;
786 }
787
788 if std::fs::create_dir_all(&save_dir).is_err() {
790 return false;
791 }
792
793 let path = save_dir.join(format!("{key}.json"));
794 std::fs::write(path, value).is_ok()
795}
796
797#[deno_core::op2]
799#[string]
800pub fn op_load_file(state: &mut OpState, #[string] key: &str) -> String {
801 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
802 let save_dir = bridge.borrow().save_dir.clone();
803
804 let path = save_dir.join(format!("{key}.json"));
805 std::fs::read_to_string(path).unwrap_or_default()
806}
807
808#[deno_core::op2(fast)]
810pub fn op_delete_file(state: &mut OpState, #[string] key: &str) -> bool {
811 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
812 let save_dir = bridge.borrow().save_dir.clone();
813
814 let path = save_dir.join(format!("{key}.json"));
815 std::fs::remove_file(path).is_ok()
816}
817
818#[deno_core::op2]
820#[serde]
821pub fn op_list_save_files(state: &mut OpState) -> Vec<String> {
822 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
823 let save_dir = bridge.borrow().save_dir.clone();
824
825 let mut keys = Vec::new();
826 if let Ok(entries) = std::fs::read_dir(&save_dir) {
827 for entry in entries.flatten() {
828 let path = entry.path();
829 if path.extension().map_or(false, |ext| ext == "json") {
830 if let Some(stem) = path.file_stem() {
831 keys.push(stem.to_string_lossy().to_string());
832 }
833 }
834 }
835 }
836 keys.sort();
837 keys
838}
839
840#[deno_core::op2(fast)]
844pub fn op_create_shader(state: &mut OpState, #[string] name: &str, #[string] source: &str) -> u32 {
845 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
846 let mut b = bridge.borrow_mut();
847 let id = b.next_shader_id;
848 b.next_shader_id += 1;
849 b.shader_create_queue
850 .push((id, name.to_string(), source.to_string()));
851 id
852}
853
854#[deno_core::op2(fast)]
856pub fn op_set_shader_param(
857 state: &mut OpState,
858 shader_id: u32,
859 index: u32,
860 x: f64,
861 y: f64,
862 z: f64,
863 w: f64,
864) {
865 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
866 bridge.borrow_mut().shader_param_queue.push((
867 shader_id,
868 index,
869 [x as f32, y as f32, z as f32, w as f32],
870 ));
871}
872
873#[deno_core::op2(fast)]
877pub fn op_add_effect(state: &mut OpState, #[string] effect_type: &str) -> u32 {
878 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
879 let mut b = bridge.borrow_mut();
880 let id = b.next_effect_id;
881 b.next_effect_id += 1;
882 b.effect_create_queue
883 .push((id, effect_type.to_string()));
884 id
885}
886
887#[deno_core::op2(fast)]
889pub fn op_set_effect_param(
890 state: &mut OpState,
891 effect_id: u32,
892 index: u32,
893 x: f64,
894 y: f64,
895 z: f64,
896 w: f64,
897) {
898 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
899 bridge.borrow_mut().effect_param_queue.push((
900 effect_id,
901 index,
902 [x as f32, y as f32, z as f32, w as f32],
903 ));
904}
905
906#[deno_core::op2(fast)]
908pub fn op_remove_effect(state: &mut OpState, effect_id: u32) {
909 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
910 bridge.borrow_mut().effect_remove_queue.push(effect_id);
911}
912
913#[deno_core::op2(fast)]
915pub fn op_clear_effects(state: &mut OpState) {
916 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
917 bridge.borrow_mut().effect_clear = true;
918}
919
920#[deno_core::op2(fast)]
924pub fn op_set_camera_bounds(state: &mut OpState, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
925 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
926 bridge.borrow_mut().camera_bounds = Some(CameraBounds {
927 min_x: min_x as f32,
928 min_y: min_y as f32,
929 max_x: max_x as f32,
930 max_y: max_y as f32,
931 });
932}
933
934#[deno_core::op2(fast)]
936pub fn op_clear_camera_bounds(state: &mut OpState) {
937 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
938 bridge.borrow_mut().camera_bounds = None;
939}
940
941#[deno_core::op2]
943#[serde]
944pub fn op_get_camera_bounds(state: &mut OpState) -> Vec<f64> {
945 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
946 let b = bridge.borrow();
947 match b.camera_bounds {
948 Some(bounds) => vec![
949 bounds.min_x as f64,
950 bounds.min_y as f64,
951 bounds.max_x as f64,
952 bounds.max_y as f64,
953 ],
954 None => vec![],
955 }
956}
957
958#[deno_core::op2(fast)]
962pub fn op_enable_gi(state: &mut OpState) {
963 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
964 bridge.borrow_mut().gi_enabled = true;
965}
966
967#[deno_core::op2(fast)]
969pub fn op_disable_gi(state: &mut OpState) {
970 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
971 bridge.borrow_mut().gi_enabled = false;
972}
973
974#[deno_core::op2(fast)]
976pub fn op_set_gi_intensity(state: &mut OpState, intensity: f64) {
977 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
978 bridge.borrow_mut().gi_intensity = intensity as f32;
979}
980
981#[deno_core::op2(fast)]
984pub fn op_set_gi_quality(state: &mut OpState, probe_spacing: f64, interval: f64, cascade_count: f64) {
985 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
986 let mut b = bridge.borrow_mut();
987 if probe_spacing > 0.0 {
988 b.gi_probe_spacing = Some(probe_spacing as f32);
989 }
990 if interval > 0.0 {
991 b.gi_interval = Some(interval as f32);
992 }
993 if cascade_count > 0.0 {
994 b.gi_cascade_count = Some(cascade_count as u32);
995 }
996}
997
998#[deno_core::op2(fast)]
1000pub fn op_add_emissive(
1001 state: &mut OpState,
1002 x: f64,
1003 y: f64,
1004 w: f64,
1005 h: f64,
1006 r: f64,
1007 g: f64,
1008 b: f64,
1009 intensity: f64,
1010) {
1011 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1012 bridge.borrow_mut().emissives.push([
1013 x as f32,
1014 y as f32,
1015 w as f32,
1016 h as f32,
1017 r as f32,
1018 g as f32,
1019 b as f32,
1020 intensity as f32,
1021 ]);
1022}
1023
1024#[deno_core::op2(fast)]
1026pub fn op_clear_emissives(state: &mut OpState) {
1027 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1028 bridge.borrow_mut().emissives.clear();
1029}
1030
1031#[deno_core::op2(fast)]
1033pub fn op_add_occluder(state: &mut OpState, x: f64, y: f64, w: f64, h: f64) {
1034 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1035 bridge.borrow_mut().occluders.push([x as f32, y as f32, w as f32, h as f32]);
1036}
1037
1038#[deno_core::op2(fast)]
1040pub fn op_clear_occluders(state: &mut OpState) {
1041 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1042 bridge.borrow_mut().occluders.clear();
1043}
1044
1045#[deno_core::op2(fast)]
1047pub fn op_add_directional_light(
1048 state: &mut OpState,
1049 angle: f64,
1050 r: f64,
1051 g: f64,
1052 b: f64,
1053 intensity: f64,
1054) {
1055 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1056 bridge.borrow_mut().directional_lights.push([
1057 angle as f32,
1058 r as f32,
1059 g as f32,
1060 b as f32,
1061 intensity as f32,
1062 ]);
1063}
1064
1065#[deno_core::op2(fast)]
1067pub fn op_add_spot_light(
1068 state: &mut OpState,
1069 x: f64,
1070 y: f64,
1071 angle: f64,
1072 spread: f64,
1073 range: f64,
1074 r: f64,
1075 g: f64,
1076 b: f64,
1077 intensity: f64,
1078) {
1079 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1080 bridge.borrow_mut().spot_lights.push([
1081 x as f32,
1082 y as f32,
1083 angle as f32,
1084 spread as f32,
1085 range as f32,
1086 r as f32,
1087 g as f32,
1088 b as f32,
1089 intensity as f32,
1090 ]);
1091}
1092
1093#[deno_core::op2(fast)]
1098pub fn op_play_sound_ex(
1099 state: &mut OpState,
1100 sound_id: u32,
1101 instance_id: f64,
1102 volume: f64,
1103 looping: bool,
1104 bus: u32,
1105 pan: f64,
1106 pitch: f64,
1107 low_pass_freq: u32,
1108 reverb_mix: f64,
1109 reverb_delay_ms: u32,
1110) {
1111 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1112 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundEx {
1113 sound_id,
1114 instance_id: instance_id as u64,
1115 volume: volume as f32,
1116 looping,
1117 bus,
1118 pan: pan as f32,
1119 pitch: pitch as f32,
1120 low_pass_freq,
1121 reverb_mix: reverb_mix as f32,
1122 reverb_delay_ms,
1123 });
1124}
1125
1126#[deno_core::op2(fast)]
1129pub fn op_play_sound_spatial(
1130 state: &mut OpState,
1131 sound_id: u32,
1132 instance_id: f64,
1133 volume: f64,
1134 looping: bool,
1135 bus: u32,
1136 pitch: f64,
1137 source_x: f64,
1138 source_y: f64,
1139 listener_x: f64,
1140 listener_y: f64,
1141) {
1142 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1143 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundSpatial {
1144 sound_id,
1145 instance_id: instance_id as u64,
1146 volume: volume as f32,
1147 looping,
1148 bus,
1149 pitch: pitch as f32,
1150 source_x: source_x as f32,
1151 source_y: source_y as f32,
1152 listener_x: listener_x as f32,
1153 listener_y: listener_y as f32,
1154 });
1155}
1156
1157#[deno_core::op2(fast)]
1160pub fn op_stop_instance(state: &mut OpState, instance_id: f64) {
1161 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1162 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopInstance {
1163 instance_id: instance_id as u64,
1164 });
1165}
1166
1167#[deno_core::op2(fast)]
1170pub fn op_set_instance_volume(state: &mut OpState, instance_id: f64, volume: f64) {
1171 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1172 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstanceVolume {
1173 instance_id: instance_id as u64,
1174 volume: volume as f32,
1175 });
1176}
1177
1178#[deno_core::op2(fast)]
1181pub fn op_set_instance_pitch(state: &mut OpState, instance_id: f64, pitch: f64) {
1182 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1183 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstancePitch {
1184 instance_id: instance_id as u64,
1185 pitch: pitch as f32,
1186 });
1187}
1188
1189#[deno_core::op2(fast)]
1193pub fn op_update_spatial_positions(state: &mut OpState, #[string] data_json: &str, listener_x: f64, listener_y: f64) {
1194 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1195
1196 let mut updates = Vec::new();
1199
1200 if let Some(ids_start) = data_json.find("\"instanceIds\":[") {
1202 if let Some(xs_start) = data_json.find("\"sourceXs\":[") {
1203 if let Some(ys_start) = data_json.find("\"sourceYs\":[") {
1204 let ids_str = &data_json[ids_start + 15..];
1205 let xs_str = &data_json[xs_start + 12..];
1206 let ys_str = &data_json[ys_start + 12..];
1207
1208 let ids_end = ids_str.find(']').unwrap_or(0);
1209 let xs_end = xs_str.find(']').unwrap_or(0);
1210 let ys_end = ys_str.find(']').unwrap_or(0);
1211
1212 let ids: Vec<u64> = ids_str[..ids_end]
1213 .split(',')
1214 .filter_map(|s| s.trim().parse().ok())
1215 .collect();
1216 let xs: Vec<f32> = xs_str[..xs_end]
1217 .split(',')
1218 .filter_map(|s| s.trim().parse().ok())
1219 .collect();
1220 let ys: Vec<f32> = ys_str[..ys_end]
1221 .split(',')
1222 .filter_map(|s| s.trim().parse().ok())
1223 .collect();
1224
1225 for i in 0..ids.len().min(xs.len()).min(ys.len()) {
1226 updates.push((ids[i], xs[i], ys[i]));
1227 }
1228 }
1229 }
1230 }
1231
1232 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::UpdateSpatialPositions {
1233 updates,
1234 listener_x: listener_x as f32,
1235 listener_y: listener_y as f32,
1236 });
1237}
1238
1239#[deno_core::op2(fast)]
1242pub fn op_set_bus_volume(state: &mut OpState, bus: u32, volume: f64) {
1243 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1244 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetBusVolume {
1245 bus,
1246 volume: volume as f32,
1247 });
1248}
1249
1250#[deno_core::op2]
1255#[string]
1256pub fn op_create_msdf_builtin_font(state: &mut OpState) -> String {
1257 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1258 let mut b = bridge.borrow_mut();
1259
1260 let key = "__msdf_builtin__".to_string();
1262 if let Some(&tex_id) = b.texture_path_to_id.get(&key) {
1263 let font_id_key = format!("__msdf_font_{tex_id}__");
1266 if let Some(&font_id) = b.texture_path_to_id.get(&font_id_key) {
1267 let pool = &b.msdf_shader_pool;
1268 let shader_id = pool.first().copied().unwrap_or(0);
1269 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1270 return format!(
1271 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1272 font_id, tex_id, shader_id, pool_json.join(",")
1273 );
1274 }
1275 }
1276
1277 let tex_id = b.next_texture_id;
1279 b.next_texture_id += 1;
1280 b.texture_path_to_id.insert(key, tex_id);
1281
1282 let (_pixels, _width, _height, mut font) =
1284 crate::renderer::msdf::generate_builtin_msdf_font();
1285 font.texture_id = tex_id;
1286
1287 let font_id = b.msdf_fonts.register(font);
1289 b.texture_path_to_id
1290 .insert(format!("__msdf_font_{tex_id}__"), font_id);
1291
1292 b.msdf_builtin_queue.push((font_id, tex_id));
1295
1296 let pool = ensure_msdf_shader_pool(&mut b);
1298 let shader_id = pool.first().copied().unwrap_or(0);
1299 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1300
1301 format!(
1302 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1303 font_id, tex_id, shader_id, pool_json.join(",")
1304 )
1305}
1306
1307#[deno_core::op2]
1310#[string]
1311pub fn op_get_msdf_glyphs(
1312 state: &mut OpState,
1313 font_id: u32,
1314 #[string] text: &str,
1315) -> String {
1316 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1317 let b = bridge.borrow();
1318
1319 let font = match b.msdf_fonts.get(font_id) {
1320 Some(f) => f,
1321 None => return "[]".to_string(),
1322 };
1323
1324 let mut entries = Vec::new();
1325 for ch in text.chars() {
1326 if let Some(glyph) = font.get_glyph(ch) {
1327 entries.push(format!(
1328 "{{\"char\":{},\"uv\":[{},{},{},{}],\"advance\":{},\"width\":{},\"height\":{},\"offsetX\":{},\"offsetY\":{}}}",
1329 ch as u32,
1330 glyph.uv_x, glyph.uv_y, glyph.uv_w, glyph.uv_h,
1331 glyph.advance, glyph.width, glyph.height,
1332 glyph.offset_x, glyph.offset_y,
1333 ));
1334 }
1335 }
1336
1337 format!("[{}]", entries.join(","))
1338}
1339
1340#[deno_core::op2]
1342#[string]
1343pub fn op_get_msdf_font_info(state: &mut OpState, font_id: u32) -> String {
1344 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1345 let b = bridge.borrow();
1346
1347 match b.msdf_fonts.get(font_id) {
1348 Some(font) => format!(
1349 "{{\"fontSize\":{},\"lineHeight\":{},\"distanceRange\":{},\"textureId\":{}}}",
1350 font.font_size, font.line_height, font.distance_range, font.texture_id,
1351 ),
1352 None => "null".to_string(),
1353 }
1354}
1355
1356#[deno_core::op2]
1359#[string]
1360pub fn op_load_msdf_font(
1361 state: &mut OpState,
1362 #[string] atlas_path: &str,
1363 #[string] metrics_json_or_path: &str,
1364) -> String {
1365 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1366 let mut b = bridge.borrow_mut();
1367
1368 let resolved = if std::path::Path::new(atlas_path).is_absolute() {
1370 atlas_path.to_string()
1371 } else {
1372 b.base_dir.join(atlas_path).to_string_lossy().to_string()
1373 };
1374
1375 let tex_id = if let Some(&id) = b.texture_path_to_id.get(&resolved) {
1377 id
1378 } else {
1379 let id = b.next_texture_id;
1380 b.next_texture_id += 1;
1381 b.texture_path_to_id.insert(resolved.clone(), id);
1382 b.msdf_texture_load_queue.push((resolved, id));
1383 id
1384 };
1385
1386 let metrics_json: String = if metrics_json_or_path.trim_start().starts_with('{') {
1389 metrics_json_or_path.to_string()
1390 } else {
1391 let json_path = if std::path::Path::new(metrics_json_or_path).is_absolute() {
1393 metrics_json_or_path.to_string()
1394 } else {
1395 b.base_dir
1396 .join(metrics_json_or_path)
1397 .to_string_lossy()
1398 .to_string()
1399 };
1400 match std::fs::read_to_string(&json_path) {
1401 Ok(content) => content,
1402 Err(e) => {
1403 return format!("{{\"error\":\"Failed to read metrics file {}: {}\"}}", json_path, e);
1404 }
1405 }
1406 };
1407
1408 let font = match crate::renderer::msdf::parse_msdf_metrics(&metrics_json, tex_id) {
1410 Ok(f) => f,
1411 Err(e) => {
1412 return format!("{{\"error\":\"{}\"}}", e);
1413 }
1414 };
1415
1416 let font_id = b.msdf_fonts.register(font);
1417 let pool = ensure_msdf_shader_pool(&mut b);
1418 let shader_id = pool.first().copied().unwrap_or(0);
1419 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1420
1421 format!(
1422 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1423 font_id, tex_id, shader_id, pool_json.join(",")
1424 )
1425}
1426
1427const MSDF_SHADER_POOL_SIZE: usize = 8;
1429
1430fn ensure_msdf_shader_pool(b: &mut RenderBridgeState) -> Vec<u32> {
1432 if !b.msdf_shader_pool.is_empty() {
1433 return b.msdf_shader_pool.clone();
1434 }
1435
1436 let source = crate::renderer::msdf::MSDF_FRAGMENT_SOURCE.to_string();
1437 let mut pool = Vec::with_capacity(MSDF_SHADER_POOL_SIZE);
1438
1439 for _ in 0..MSDF_SHADER_POOL_SIZE {
1440 let id = b.next_shader_id;
1441 b.next_shader_id += 1;
1442 b.msdf_shader_queue.push((id, source.clone()));
1443 pool.push(id);
1444 }
1445
1446 b.msdf_shader_pool = pool.clone();
1447 pool
1448}
1449
1450#[deno_core::op2(fast)]
1454pub fn op_get_gamepad_count(state: &mut OpState) -> u32 {
1455 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1456 bridge.borrow().gamepad_count
1457}
1458
1459#[deno_core::op2]
1461#[string]
1462pub fn op_get_gamepad_name(state: &mut OpState) -> String {
1463 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1464 bridge.borrow().gamepad_name.clone()
1465}
1466
1467#[deno_core::op2(fast)]
1470pub fn op_is_gamepad_button_down(state: &mut OpState, #[string] button: &str) -> bool {
1471 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1472 bridge.borrow().gamepad_buttons_down.contains(button)
1473}
1474
1475#[deno_core::op2(fast)]
1477pub fn op_is_gamepad_button_pressed(state: &mut OpState, #[string] button: &str) -> bool {
1478 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1479 bridge.borrow().gamepad_buttons_pressed.contains(button)
1480}
1481
1482#[deno_core::op2(fast)]
1485pub fn op_get_gamepad_axis(state: &mut OpState, #[string] axis: &str) -> f64 {
1486 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1487 bridge.borrow().gamepad_axes.get(axis).copied().unwrap_or(0.0) as f64
1488}
1489
1490#[deno_core::op2(fast)]
1494pub fn op_get_touch_count(state: &mut OpState) -> u32 {
1495 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1496 bridge.borrow().touch_count
1497}
1498
1499#[deno_core::op2]
1501#[serde]
1502pub fn op_get_touch_position(state: &mut OpState, index: u32) -> Vec<f64> {
1503 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1504 let b = bridge.borrow();
1505 if let Some(&(_, x, y)) = b.touch_points.get(index as usize) {
1506 vec![x as f64, y as f64]
1507 } else {
1508 vec![]
1509 }
1510}
1511
1512#[deno_core::op2(fast)]
1514pub fn op_is_touch_active(state: &mut OpState) -> bool {
1515 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1516 bridge.borrow().touch_count > 0
1517}
1518
1519deno_core::extension!(
1520 render_ext,
1521 ops = [
1522 op_draw_sprite,
1523 op_clear_sprites,
1524 op_submit_sprite_batch,
1525 op_set_camera,
1526 op_get_camera,
1527 op_load_texture,
1528 op_load_texture_linear,
1529 op_upload_rgba_texture,
1530 op_is_key_down,
1531 op_is_key_pressed,
1532 op_get_mouse_position,
1533 op_is_mouse_button_down,
1534 op_is_mouse_button_pressed,
1535 op_get_delta_time,
1536 op_create_solid_texture,
1537 op_create_tilemap,
1538 op_set_tile,
1539 op_get_tile,
1540 op_draw_tilemap,
1541 op_set_ambient_light,
1542 op_add_point_light,
1543 op_clear_lights,
1544 op_load_sound,
1545 op_play_sound,
1546 op_stop_sound,
1547 op_stop_all_sounds,
1548 op_set_master_volume,
1549 op_play_sound_ex,
1550 op_play_sound_spatial,
1551 op_stop_instance,
1552 op_set_instance_volume,
1553 op_set_instance_pitch,
1554 op_update_spatial_positions,
1555 op_set_bus_volume,
1556 op_create_font_texture,
1557 op_get_viewport_size,
1558 op_get_scale_factor,
1559 op_set_background_color,
1560 op_save_file,
1561 op_load_file,
1562 op_delete_file,
1563 op_list_save_files,
1564 op_create_shader,
1565 op_set_shader_param,
1566 op_add_effect,
1567 op_set_effect_param,
1568 op_remove_effect,
1569 op_clear_effects,
1570 op_set_camera_bounds,
1571 op_clear_camera_bounds,
1572 op_get_camera_bounds,
1573 op_enable_gi,
1574 op_disable_gi,
1575 op_set_gi_intensity,
1576 op_set_gi_quality,
1577 op_add_emissive,
1578 op_clear_emissives,
1579 op_add_occluder,
1580 op_clear_occluders,
1581 op_add_directional_light,
1582 op_add_spot_light,
1583 op_create_msdf_builtin_font,
1584 op_get_msdf_glyphs,
1585 op_get_msdf_font_info,
1586 op_load_msdf_font,
1587 op_get_gamepad_count,
1588 op_get_gamepad_name,
1589 op_is_gamepad_button_down,
1590 op_is_gamepad_button_pressed,
1591 op_get_gamepad_axis,
1592 op_get_touch_count,
1593 op_get_touch_position,
1594 op_is_touch_active,
1595 ],
1596);