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 keys_down: std::collections::HashSet<String>,
71 pub keys_pressed: std::collections::HashSet<String>,
72 pub mouse_x: f32,
73 pub mouse_y: f32,
74 pub mouse_buttons_down: std::collections::HashSet<u8>,
75 pub mouse_buttons_pressed: std::collections::HashSet<u8>,
76 pub gamepad_buttons_down: std::collections::HashSet<String>,
78 pub gamepad_buttons_pressed: std::collections::HashSet<String>,
80 pub gamepad_axes: std::collections::HashMap<String, f32>,
82 pub gamepad_count: u32,
84 pub gamepad_name: String,
86 pub touch_points: Vec<(u64, f32, f32)>,
88 pub touch_count: u32,
90 pub texture_load_queue: Vec<(String, u32)>,
92 pub base_dir: PathBuf,
94 pub next_texture_id: u32,
96 pub texture_path_to_id: std::collections::HashMap<String, u32>,
98 pub tilemaps: TilemapStore,
100 pub ambient_light: [f32; 3],
102 pub point_lights: Vec<PointLight>,
104 pub audio_commands: Vec<BridgeAudioCommand>,
106 pub next_sound_id: u32,
108 pub sound_path_to_id: std::collections::HashMap<String, u32>,
110 pub font_texture_queue: Vec<u32>,
112 pub viewport_width: f32,
114 pub viewport_height: f32,
115 pub scale_factor: f32,
117 pub clear_color: [f32; 4],
119 pub save_dir: PathBuf,
121 pub shader_create_queue: Vec<(u32, String, String)>,
123 pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
125 pub next_shader_id: u32,
127 pub effect_create_queue: Vec<(u32, String)>,
129 pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
131 pub effect_remove_queue: Vec<u32>,
133 pub effect_clear: bool,
135 pub next_effect_id: u32,
137 pub camera_bounds: Option<CameraBounds>,
139 pub gi_enabled: bool,
141 pub gi_intensity: f32,
143 pub gi_probe_spacing: Option<f32>,
145 pub gi_interval: Option<f32>,
147 pub gi_cascade_count: Option<u32>,
149 pub emissives: Vec<[f32; 8]>,
151 pub occluders: Vec<[f32; 4]>,
153 pub directional_lights: Vec<[f32; 5]>,
155 pub spot_lights: Vec<[f32; 9]>,
157 pub msdf_fonts: MsdfFontStore,
159 pub msdf_builtin_queue: Vec<(u32, u32)>,
161 pub msdf_shader_queue: Vec<(u32, String)>,
163 pub msdf_shader_pool: Vec<u32>,
165 pub msdf_texture_load_queue: Vec<(String, u32)>,
167}
168
169impl RenderBridgeState {
170 pub fn new(base_dir: PathBuf) -> Self {
171 let save_dir = base_dir.join(".arcane").join("saves");
172 Self {
173 sprite_commands: Vec::new(),
174 camera_x: 0.0,
175 camera_y: 0.0,
176 camera_zoom: 1.0,
177 camera_dirty: false,
178 delta_time: 0.0,
179 keys_down: std::collections::HashSet::new(),
180 keys_pressed: std::collections::HashSet::new(),
181 mouse_x: 0.0,
182 mouse_y: 0.0,
183 mouse_buttons_down: std::collections::HashSet::new(),
184 mouse_buttons_pressed: std::collections::HashSet::new(),
185 gamepad_buttons_down: std::collections::HashSet::new(),
186 gamepad_buttons_pressed: std::collections::HashSet::new(),
187 gamepad_axes: std::collections::HashMap::new(),
188 gamepad_count: 0,
189 gamepad_name: String::new(),
190 touch_points: Vec::new(),
191 touch_count: 0,
192 texture_load_queue: Vec::new(),
193 base_dir,
194 next_texture_id: 1,
195 texture_path_to_id: std::collections::HashMap::new(),
196 tilemaps: TilemapStore::new(),
197 ambient_light: [1.0, 1.0, 1.0],
198 point_lights: Vec::new(),
199 audio_commands: Vec::new(),
200 next_sound_id: 1,
201 sound_path_to_id: std::collections::HashMap::new(),
202 font_texture_queue: Vec::new(),
203 viewport_width: 800.0,
204 viewport_height: 600.0,
205 scale_factor: 1.0,
206 clear_color: [0.1, 0.1, 0.15, 1.0],
207 save_dir,
208 shader_create_queue: Vec::new(),
209 shader_param_queue: Vec::new(),
210 next_shader_id: 1,
211 effect_create_queue: Vec::new(),
212 effect_param_queue: Vec::new(),
213 effect_remove_queue: Vec::new(),
214 effect_clear: false,
215 next_effect_id: 1,
216 camera_bounds: None,
217 gi_enabled: false,
218 gi_intensity: 1.0,
219 gi_probe_spacing: None,
220 gi_interval: None,
221 gi_cascade_count: None,
222 emissives: Vec::new(),
223 occluders: Vec::new(),
224 directional_lights: Vec::new(),
225 spot_lights: Vec::new(),
226 msdf_fonts: MsdfFontStore::new(),
227 msdf_builtin_queue: Vec::new(),
228 msdf_shader_queue: Vec::new(),
229 msdf_shader_pool: Vec::new(),
230 msdf_texture_load_queue: Vec::new(),
231 }
232 }
233}
234
235#[deno_core::op2(fast)]
238pub fn op_draw_sprite(
239 state: &mut OpState,
240 texture_id: u32,
241 x: f64,
242 y: f64,
243 w: f64,
244 h: f64,
245 layer: i32,
246 uv_x: f64,
247 uv_y: f64,
248 uv_w: f64,
249 uv_h: f64,
250 tint_r: f64,
251 tint_g: f64,
252 tint_b: f64,
253 tint_a: f64,
254 rotation: f64,
255 origin_x: f64,
256 origin_y: f64,
257 flip_x: f64,
258 flip_y: f64,
259 opacity: f64,
260 blend_mode: f64,
261 shader_id: f64,
262) {
263 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
264 bridge.borrow_mut().sprite_commands.push(SpriteCommand {
265 texture_id,
266 x: x as f32,
267 y: y as f32,
268 w: w as f32,
269 h: h as f32,
270 layer,
271 uv_x: uv_x as f32,
272 uv_y: uv_y as f32,
273 uv_w: uv_w as f32,
274 uv_h: uv_h as f32,
275 tint_r: tint_r as f32,
276 tint_g: tint_g as f32,
277 tint_b: tint_b as f32,
278 tint_a: tint_a as f32,
279 rotation: rotation as f32,
280 origin_x: origin_x as f32,
281 origin_y: origin_y as f32,
282 flip_x: flip_x != 0.0,
283 flip_y: flip_y != 0.0,
284 opacity: opacity as f32,
285 blend_mode: (blend_mode as u8).min(3),
286 shader_id: shader_id as u32,
287 });
288}
289
290#[deno_core::op2(fast)]
292pub fn op_clear_sprites(state: &mut OpState) {
293 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
294 bridge.borrow_mut().sprite_commands.clear();
295}
296
297#[deno_core::op2(fast)]
300pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
301 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
302 let mut b = bridge.borrow_mut();
303 b.camera_x = x as f32;
304 b.camera_y = y as f32;
305 b.camera_zoom = zoom as f32;
306 b.camera_dirty = true;
307}
308
309#[deno_core::op2]
311#[serde]
312pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
313 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
314 let b = bridge.borrow();
315 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
316}
317
318#[deno_core::op2(fast)]
321pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
322 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
323 let mut b = bridge.borrow_mut();
324
325 let resolved = if std::path::Path::new(path).is_absolute() {
327 path.to_string()
328 } else {
329 b.base_dir.join(path).to_string_lossy().to_string()
330 };
331
332 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
334 return id;
335 }
336
337 let id = b.next_texture_id;
338 b.next_texture_id += 1;
339 b.texture_path_to_id.insert(resolved.clone(), id);
340 b.texture_load_queue.push((resolved, id));
341 id
342}
343
344#[deno_core::op2(fast)]
346pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
347 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
348 bridge.borrow().keys_down.contains(key)
349}
350
351#[deno_core::op2(fast)]
353pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
354 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
355 bridge.borrow().keys_pressed.contains(key)
356}
357
358#[deno_core::op2]
360#[serde]
361pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
362 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
363 let b = bridge.borrow();
364 vec![b.mouse_x as f64, b.mouse_y as f64]
365}
366
367#[deno_core::op2(fast)]
370pub fn op_is_mouse_button_down(state: &mut OpState, button: u8) -> bool {
371 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
372 bridge.borrow().mouse_buttons_down.contains(&button)
373}
374
375#[deno_core::op2(fast)]
378pub fn op_is_mouse_button_pressed(state: &mut OpState, button: u8) -> bool {
379 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
380 bridge.borrow().mouse_buttons_pressed.contains(&button)
381}
382
383#[deno_core::op2(fast)]
385pub fn op_get_delta_time(state: &mut OpState) -> f64 {
386 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
387 bridge.borrow().delta_time
388}
389
390#[deno_core::op2(fast)]
393pub fn op_create_solid_texture(
394 state: &mut OpState,
395 #[string] name: &str,
396 r: u32,
397 g: u32,
398 b: u32,
399 a: u32,
400) -> u32 {
401 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
402 let mut br = bridge.borrow_mut();
403
404 let key = format!("__solid__{name}");
405 if let Some(&id) = br.texture_path_to_id.get(&key) {
406 return id;
407 }
408
409 let id = br.next_texture_id;
410 br.next_texture_id += 1;
411 br.texture_path_to_id.insert(key.clone(), id);
412 br.texture_load_queue
414 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
415 id
416}
417
418#[deno_core::op2(fast)]
420pub fn op_create_tilemap(
421 state: &mut OpState,
422 texture_id: u32,
423 width: u32,
424 height: u32,
425 tile_size: f64,
426 atlas_columns: u32,
427 atlas_rows: u32,
428) -> u32 {
429 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
430 bridge
431 .borrow_mut()
432 .tilemaps
433 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
434}
435
436#[deno_core::op2(fast)]
438pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
439 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
440 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
441 tm.set_tile(gx, gy, tile_id as u16);
442 }
443}
444
445#[deno_core::op2(fast)]
447pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
448 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
449 bridge
450 .borrow()
451 .tilemaps
452 .get(tilemap_id)
453 .map(|tm| tm.get_tile(gx, gy) as u32)
454 .unwrap_or(0)
455}
456
457#[deno_core::op2(fast)]
460pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
461 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
462 let mut b = bridge.borrow_mut();
463 let cam_x = b.camera_x;
464 let cam_y = b.camera_y;
465 let cam_zoom = b.camera_zoom;
466 let vp_w = 800.0;
468 let vp_h = 600.0;
469
470 if let Some(tm) = b.tilemaps.get(tilemap_id) {
471 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
472 b.sprite_commands.extend(cmds);
473 }
474}
475
476#[deno_core::op2(fast)]
481pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
482 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
483 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
484}
485
486#[deno_core::op2(fast)]
489pub fn op_add_point_light(
490 state: &mut OpState,
491 x: f64,
492 y: f64,
493 radius: f64,
494 r: f64,
495 g: f64,
496 b: f64,
497 intensity: f64,
498) {
499 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
500 bridge.borrow_mut().point_lights.push(PointLight {
501 x: x as f32,
502 y: y as f32,
503 radius: radius as f32,
504 r: r as f32,
505 g: g as f32,
506 b: b as f32,
507 intensity: intensity as f32,
508 });
509}
510
511#[deno_core::op2(fast)]
513pub fn op_clear_lights(state: &mut OpState) {
514 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
515 bridge.borrow_mut().point_lights.clear();
516}
517
518#[deno_core::op2(fast)]
522pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
523 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
524 let mut b = bridge.borrow_mut();
525
526 let resolved = if std::path::Path::new(path).is_absolute() {
527 path.to_string()
528 } else {
529 b.base_dir.join(path).to_string_lossy().to_string()
530 };
531
532 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
533 return id;
534 }
535
536 let id = b.next_sound_id;
537 b.next_sound_id += 1;
538 b.sound_path_to_id.insert(resolved.clone(), id);
539 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
540 id
541}
542
543#[deno_core::op2(fast)]
546pub fn op_play_sound(state: &mut OpState, id: u32, volume: f64, looping: bool) {
547 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
548 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySound { id, volume: volume as f32, looping });
549}
550
551#[deno_core::op2(fast)]
553pub fn op_stop_sound(state: &mut OpState, id: u32) {
554 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
555 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopSound { id });
556}
557
558#[deno_core::op2(fast)]
560pub fn op_stop_all_sounds(state: &mut OpState) {
561 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
562 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
563}
564
565#[deno_core::op2(fast)]
568pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
569 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
570 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
571}
572
573#[deno_core::op2(fast)]
577pub fn op_create_font_texture(state: &mut OpState) -> u32 {
578 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
579 let mut b = bridge.borrow_mut();
580
581 let key = "__builtin_font__".to_string();
582 if let Some(&id) = b.texture_path_to_id.get(&key) {
583 return id;
584 }
585
586 let id = b.next_texture_id;
587 b.next_texture_id += 1;
588 b.texture_path_to_id.insert(key, id);
589 b.font_texture_queue.push(id);
590 id
591}
592
593#[deno_core::op2]
597#[serde]
598pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
599 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
600 let b = bridge.borrow();
601 vec![b.viewport_width as f64, b.viewport_height as f64]
602}
603
604#[deno_core::op2(fast)]
606pub fn op_get_scale_factor(state: &mut OpState) -> f64 {
607 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
608 bridge.borrow().scale_factor as f64
609}
610
611#[deno_core::op2(fast)]
613pub fn op_set_background_color(state: &mut OpState, r: f64, g: f64, b: f64) {
614 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
615 let mut br = bridge.borrow_mut();
616 br.clear_color = [r as f32, g as f32, b as f32, 1.0];
617}
618
619#[deno_core::op2(fast)]
623pub fn op_save_file(state: &mut OpState, #[string] key: &str, #[string] value: &str) -> bool {
624 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
625 let save_dir = bridge.borrow().save_dir.clone();
626
627 if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
629 return false;
630 }
631
632 if std::fs::create_dir_all(&save_dir).is_err() {
634 return false;
635 }
636
637 let path = save_dir.join(format!("{key}.json"));
638 std::fs::write(path, value).is_ok()
639}
640
641#[deno_core::op2]
643#[string]
644pub fn op_load_file(state: &mut OpState, #[string] key: &str) -> String {
645 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
646 let save_dir = bridge.borrow().save_dir.clone();
647
648 let path = save_dir.join(format!("{key}.json"));
649 std::fs::read_to_string(path).unwrap_or_default()
650}
651
652#[deno_core::op2(fast)]
654pub fn op_delete_file(state: &mut OpState, #[string] key: &str) -> bool {
655 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
656 let save_dir = bridge.borrow().save_dir.clone();
657
658 let path = save_dir.join(format!("{key}.json"));
659 std::fs::remove_file(path).is_ok()
660}
661
662#[deno_core::op2]
664#[serde]
665pub fn op_list_save_files(state: &mut OpState) -> Vec<String> {
666 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
667 let save_dir = bridge.borrow().save_dir.clone();
668
669 let mut keys = Vec::new();
670 if let Ok(entries) = std::fs::read_dir(&save_dir) {
671 for entry in entries.flatten() {
672 let path = entry.path();
673 if path.extension().map_or(false, |ext| ext == "json") {
674 if let Some(stem) = path.file_stem() {
675 keys.push(stem.to_string_lossy().to_string());
676 }
677 }
678 }
679 }
680 keys.sort();
681 keys
682}
683
684#[deno_core::op2(fast)]
688pub fn op_create_shader(state: &mut OpState, #[string] name: &str, #[string] source: &str) -> u32 {
689 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
690 let mut b = bridge.borrow_mut();
691 let id = b.next_shader_id;
692 b.next_shader_id += 1;
693 b.shader_create_queue
694 .push((id, name.to_string(), source.to_string()));
695 id
696}
697
698#[deno_core::op2(fast)]
700pub fn op_set_shader_param(
701 state: &mut OpState,
702 shader_id: u32,
703 index: u32,
704 x: f64,
705 y: f64,
706 z: f64,
707 w: f64,
708) {
709 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
710 bridge.borrow_mut().shader_param_queue.push((
711 shader_id,
712 index,
713 [x as f32, y as f32, z as f32, w as f32],
714 ));
715}
716
717#[deno_core::op2(fast)]
721pub fn op_add_effect(state: &mut OpState, #[string] effect_type: &str) -> u32 {
722 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
723 let mut b = bridge.borrow_mut();
724 let id = b.next_effect_id;
725 b.next_effect_id += 1;
726 b.effect_create_queue
727 .push((id, effect_type.to_string()));
728 id
729}
730
731#[deno_core::op2(fast)]
733pub fn op_set_effect_param(
734 state: &mut OpState,
735 effect_id: u32,
736 index: u32,
737 x: f64,
738 y: f64,
739 z: f64,
740 w: f64,
741) {
742 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
743 bridge.borrow_mut().effect_param_queue.push((
744 effect_id,
745 index,
746 [x as f32, y as f32, z as f32, w as f32],
747 ));
748}
749
750#[deno_core::op2(fast)]
752pub fn op_remove_effect(state: &mut OpState, effect_id: u32) {
753 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
754 bridge.borrow_mut().effect_remove_queue.push(effect_id);
755}
756
757#[deno_core::op2(fast)]
759pub fn op_clear_effects(state: &mut OpState) {
760 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
761 bridge.borrow_mut().effect_clear = true;
762}
763
764#[deno_core::op2(fast)]
768pub fn op_set_camera_bounds(state: &mut OpState, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
769 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
770 bridge.borrow_mut().camera_bounds = Some(CameraBounds {
771 min_x: min_x as f32,
772 min_y: min_y as f32,
773 max_x: max_x as f32,
774 max_y: max_y as f32,
775 });
776}
777
778#[deno_core::op2(fast)]
780pub fn op_clear_camera_bounds(state: &mut OpState) {
781 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
782 bridge.borrow_mut().camera_bounds = None;
783}
784
785#[deno_core::op2]
787#[serde]
788pub fn op_get_camera_bounds(state: &mut OpState) -> Vec<f64> {
789 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
790 let b = bridge.borrow();
791 match b.camera_bounds {
792 Some(bounds) => vec![
793 bounds.min_x as f64,
794 bounds.min_y as f64,
795 bounds.max_x as f64,
796 bounds.max_y as f64,
797 ],
798 None => vec![],
799 }
800}
801
802#[deno_core::op2(fast)]
806pub fn op_enable_gi(state: &mut OpState) {
807 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
808 bridge.borrow_mut().gi_enabled = true;
809}
810
811#[deno_core::op2(fast)]
813pub fn op_disable_gi(state: &mut OpState) {
814 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
815 bridge.borrow_mut().gi_enabled = false;
816}
817
818#[deno_core::op2(fast)]
820pub fn op_set_gi_intensity(state: &mut OpState, intensity: f64) {
821 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
822 bridge.borrow_mut().gi_intensity = intensity as f32;
823}
824
825#[deno_core::op2(fast)]
828pub fn op_set_gi_quality(state: &mut OpState, probe_spacing: f64, interval: f64, cascade_count: f64) {
829 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
830 let mut b = bridge.borrow_mut();
831 if probe_spacing > 0.0 {
832 b.gi_probe_spacing = Some(probe_spacing as f32);
833 }
834 if interval > 0.0 {
835 b.gi_interval = Some(interval as f32);
836 }
837 if cascade_count > 0.0 {
838 b.gi_cascade_count = Some(cascade_count as u32);
839 }
840}
841
842#[deno_core::op2(fast)]
844pub fn op_add_emissive(
845 state: &mut OpState,
846 x: f64,
847 y: f64,
848 w: f64,
849 h: f64,
850 r: f64,
851 g: f64,
852 b: f64,
853 intensity: f64,
854) {
855 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
856 bridge.borrow_mut().emissives.push([
857 x as f32,
858 y as f32,
859 w as f32,
860 h as f32,
861 r as f32,
862 g as f32,
863 b as f32,
864 intensity as f32,
865 ]);
866}
867
868#[deno_core::op2(fast)]
870pub fn op_clear_emissives(state: &mut OpState) {
871 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
872 bridge.borrow_mut().emissives.clear();
873}
874
875#[deno_core::op2(fast)]
877pub fn op_add_occluder(state: &mut OpState, x: f64, y: f64, w: f64, h: f64) {
878 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
879 bridge.borrow_mut().occluders.push([x as f32, y as f32, w as f32, h as f32]);
880}
881
882#[deno_core::op2(fast)]
884pub fn op_clear_occluders(state: &mut OpState) {
885 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
886 bridge.borrow_mut().occluders.clear();
887}
888
889#[deno_core::op2(fast)]
891pub fn op_add_directional_light(
892 state: &mut OpState,
893 angle: f64,
894 r: f64,
895 g: f64,
896 b: f64,
897 intensity: f64,
898) {
899 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
900 bridge.borrow_mut().directional_lights.push([
901 angle as f32,
902 r as f32,
903 g as f32,
904 b as f32,
905 intensity as f32,
906 ]);
907}
908
909#[deno_core::op2(fast)]
911pub fn op_add_spot_light(
912 state: &mut OpState,
913 x: f64,
914 y: f64,
915 angle: f64,
916 spread: f64,
917 range: f64,
918 r: f64,
919 g: f64,
920 b: f64,
921 intensity: f64,
922) {
923 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
924 bridge.borrow_mut().spot_lights.push([
925 x as f32,
926 y as f32,
927 angle as f32,
928 spread as f32,
929 range as f32,
930 r as f32,
931 g as f32,
932 b as f32,
933 intensity as f32,
934 ]);
935}
936
937#[deno_core::op2(fast)]
942pub fn op_play_sound_ex(
943 state: &mut OpState,
944 sound_id: u32,
945 instance_id: f64,
946 volume: f64,
947 looping: bool,
948 bus: u32,
949 pan: f64,
950 pitch: f64,
951 low_pass_freq: u32,
952 reverb_mix: f64,
953 reverb_delay_ms: u32,
954) {
955 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
956 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundEx {
957 sound_id,
958 instance_id: instance_id as u64,
959 volume: volume as f32,
960 looping,
961 bus,
962 pan: pan as f32,
963 pitch: pitch as f32,
964 low_pass_freq,
965 reverb_mix: reverb_mix as f32,
966 reverb_delay_ms,
967 });
968}
969
970#[deno_core::op2(fast)]
973pub fn op_play_sound_spatial(
974 state: &mut OpState,
975 sound_id: u32,
976 instance_id: f64,
977 volume: f64,
978 looping: bool,
979 bus: u32,
980 pitch: f64,
981 source_x: f64,
982 source_y: f64,
983 listener_x: f64,
984 listener_y: f64,
985) {
986 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
987 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundSpatial {
988 sound_id,
989 instance_id: instance_id as u64,
990 volume: volume as f32,
991 looping,
992 bus,
993 pitch: pitch as f32,
994 source_x: source_x as f32,
995 source_y: source_y as f32,
996 listener_x: listener_x as f32,
997 listener_y: listener_y as f32,
998 });
999}
1000
1001#[deno_core::op2(fast)]
1004pub fn op_stop_instance(state: &mut OpState, instance_id: f64) {
1005 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1006 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopInstance {
1007 instance_id: instance_id as u64,
1008 });
1009}
1010
1011#[deno_core::op2(fast)]
1014pub fn op_set_instance_volume(state: &mut OpState, instance_id: f64, volume: f64) {
1015 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1016 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstanceVolume {
1017 instance_id: instance_id as u64,
1018 volume: volume as f32,
1019 });
1020}
1021
1022#[deno_core::op2(fast)]
1025pub fn op_set_instance_pitch(state: &mut OpState, instance_id: f64, pitch: f64) {
1026 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1027 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstancePitch {
1028 instance_id: instance_id as u64,
1029 pitch: pitch as f32,
1030 });
1031}
1032
1033#[deno_core::op2(fast)]
1037pub fn op_update_spatial_positions(state: &mut OpState, #[string] data_json: &str, listener_x: f64, listener_y: f64) {
1038 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1039
1040 let mut updates = Vec::new();
1043
1044 if let Some(ids_start) = data_json.find("\"instanceIds\":[") {
1046 if let Some(xs_start) = data_json.find("\"sourceXs\":[") {
1047 if let Some(ys_start) = data_json.find("\"sourceYs\":[") {
1048 let ids_str = &data_json[ids_start + 15..];
1049 let xs_str = &data_json[xs_start + 12..];
1050 let ys_str = &data_json[ys_start + 12..];
1051
1052 let ids_end = ids_str.find(']').unwrap_or(0);
1053 let xs_end = xs_str.find(']').unwrap_or(0);
1054 let ys_end = ys_str.find(']').unwrap_or(0);
1055
1056 let ids: Vec<u64> = ids_str[..ids_end]
1057 .split(',')
1058 .filter_map(|s| s.trim().parse().ok())
1059 .collect();
1060 let xs: Vec<f32> = xs_str[..xs_end]
1061 .split(',')
1062 .filter_map(|s| s.trim().parse().ok())
1063 .collect();
1064 let ys: Vec<f32> = ys_str[..ys_end]
1065 .split(',')
1066 .filter_map(|s| s.trim().parse().ok())
1067 .collect();
1068
1069 for i in 0..ids.len().min(xs.len()).min(ys.len()) {
1070 updates.push((ids[i], xs[i], ys[i]));
1071 }
1072 }
1073 }
1074 }
1075
1076 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::UpdateSpatialPositions {
1077 updates,
1078 listener_x: listener_x as f32,
1079 listener_y: listener_y as f32,
1080 });
1081}
1082
1083#[deno_core::op2(fast)]
1086pub fn op_set_bus_volume(state: &mut OpState, bus: u32, volume: f64) {
1087 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1088 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetBusVolume {
1089 bus,
1090 volume: volume as f32,
1091 });
1092}
1093
1094#[deno_core::op2]
1099#[string]
1100pub fn op_create_msdf_builtin_font(state: &mut OpState) -> String {
1101 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1102 let mut b = bridge.borrow_mut();
1103
1104 let key = "__msdf_builtin__".to_string();
1106 if let Some(&tex_id) = b.texture_path_to_id.get(&key) {
1107 let font_id_key = format!("__msdf_font_{tex_id}__");
1110 if let Some(&font_id) = b.texture_path_to_id.get(&font_id_key) {
1111 let pool = &b.msdf_shader_pool;
1112 let shader_id = pool.first().copied().unwrap_or(0);
1113 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1114 return format!(
1115 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1116 font_id, tex_id, shader_id, pool_json.join(",")
1117 );
1118 }
1119 }
1120
1121 let tex_id = b.next_texture_id;
1123 b.next_texture_id += 1;
1124 b.texture_path_to_id.insert(key, tex_id);
1125
1126 let (_pixels, _width, _height, mut font) =
1128 crate::renderer::msdf::generate_builtin_msdf_font();
1129 font.texture_id = tex_id;
1130
1131 let font_id = b.msdf_fonts.register(font);
1133 b.texture_path_to_id
1134 .insert(format!("__msdf_font_{tex_id}__"), font_id);
1135
1136 b.msdf_builtin_queue.push((font_id, tex_id));
1139
1140 let pool = ensure_msdf_shader_pool(&mut b);
1142 let shader_id = pool.first().copied().unwrap_or(0);
1143 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1144
1145 format!(
1146 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1147 font_id, tex_id, shader_id, pool_json.join(",")
1148 )
1149}
1150
1151#[deno_core::op2]
1154#[string]
1155pub fn op_get_msdf_glyphs(
1156 state: &mut OpState,
1157 font_id: u32,
1158 #[string] text: &str,
1159) -> String {
1160 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1161 let b = bridge.borrow();
1162
1163 let font = match b.msdf_fonts.get(font_id) {
1164 Some(f) => f,
1165 None => return "[]".to_string(),
1166 };
1167
1168 let mut entries = Vec::new();
1169 for ch in text.chars() {
1170 if let Some(glyph) = font.get_glyph(ch) {
1171 entries.push(format!(
1172 "{{\"char\":{},\"uv\":[{},{},{},{}],\"advance\":{},\"width\":{},\"height\":{},\"offsetX\":{},\"offsetY\":{}}}",
1173 ch as u32,
1174 glyph.uv_x, glyph.uv_y, glyph.uv_w, glyph.uv_h,
1175 glyph.advance, glyph.width, glyph.height,
1176 glyph.offset_x, glyph.offset_y,
1177 ));
1178 }
1179 }
1180
1181 format!("[{}]", entries.join(","))
1182}
1183
1184#[deno_core::op2]
1186#[string]
1187pub fn op_get_msdf_font_info(state: &mut OpState, font_id: u32) -> String {
1188 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1189 let b = bridge.borrow();
1190
1191 match b.msdf_fonts.get(font_id) {
1192 Some(font) => format!(
1193 "{{\"fontSize\":{},\"lineHeight\":{},\"distanceRange\":{},\"textureId\":{}}}",
1194 font.font_size, font.line_height, font.distance_range, font.texture_id,
1195 ),
1196 None => "null".to_string(),
1197 }
1198}
1199
1200#[deno_core::op2]
1203#[string]
1204pub fn op_load_msdf_font(
1205 state: &mut OpState,
1206 #[string] atlas_path: &str,
1207 #[string] metrics_json: &str,
1208) -> String {
1209 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1210 let mut b = bridge.borrow_mut();
1211
1212 let resolved = if std::path::Path::new(atlas_path).is_absolute() {
1214 atlas_path.to_string()
1215 } else {
1216 b.base_dir.join(atlas_path).to_string_lossy().to_string()
1217 };
1218
1219 let tex_id = if let Some(&id) = b.texture_path_to_id.get(&resolved) {
1221 id
1222 } else {
1223 let id = b.next_texture_id;
1224 b.next_texture_id += 1;
1225 b.texture_path_to_id.insert(resolved.clone(), id);
1226 b.msdf_texture_load_queue.push((resolved, id));
1227 id
1228 };
1229
1230 let font = match crate::renderer::msdf::parse_msdf_metrics(metrics_json, tex_id) {
1232 Ok(f) => f,
1233 Err(e) => {
1234 return format!("{{\"error\":\"{}\"}}", e);
1235 }
1236 };
1237
1238 let font_id = b.msdf_fonts.register(font);
1239 let pool = ensure_msdf_shader_pool(&mut b);
1240 let shader_id = pool.first().copied().unwrap_or(0);
1241 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1242
1243 format!(
1244 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1245 font_id, tex_id, shader_id, pool_json.join(",")
1246 )
1247}
1248
1249const MSDF_SHADER_POOL_SIZE: usize = 8;
1251
1252fn ensure_msdf_shader_pool(b: &mut RenderBridgeState) -> Vec<u32> {
1254 if !b.msdf_shader_pool.is_empty() {
1255 return b.msdf_shader_pool.clone();
1256 }
1257
1258 let source = crate::renderer::msdf::MSDF_FRAGMENT_SOURCE.to_string();
1259 let mut pool = Vec::with_capacity(MSDF_SHADER_POOL_SIZE);
1260
1261 for _ in 0..MSDF_SHADER_POOL_SIZE {
1262 let id = b.next_shader_id;
1263 b.next_shader_id += 1;
1264 b.msdf_shader_queue.push((id, source.clone()));
1265 pool.push(id);
1266 }
1267
1268 b.msdf_shader_pool = pool.clone();
1269 pool
1270}
1271
1272#[deno_core::op2(fast)]
1276pub fn op_get_gamepad_count(state: &mut OpState) -> u32 {
1277 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1278 bridge.borrow().gamepad_count
1279}
1280
1281#[deno_core::op2]
1283#[string]
1284pub fn op_get_gamepad_name(state: &mut OpState) -> String {
1285 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1286 bridge.borrow().gamepad_name.clone()
1287}
1288
1289#[deno_core::op2(fast)]
1292pub fn op_is_gamepad_button_down(state: &mut OpState, #[string] button: &str) -> bool {
1293 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1294 bridge.borrow().gamepad_buttons_down.contains(button)
1295}
1296
1297#[deno_core::op2(fast)]
1299pub fn op_is_gamepad_button_pressed(state: &mut OpState, #[string] button: &str) -> bool {
1300 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1301 bridge.borrow().gamepad_buttons_pressed.contains(button)
1302}
1303
1304#[deno_core::op2(fast)]
1307pub fn op_get_gamepad_axis(state: &mut OpState, #[string] axis: &str) -> f64 {
1308 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1309 bridge.borrow().gamepad_axes.get(axis).copied().unwrap_or(0.0) as f64
1310}
1311
1312#[deno_core::op2(fast)]
1316pub fn op_get_touch_count(state: &mut OpState) -> u32 {
1317 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1318 bridge.borrow().touch_count
1319}
1320
1321#[deno_core::op2]
1323#[serde]
1324pub fn op_get_touch_position(state: &mut OpState, index: u32) -> Vec<f64> {
1325 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1326 let b = bridge.borrow();
1327 if let Some(&(_, x, y)) = b.touch_points.get(index as usize) {
1328 vec![x as f64, y as f64]
1329 } else {
1330 vec![]
1331 }
1332}
1333
1334#[deno_core::op2(fast)]
1336pub fn op_is_touch_active(state: &mut OpState) -> bool {
1337 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1338 bridge.borrow().touch_count > 0
1339}
1340
1341deno_core::extension!(
1342 render_ext,
1343 ops = [
1344 op_draw_sprite,
1345 op_clear_sprites,
1346 op_set_camera,
1347 op_get_camera,
1348 op_load_texture,
1349 op_is_key_down,
1350 op_is_key_pressed,
1351 op_get_mouse_position,
1352 op_is_mouse_button_down,
1353 op_is_mouse_button_pressed,
1354 op_get_delta_time,
1355 op_create_solid_texture,
1356 op_create_tilemap,
1357 op_set_tile,
1358 op_get_tile,
1359 op_draw_tilemap,
1360 op_set_ambient_light,
1361 op_add_point_light,
1362 op_clear_lights,
1363 op_load_sound,
1364 op_play_sound,
1365 op_stop_sound,
1366 op_stop_all_sounds,
1367 op_set_master_volume,
1368 op_play_sound_ex,
1369 op_play_sound_spatial,
1370 op_stop_instance,
1371 op_set_instance_volume,
1372 op_set_instance_pitch,
1373 op_update_spatial_positions,
1374 op_set_bus_volume,
1375 op_create_font_texture,
1376 op_get_viewport_size,
1377 op_get_scale_factor,
1378 op_set_background_color,
1379 op_save_file,
1380 op_load_file,
1381 op_delete_file,
1382 op_list_save_files,
1383 op_create_shader,
1384 op_set_shader_param,
1385 op_add_effect,
1386 op_set_effect_param,
1387 op_remove_effect,
1388 op_clear_effects,
1389 op_set_camera_bounds,
1390 op_clear_camera_bounds,
1391 op_get_camera_bounds,
1392 op_enable_gi,
1393 op_disable_gi,
1394 op_set_gi_intensity,
1395 op_set_gi_quality,
1396 op_add_emissive,
1397 op_clear_emissives,
1398 op_add_occluder,
1399 op_clear_occluders,
1400 op_add_directional_light,
1401 op_add_spot_light,
1402 op_create_msdf_builtin_font,
1403 op_get_msdf_glyphs,
1404 op_get_msdf_font_info,
1405 op_load_msdf_font,
1406 op_get_gamepad_count,
1407 op_get_gamepad_name,
1408 op_is_gamepad_button_down,
1409 op_is_gamepad_button_pressed,
1410 op_get_gamepad_axis,
1411 op_get_touch_count,
1412 op_get_touch_position,
1413 op_is_touch_active,
1414 ],
1415);