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
23#[derive(Clone)]
26pub struct RenderBridgeState {
27 pub sprite_commands: Vec<SpriteCommand>,
28 pub camera_x: f32,
29 pub camera_y: f32,
30 pub camera_zoom: f32,
31 pub delta_time: f64,
32 pub keys_down: std::collections::HashSet<String>,
34 pub keys_pressed: std::collections::HashSet<String>,
35 pub mouse_x: f32,
36 pub mouse_y: f32,
37 pub texture_load_queue: Vec<(String, u32)>,
39 pub base_dir: PathBuf,
41 pub next_texture_id: u32,
43 pub texture_path_to_id: std::collections::HashMap<String, u32>,
45 pub tilemaps: TilemapStore,
47 pub ambient_light: [f32; 3],
49 pub point_lights: Vec<PointLight>,
51 pub audio_commands: Vec<BridgeAudioCommand>,
53 pub next_sound_id: u32,
55 pub sound_path_to_id: std::collections::HashMap<String, u32>,
57 pub font_texture_queue: Vec<u32>,
59 pub viewport_width: f32,
61 pub viewport_height: f32,
62 pub scale_factor: f32,
64 pub clear_color: [f32; 4],
66 pub save_dir: PathBuf,
68 pub shader_create_queue: Vec<(u32, String, String)>,
70 pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
72 pub next_shader_id: u32,
74 pub effect_create_queue: Vec<(u32, String)>,
76 pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
78 pub effect_remove_queue: Vec<u32>,
80 pub effect_clear: bool,
82 pub next_effect_id: u32,
84 pub camera_bounds: Option<CameraBounds>,
86 pub gi_enabled: bool,
88 pub gi_intensity: f32,
90 pub gi_probe_spacing: Option<f32>,
92 pub gi_interval: Option<f32>,
94 pub gi_cascade_count: Option<u32>,
96 pub emissives: Vec<[f32; 8]>,
98 pub occluders: Vec<[f32; 4]>,
100 pub directional_lights: Vec<[f32; 5]>,
102 pub spot_lights: Vec<[f32; 9]>,
104 pub msdf_fonts: MsdfFontStore,
106 pub msdf_builtin_queue: Vec<(u32, u32)>,
108 pub msdf_shader_queue: Vec<(u32, String)>,
110 pub msdf_shader_pool: Vec<u32>,
112 pub msdf_texture_load_queue: Vec<(String, u32)>,
114}
115
116impl RenderBridgeState {
117 pub fn new(base_dir: PathBuf) -> Self {
118 let save_dir = base_dir.join(".arcane").join("saves");
119 Self {
120 sprite_commands: Vec::new(),
121 camera_x: 0.0,
122 camera_y: 0.0,
123 camera_zoom: 1.0,
124 delta_time: 0.0,
125 keys_down: std::collections::HashSet::new(),
126 keys_pressed: std::collections::HashSet::new(),
127 mouse_x: 0.0,
128 mouse_y: 0.0,
129 texture_load_queue: Vec::new(),
130 base_dir,
131 next_texture_id: 1,
132 texture_path_to_id: std::collections::HashMap::new(),
133 tilemaps: TilemapStore::new(),
134 ambient_light: [1.0, 1.0, 1.0],
135 point_lights: Vec::new(),
136 audio_commands: Vec::new(),
137 next_sound_id: 1,
138 sound_path_to_id: std::collections::HashMap::new(),
139 font_texture_queue: Vec::new(),
140 viewport_width: 800.0,
141 viewport_height: 600.0,
142 scale_factor: 1.0,
143 clear_color: [0.1, 0.1, 0.15, 1.0],
144 save_dir,
145 shader_create_queue: Vec::new(),
146 shader_param_queue: Vec::new(),
147 next_shader_id: 1,
148 effect_create_queue: Vec::new(),
149 effect_param_queue: Vec::new(),
150 effect_remove_queue: Vec::new(),
151 effect_clear: false,
152 next_effect_id: 1,
153 camera_bounds: None,
154 gi_enabled: false,
155 gi_intensity: 1.0,
156 gi_probe_spacing: None,
157 gi_interval: None,
158 gi_cascade_count: None,
159 emissives: Vec::new(),
160 occluders: Vec::new(),
161 directional_lights: Vec::new(),
162 spot_lights: Vec::new(),
163 msdf_fonts: MsdfFontStore::new(),
164 msdf_builtin_queue: Vec::new(),
165 msdf_shader_queue: Vec::new(),
166 msdf_shader_pool: Vec::new(),
167 msdf_texture_load_queue: Vec::new(),
168 }
169 }
170}
171
172#[deno_core::op2(fast)]
175pub fn op_draw_sprite(
176 state: &mut OpState,
177 texture_id: u32,
178 x: f64,
179 y: f64,
180 w: f64,
181 h: f64,
182 layer: i32,
183 uv_x: f64,
184 uv_y: f64,
185 uv_w: f64,
186 uv_h: f64,
187 tint_r: f64,
188 tint_g: f64,
189 tint_b: f64,
190 tint_a: f64,
191 rotation: f64,
192 origin_x: f64,
193 origin_y: f64,
194 flip_x: f64,
195 flip_y: f64,
196 opacity: f64,
197 blend_mode: f64,
198 shader_id: f64,
199) {
200 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
201 bridge.borrow_mut().sprite_commands.push(SpriteCommand {
202 texture_id,
203 x: x as f32,
204 y: y as f32,
205 w: w as f32,
206 h: h as f32,
207 layer,
208 uv_x: uv_x as f32,
209 uv_y: uv_y as f32,
210 uv_w: uv_w as f32,
211 uv_h: uv_h as f32,
212 tint_r: tint_r as f32,
213 tint_g: tint_g as f32,
214 tint_b: tint_b as f32,
215 tint_a: tint_a as f32,
216 rotation: rotation as f32,
217 origin_x: origin_x as f32,
218 origin_y: origin_y as f32,
219 flip_x: flip_x != 0.0,
220 flip_y: flip_y != 0.0,
221 opacity: opacity as f32,
222 blend_mode: (blend_mode as u8).min(3),
223 shader_id: shader_id as u32,
224 });
225}
226
227#[deno_core::op2(fast)]
229pub fn op_clear_sprites(state: &mut OpState) {
230 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
231 bridge.borrow_mut().sprite_commands.clear();
232}
233
234#[deno_core::op2(fast)]
237pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
238 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
239 let mut b = bridge.borrow_mut();
240 b.camera_x = x as f32;
241 b.camera_y = y as f32;
242 b.camera_zoom = zoom as f32;
243}
244
245#[deno_core::op2]
247#[serde]
248pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
249 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
250 let b = bridge.borrow();
251 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
252}
253
254#[deno_core::op2(fast)]
257pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
258 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
259 let mut b = bridge.borrow_mut();
260
261 let resolved = if std::path::Path::new(path).is_absolute() {
263 path.to_string()
264 } else {
265 b.base_dir.join(path).to_string_lossy().to_string()
266 };
267
268 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
270 return id;
271 }
272
273 let id = b.next_texture_id;
274 b.next_texture_id += 1;
275 b.texture_path_to_id.insert(resolved.clone(), id);
276 b.texture_load_queue.push((resolved, id));
277 id
278}
279
280#[deno_core::op2(fast)]
282pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
283 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
284 bridge.borrow().keys_down.contains(key)
285}
286
287#[deno_core::op2(fast)]
289pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
290 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
291 bridge.borrow().keys_pressed.contains(key)
292}
293
294#[deno_core::op2]
296#[serde]
297pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
298 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
299 let b = bridge.borrow();
300 vec![b.mouse_x as f64, b.mouse_y as f64]
301}
302
303#[deno_core::op2(fast)]
305pub fn op_get_delta_time(state: &mut OpState) -> f64 {
306 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
307 bridge.borrow().delta_time
308}
309
310#[deno_core::op2(fast)]
313pub fn op_create_solid_texture(
314 state: &mut OpState,
315 #[string] name: &str,
316 r: u32,
317 g: u32,
318 b: u32,
319 a: u32,
320) -> u32 {
321 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
322 let mut br = bridge.borrow_mut();
323
324 let key = format!("__solid__{name}");
325 if let Some(&id) = br.texture_path_to_id.get(&key) {
326 return id;
327 }
328
329 let id = br.next_texture_id;
330 br.next_texture_id += 1;
331 br.texture_path_to_id.insert(key.clone(), id);
332 br.texture_load_queue
334 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
335 id
336}
337
338#[deno_core::op2(fast)]
340pub fn op_create_tilemap(
341 state: &mut OpState,
342 texture_id: u32,
343 width: u32,
344 height: u32,
345 tile_size: f64,
346 atlas_columns: u32,
347 atlas_rows: u32,
348) -> u32 {
349 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
350 bridge
351 .borrow_mut()
352 .tilemaps
353 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
354}
355
356#[deno_core::op2(fast)]
358pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
359 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
360 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
361 tm.set_tile(gx, gy, tile_id as u16);
362 }
363}
364
365#[deno_core::op2(fast)]
367pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
368 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
369 bridge
370 .borrow()
371 .tilemaps
372 .get(tilemap_id)
373 .map(|tm| tm.get_tile(gx, gy) as u32)
374 .unwrap_or(0)
375}
376
377#[deno_core::op2(fast)]
380pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
381 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
382 let mut b = bridge.borrow_mut();
383 let cam_x = b.camera_x;
384 let cam_y = b.camera_y;
385 let cam_zoom = b.camera_zoom;
386 let vp_w = 800.0;
388 let vp_h = 600.0;
389
390 if let Some(tm) = b.tilemaps.get(tilemap_id) {
391 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
392 b.sprite_commands.extend(cmds);
393 }
394}
395
396#[deno_core::op2(fast)]
401pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
402 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
403 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
404}
405
406#[deno_core::op2(fast)]
409pub fn op_add_point_light(
410 state: &mut OpState,
411 x: f64,
412 y: f64,
413 radius: f64,
414 r: f64,
415 g: f64,
416 b: f64,
417 intensity: f64,
418) {
419 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
420 bridge.borrow_mut().point_lights.push(PointLight {
421 x: x as f32,
422 y: y as f32,
423 radius: radius as f32,
424 r: r as f32,
425 g: g as f32,
426 b: b as f32,
427 intensity: intensity as f32,
428 });
429}
430
431#[deno_core::op2(fast)]
433pub fn op_clear_lights(state: &mut OpState) {
434 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
435 bridge.borrow_mut().point_lights.clear();
436}
437
438#[deno_core::op2(fast)]
442pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
443 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
444 let mut b = bridge.borrow_mut();
445
446 let resolved = if std::path::Path::new(path).is_absolute() {
447 path.to_string()
448 } else {
449 b.base_dir.join(path).to_string_lossy().to_string()
450 };
451
452 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
453 return id;
454 }
455
456 let id = b.next_sound_id;
457 b.next_sound_id += 1;
458 b.sound_path_to_id.insert(resolved.clone(), id);
459 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
460 id
461}
462
463#[deno_core::op2(fast)]
466pub fn op_play_sound(state: &mut OpState, id: u32, volume: f64, looping: bool) {
467 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
468 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySound { id, volume: volume as f32, looping });
469}
470
471#[deno_core::op2(fast)]
473pub fn op_stop_sound(state: &mut OpState, id: u32) {
474 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
475 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopSound { id });
476}
477
478#[deno_core::op2(fast)]
480pub fn op_stop_all_sounds(state: &mut OpState) {
481 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
482 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
483}
484
485#[deno_core::op2(fast)]
488pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
489 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
490 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
491}
492
493#[deno_core::op2(fast)]
497pub fn op_create_font_texture(state: &mut OpState) -> u32 {
498 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
499 let mut b = bridge.borrow_mut();
500
501 let key = "__builtin_font__".to_string();
502 if let Some(&id) = b.texture_path_to_id.get(&key) {
503 return id;
504 }
505
506 let id = b.next_texture_id;
507 b.next_texture_id += 1;
508 b.texture_path_to_id.insert(key, id);
509 b.font_texture_queue.push(id);
510 id
511}
512
513#[deno_core::op2]
517#[serde]
518pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
519 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
520 let b = bridge.borrow();
521 vec![b.viewport_width as f64, b.viewport_height as f64]
522}
523
524#[deno_core::op2(fast)]
526pub fn op_get_scale_factor(state: &mut OpState) -> f64 {
527 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
528 bridge.borrow().scale_factor as f64
529}
530
531#[deno_core::op2(fast)]
533pub fn op_set_background_color(state: &mut OpState, r: f64, g: f64, b: f64) {
534 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
535 let mut br = bridge.borrow_mut();
536 br.clear_color = [r as f32, g as f32, b as f32, 1.0];
537}
538
539#[deno_core::op2(fast)]
543pub fn op_save_file(state: &mut OpState, #[string] key: &str, #[string] value: &str) -> bool {
544 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
545 let save_dir = bridge.borrow().save_dir.clone();
546
547 if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
549 return false;
550 }
551
552 if std::fs::create_dir_all(&save_dir).is_err() {
554 return false;
555 }
556
557 let path = save_dir.join(format!("{key}.json"));
558 std::fs::write(path, value).is_ok()
559}
560
561#[deno_core::op2]
563#[string]
564pub fn op_load_file(state: &mut OpState, #[string] key: &str) -> String {
565 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
566 let save_dir = bridge.borrow().save_dir.clone();
567
568 let path = save_dir.join(format!("{key}.json"));
569 std::fs::read_to_string(path).unwrap_or_default()
570}
571
572#[deno_core::op2(fast)]
574pub fn op_delete_file(state: &mut OpState, #[string] key: &str) -> bool {
575 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
576 let save_dir = bridge.borrow().save_dir.clone();
577
578 let path = save_dir.join(format!("{key}.json"));
579 std::fs::remove_file(path).is_ok()
580}
581
582#[deno_core::op2]
584#[serde]
585pub fn op_list_save_files(state: &mut OpState) -> Vec<String> {
586 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
587 let save_dir = bridge.borrow().save_dir.clone();
588
589 let mut keys = Vec::new();
590 if let Ok(entries) = std::fs::read_dir(&save_dir) {
591 for entry in entries.flatten() {
592 let path = entry.path();
593 if path.extension().map_or(false, |ext| ext == "json") {
594 if let Some(stem) = path.file_stem() {
595 keys.push(stem.to_string_lossy().to_string());
596 }
597 }
598 }
599 }
600 keys.sort();
601 keys
602}
603
604#[deno_core::op2(fast)]
608pub fn op_create_shader(state: &mut OpState, #[string] name: &str, #[string] source: &str) -> u32 {
609 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
610 let mut b = bridge.borrow_mut();
611 let id = b.next_shader_id;
612 b.next_shader_id += 1;
613 b.shader_create_queue
614 .push((id, name.to_string(), source.to_string()));
615 id
616}
617
618#[deno_core::op2(fast)]
620pub fn op_set_shader_param(
621 state: &mut OpState,
622 shader_id: u32,
623 index: u32,
624 x: f64,
625 y: f64,
626 z: f64,
627 w: f64,
628) {
629 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
630 bridge.borrow_mut().shader_param_queue.push((
631 shader_id,
632 index,
633 [x as f32, y as f32, z as f32, w as f32],
634 ));
635}
636
637#[deno_core::op2(fast)]
641pub fn op_add_effect(state: &mut OpState, #[string] effect_type: &str) -> u32 {
642 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
643 let mut b = bridge.borrow_mut();
644 let id = b.next_effect_id;
645 b.next_effect_id += 1;
646 b.effect_create_queue
647 .push((id, effect_type.to_string()));
648 id
649}
650
651#[deno_core::op2(fast)]
653pub fn op_set_effect_param(
654 state: &mut OpState,
655 effect_id: u32,
656 index: u32,
657 x: f64,
658 y: f64,
659 z: f64,
660 w: f64,
661) {
662 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
663 bridge.borrow_mut().effect_param_queue.push((
664 effect_id,
665 index,
666 [x as f32, y as f32, z as f32, w as f32],
667 ));
668}
669
670#[deno_core::op2(fast)]
672pub fn op_remove_effect(state: &mut OpState, effect_id: u32) {
673 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
674 bridge.borrow_mut().effect_remove_queue.push(effect_id);
675}
676
677#[deno_core::op2(fast)]
679pub fn op_clear_effects(state: &mut OpState) {
680 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
681 bridge.borrow_mut().effect_clear = true;
682}
683
684#[deno_core::op2(fast)]
688pub fn op_set_camera_bounds(state: &mut OpState, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
689 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
690 bridge.borrow_mut().camera_bounds = Some(CameraBounds {
691 min_x: min_x as f32,
692 min_y: min_y as f32,
693 max_x: max_x as f32,
694 max_y: max_y as f32,
695 });
696}
697
698#[deno_core::op2(fast)]
700pub fn op_clear_camera_bounds(state: &mut OpState) {
701 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
702 bridge.borrow_mut().camera_bounds = None;
703}
704
705#[deno_core::op2]
707#[serde]
708pub fn op_get_camera_bounds(state: &mut OpState) -> Vec<f64> {
709 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
710 let b = bridge.borrow();
711 match b.camera_bounds {
712 Some(bounds) => vec![
713 bounds.min_x as f64,
714 bounds.min_y as f64,
715 bounds.max_x as f64,
716 bounds.max_y as f64,
717 ],
718 None => vec![],
719 }
720}
721
722#[deno_core::op2(fast)]
726pub fn op_enable_gi(state: &mut OpState) {
727 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
728 bridge.borrow_mut().gi_enabled = true;
729}
730
731#[deno_core::op2(fast)]
733pub fn op_disable_gi(state: &mut OpState) {
734 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
735 bridge.borrow_mut().gi_enabled = false;
736}
737
738#[deno_core::op2(fast)]
740pub fn op_set_gi_intensity(state: &mut OpState, intensity: f64) {
741 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
742 bridge.borrow_mut().gi_intensity = intensity as f32;
743}
744
745#[deno_core::op2(fast)]
748pub fn op_set_gi_quality(state: &mut OpState, probe_spacing: f64, interval: f64, cascade_count: f64) {
749 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
750 let mut b = bridge.borrow_mut();
751 if probe_spacing > 0.0 {
752 b.gi_probe_spacing = Some(probe_spacing as f32);
753 }
754 if interval > 0.0 {
755 b.gi_interval = Some(interval as f32);
756 }
757 if cascade_count > 0.0 {
758 b.gi_cascade_count = Some(cascade_count as u32);
759 }
760}
761
762#[deno_core::op2(fast)]
764pub fn op_add_emissive(
765 state: &mut OpState,
766 x: f64,
767 y: f64,
768 w: f64,
769 h: f64,
770 r: f64,
771 g: f64,
772 b: f64,
773 intensity: f64,
774) {
775 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
776 bridge.borrow_mut().emissives.push([
777 x as f32,
778 y as f32,
779 w as f32,
780 h as f32,
781 r as f32,
782 g as f32,
783 b as f32,
784 intensity as f32,
785 ]);
786}
787
788#[deno_core::op2(fast)]
790pub fn op_clear_emissives(state: &mut OpState) {
791 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
792 bridge.borrow_mut().emissives.clear();
793}
794
795#[deno_core::op2(fast)]
797pub fn op_add_occluder(state: &mut OpState, x: f64, y: f64, w: f64, h: f64) {
798 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
799 bridge.borrow_mut().occluders.push([x as f32, y as f32, w as f32, h as f32]);
800}
801
802#[deno_core::op2(fast)]
804pub fn op_clear_occluders(state: &mut OpState) {
805 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
806 bridge.borrow_mut().occluders.clear();
807}
808
809#[deno_core::op2(fast)]
811pub fn op_add_directional_light(
812 state: &mut OpState,
813 angle: f64,
814 r: f64,
815 g: f64,
816 b: f64,
817 intensity: f64,
818) {
819 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
820 bridge.borrow_mut().directional_lights.push([
821 angle as f32,
822 r as f32,
823 g as f32,
824 b as f32,
825 intensity as f32,
826 ]);
827}
828
829#[deno_core::op2(fast)]
831pub fn op_add_spot_light(
832 state: &mut OpState,
833 x: f64,
834 y: f64,
835 angle: f64,
836 spread: f64,
837 range: f64,
838 r: f64,
839 g: f64,
840 b: f64,
841 intensity: f64,
842) {
843 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
844 bridge.borrow_mut().spot_lights.push([
845 x as f32,
846 y as f32,
847 angle as f32,
848 spread as f32,
849 range as f32,
850 r as f32,
851 g as f32,
852 b as f32,
853 intensity as f32,
854 ]);
855}
856
857#[deno_core::op2]
862#[string]
863pub fn op_create_msdf_builtin_font(state: &mut OpState) -> String {
864 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
865 let mut b = bridge.borrow_mut();
866
867 let key = "__msdf_builtin__".to_string();
869 if let Some(&tex_id) = b.texture_path_to_id.get(&key) {
870 let font_id_key = format!("__msdf_font_{tex_id}__");
873 if let Some(&font_id) = b.texture_path_to_id.get(&font_id_key) {
874 let pool = &b.msdf_shader_pool;
875 let shader_id = pool.first().copied().unwrap_or(0);
876 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
877 return format!(
878 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
879 font_id, tex_id, shader_id, pool_json.join(",")
880 );
881 }
882 }
883
884 let tex_id = b.next_texture_id;
886 b.next_texture_id += 1;
887 b.texture_path_to_id.insert(key, tex_id);
888
889 let (_pixels, _width, _height, mut font) =
891 crate::renderer::msdf::generate_builtin_msdf_font();
892 font.texture_id = tex_id;
893
894 let font_id = b.msdf_fonts.register(font);
896 b.texture_path_to_id
897 .insert(format!("__msdf_font_{tex_id}__"), font_id);
898
899 b.msdf_builtin_queue.push((font_id, tex_id));
902
903 let pool = ensure_msdf_shader_pool(&mut b);
905 let shader_id = pool.first().copied().unwrap_or(0);
906 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
907
908 format!(
909 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
910 font_id, tex_id, shader_id, pool_json.join(",")
911 )
912}
913
914#[deno_core::op2]
917#[string]
918pub fn op_get_msdf_glyphs(
919 state: &mut OpState,
920 font_id: u32,
921 #[string] text: &str,
922) -> String {
923 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
924 let b = bridge.borrow();
925
926 let font = match b.msdf_fonts.get(font_id) {
927 Some(f) => f,
928 None => return "[]".to_string(),
929 };
930
931 let mut entries = Vec::new();
932 for ch in text.chars() {
933 if let Some(glyph) = font.get_glyph(ch) {
934 entries.push(format!(
935 "{{\"char\":{},\"uv\":[{},{},{},{}],\"advance\":{},\"width\":{},\"height\":{},\"offsetX\":{},\"offsetY\":{}}}",
936 ch as u32,
937 glyph.uv_x, glyph.uv_y, glyph.uv_w, glyph.uv_h,
938 glyph.advance, glyph.width, glyph.height,
939 glyph.offset_x, glyph.offset_y,
940 ));
941 }
942 }
943
944 format!("[{}]", entries.join(","))
945}
946
947#[deno_core::op2]
949#[string]
950pub fn op_get_msdf_font_info(state: &mut OpState, font_id: u32) -> String {
951 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
952 let b = bridge.borrow();
953
954 match b.msdf_fonts.get(font_id) {
955 Some(font) => format!(
956 "{{\"fontSize\":{},\"lineHeight\":{},\"distanceRange\":{},\"textureId\":{}}}",
957 font.font_size, font.line_height, font.distance_range, font.texture_id,
958 ),
959 None => "null".to_string(),
960 }
961}
962
963#[deno_core::op2]
966#[string]
967pub fn op_load_msdf_font(
968 state: &mut OpState,
969 #[string] atlas_path: &str,
970 #[string] metrics_json: &str,
971) -> String {
972 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
973 let mut b = bridge.borrow_mut();
974
975 let resolved = if std::path::Path::new(atlas_path).is_absolute() {
977 atlas_path.to_string()
978 } else {
979 b.base_dir.join(atlas_path).to_string_lossy().to_string()
980 };
981
982 let tex_id = if let Some(&id) = b.texture_path_to_id.get(&resolved) {
984 id
985 } else {
986 let id = b.next_texture_id;
987 b.next_texture_id += 1;
988 b.texture_path_to_id.insert(resolved.clone(), id);
989 b.msdf_texture_load_queue.push((resolved, id));
990 id
991 };
992
993 let font = match crate::renderer::msdf::parse_msdf_metrics(metrics_json, tex_id) {
995 Ok(f) => f,
996 Err(e) => {
997 return format!("{{\"error\":\"{}\"}}", e);
998 }
999 };
1000
1001 let font_id = b.msdf_fonts.register(font);
1002 let pool = ensure_msdf_shader_pool(&mut b);
1003 let shader_id = pool.first().copied().unwrap_or(0);
1004 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1005
1006 format!(
1007 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1008 font_id, tex_id, shader_id, pool_json.join(",")
1009 )
1010}
1011
1012const MSDF_SHADER_POOL_SIZE: usize = 8;
1014
1015fn ensure_msdf_shader_pool(b: &mut RenderBridgeState) -> Vec<u32> {
1017 if !b.msdf_shader_pool.is_empty() {
1018 return b.msdf_shader_pool.clone();
1019 }
1020
1021 let source = crate::renderer::msdf::MSDF_FRAGMENT_SOURCE.to_string();
1022 let mut pool = Vec::with_capacity(MSDF_SHADER_POOL_SIZE);
1023
1024 for _ in 0..MSDF_SHADER_POOL_SIZE {
1025 let id = b.next_shader_id;
1026 b.next_shader_id += 1;
1027 b.msdf_shader_queue.push((id, source.clone()));
1028 pool.push(id);
1029 }
1030
1031 b.msdf_shader_pool = pool.clone();
1032 pool
1033}
1034
1035deno_core::extension!(
1036 render_ext,
1037 ops = [
1038 op_draw_sprite,
1039 op_clear_sprites,
1040 op_set_camera,
1041 op_get_camera,
1042 op_load_texture,
1043 op_is_key_down,
1044 op_is_key_pressed,
1045 op_get_mouse_position,
1046 op_get_delta_time,
1047 op_create_solid_texture,
1048 op_create_tilemap,
1049 op_set_tile,
1050 op_get_tile,
1051 op_draw_tilemap,
1052 op_set_ambient_light,
1053 op_add_point_light,
1054 op_clear_lights,
1055 op_load_sound,
1056 op_play_sound,
1057 op_stop_sound,
1058 op_stop_all_sounds,
1059 op_set_master_volume,
1060 op_create_font_texture,
1061 op_get_viewport_size,
1062 op_get_scale_factor,
1063 op_set_background_color,
1064 op_save_file,
1065 op_load_file,
1066 op_delete_file,
1067 op_list_save_files,
1068 op_create_shader,
1069 op_set_shader_param,
1070 op_add_effect,
1071 op_set_effect_param,
1072 op_remove_effect,
1073 op_clear_effects,
1074 op_set_camera_bounds,
1075 op_clear_camera_bounds,
1076 op_get_camera_bounds,
1077 op_enable_gi,
1078 op_disable_gi,
1079 op_set_gi_intensity,
1080 op_set_gi_quality,
1081 op_add_emissive,
1082 op_clear_emissives,
1083 op_add_occluder,
1084 op_clear_occluders,
1085 op_add_directional_light,
1086 op_add_spot_light,
1087 op_create_msdf_builtin_font,
1088 op_get_msdf_glyphs,
1089 op_get_msdf_font_info,
1090 op_load_msdf_font,
1091 ],
1092);