Skip to main content

siglus_scene_vm/
layer.rs

1use crate::image_manager::ImageHandle;
2
3pub type LayerId = usize;
4pub type SpriteId = usize;
5
6#[derive(Debug, Copy, Clone, Eq, PartialEq)]
7pub enum SpriteFit {
8    /// Stretch to the entire framebuffer.
9    FullScreen,
10    /// Positioned in pixel coordinates with size controlled by `size_mode`.
11    PixelRect,
12}
13
14#[cfg(test)]
15mod tests {
16    use super::LayerManager;
17
18    #[test]
19    fn resetting_transient_effects_preserves_mesh_payload() {
20        let mut layers = LayerManager::new();
21        let layer_id = layers.create_layer();
22        let sprite_id = layers.layer_mut(layer_id).unwrap().create_sprite();
23        let sprite = layers
24            .layer_mut(layer_id)
25            .unwrap()
26            .sprite_mut(sprite_id)
27            .unwrap();
28        sprite.mesh_kind = 1;
29        sprite.mesh_file_name = Some("room.x".to_owned());
30        sprite.shadow_cast = true;
31        sprite.shadow_receive = true;
32
33        layers.reset_runtime_effects();
34
35        let sprite = layers.layer(layer_id).unwrap().sprite(sprite_id).unwrap();
36        assert_eq!(sprite.mesh_kind, 1);
37        assert_eq!(sprite.mesh_file_name.as_deref(), Some("room.x"));
38        assert!(sprite.shadow_cast);
39        assert!(sprite.shadow_receive);
40    }
41}
42
43#[derive(Debug, Copy, Clone, Eq, PartialEq)]
44pub struct ClipRect {
45    pub left: i32,
46    pub top: i32,
47    pub right: i32,
48    pub bottom: i32,
49}
50
51#[derive(Debug, Copy, Clone, Eq, PartialEq)]
52pub enum SpriteSizeMode {
53    /// Use the intrinsic image size.
54    Intrinsic,
55    /// Use an explicit size.
56    Explicit { width: u32, height: u32 },
57}
58
59#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
60pub enum SpriteBlend {
61    #[default]
62    Normal,
63    Add,
64    Sub,
65    Mul,
66    Screen,
67    Overlay,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq)]
71pub struct SpriteRuntimeLight {
72    pub id: i32,
73    pub kind: i32,
74    pub diffuse: [f32; 4],
75    pub ambient: [f32; 4],
76    pub specular: [f32; 4],
77    pub pos: [f32; 4],
78    pub dir: [f32; 4],
79    pub atten: [f32; 4],
80    pub cone: [f32; 4],
81}
82
83impl Default for SpriteRuntimeLight {
84    fn default() -> Self {
85        Self {
86            id: -1,
87            kind: -1,
88            diffuse: [0.0, 0.0, 0.0, 1.0],
89            ambient: [0.0, 0.0, 0.0, 1.0],
90            specular: [0.0, 0.0, 0.0, 1.0],
91            pos: [0.0, 0.0, 0.0, 1.0],
92            dir: [0.0, 0.0, -1.0, 0.0],
93            atten: [1.0, 0.0, 0.0, 5000.0],
94            cone: [0.0, 0.0, 1.0, 0.0],
95        }
96    }
97}
98impl SpriteBlend {
99    pub fn from_i64(v: i64) -> Self {
100        // C_elm_object clamps OBJECT.BLEND to TNM_BLEND_NORMAL..TNM_BLEND_SCREEN
101        // before converting it to a render blend mode. Overlay is an internal
102        // renderer mode and is not addressable through the Siglus property.
103        match v.clamp(0, 4) {
104            1 => SpriteBlend::Add,
105            2 => SpriteBlend::Sub,
106            3 => SpriteBlend::Mul,
107            4 => SpriteBlend::Screen,
108            _ => SpriteBlend::Normal,
109        }
110    }
111}
112
113#[derive(Debug, Clone)]
114pub struct Sprite {
115    pub image_id: Option<ImageHandle>,
116    /// Dynamic texture produced by the Eluna Emote compositor.
117    /// The outer Siglus object still uses the normal sprite pipeline.
118    pub emote_render: Option<std::sync::Arc<crate::emote::EmoteRenderPacket>>,
119    pub mask_image_id: Option<ImageHandle>,
120    pub mask_offset_x: i32,
121    pub mask_offset_y: i32,
122    pub tonecurve_image_id: Option<ImageHandle>,
123    pub tonecurve_row: f32,
124    pub tonecurve_sat: f32,
125    pub fit: SpriteFit,
126    pub size_mode: SpriteSizeMode,
127    pub visible: bool,
128    pub alpha: u8,
129    pub x: i32,
130    pub y: i32,
131    pub z: f32,
132    pub scale_x: f32,
133    pub scale_y: f32,
134    pub scale_z: f32,
135    pub rotate: f32,
136    pub rotate_x: f32,
137    pub rotate_y: f32,
138    pub pivot_x: f32,
139    pub pivot_y: f32,
140    pub pivot_z: f32,
141    /// Siglus object sprites use OBJECT.X/Y as the render anchor. For G00/PCT
142    /// object resources, vertices are offset by OBJECT.CENTER plus the texture
143    /// center stored in the cut metadata. Runtime helper sprites keep this off
144    /// and continue to use x/y as a top-left pixel rectangle.
145    pub object_anchor: bool,
146    pub texture_center_x: f32,
147    pub texture_center_y: f32,
148    pub camera_enabled: bool,
149    pub camera_eye: [f32; 3],
150    pub camera_target: [f32; 3],
151    pub camera_up: [f32; 3],
152    pub camera_view_angle_deg: f32,
153    pub culling: bool,
154    pub alpha_test: bool,
155    pub alpha_blend: bool,
156    pub fog_use: bool,
157    pub light_no: i32,
158    pub light_enabled: bool,
159    pub light_diffuse: [f32; 4],
160    pub light_ambient: [f32; 4],
161    pub light_specular: [f32; 4],
162    pub light_factor: f32,
163    pub light_kind: i32,
164    pub light_pos: [f32; 4],
165    pub light_dir: [f32; 4],
166    pub light_atten: [f32; 4],
167    pub light_cone: [f32; 4],
168    pub mesh_runtime_lights: Vec<SpriteRuntimeLight>,
169    pub fog_enabled: bool,
170    pub fog_color: [f32; 4],
171    pub fog_near: f32,
172    pub fog_far: f32,
173    pub fog_scroll_x: f32,
174    pub fog_texture_image_id: Option<ImageHandle>,
175    pub world_no: i32,
176    pub billboard: bool,
177    pub mesh_file_name: Option<String>,
178    pub mesh_animation: crate::mesh3d::MeshAnimationState,
179    /// 0 = regular sprite or 2D camera quad, 1 = static mesh, 2 = billboard mesh, 3 = skinned mesh.
180    pub mesh_kind: u8,
181    pub shadow_cast: bool,
182    pub shadow_receive: bool,
183    /// Runtime wipe/effect family applied by the renderer.
184    /// 0=none, 1=mosaic, 2=raster_h, 3=raster_v, 4=explosion_blur,
185    /// 5=shimi, 6=shimi_inv, 10=cross_mosaic, 11=cross_raster_h,
186    /// 12=cross_raster_v, 13=cross_explosion_blur.
187    pub wipe_fx_mode: u8,
188    pub wipe_fx_params: [f32; 4],
189    /// Optional secondary source texture for dual-source wipe/effect composition.
190    pub wipe_src_image_id: Option<ImageHandle>,
191    pub tr: u8,
192    pub mono: u8,
193    pub reverse: u8,
194    pub bright: u8,
195    pub dark: u8,
196    pub color_rate: u8,
197    pub color_add_r: u8,
198    pub color_add_g: u8,
199    pub color_add_b: u8,
200    pub color_r: u8,
201    pub color_g: u8,
202    pub color_b: u8,
203    /// 0 = off, 1 = use luminance as alpha, 2 = use texture alpha.
204    pub mask_mode: u8,
205    pub blend: SpriteBlend,
206    pub dst_clip: Option<ClipRect>,
207    pub src_clip: Option<ClipRect>,
208    /// Render order within a layer (ascending).
209    pub order: i32,
210}
211
212impl Default for Sprite {
213    fn default() -> Self {
214        Self {
215            image_id: None,
216            emote_render: None,
217            mask_image_id: None,
218            mask_offset_x: 0,
219            mask_offset_y: 0,
220            tonecurve_image_id: None,
221            tonecurve_row: 0.0,
222            tonecurve_sat: 0.0,
223            fit: SpriteFit::PixelRect,
224            size_mode: SpriteSizeMode::Intrinsic,
225            visible: false,
226            alpha: 255,
227            x: 0,
228            y: 0,
229            z: 0.0,
230            scale_x: 1.0,
231            scale_y: 1.0,
232            scale_z: 1.0,
233            rotate: 0.0,
234            rotate_x: 0.0,
235            rotate_y: 0.0,
236            pivot_x: 0.0,
237            pivot_y: 0.0,
238            pivot_z: 0.0,
239            object_anchor: false,
240            texture_center_x: 0.0,
241            texture_center_y: 0.0,
242            camera_enabled: false,
243            camera_eye: [0.0, 0.0, -1000.0],
244            camera_target: [0.0, 0.0, 0.0],
245            camera_up: [0.0, 1.0, 0.0],
246            camera_view_angle_deg: 45.0,
247            culling: false,
248            alpha_test: false,
249            alpha_blend: true,
250            fog_use: false,
251            light_no: -1,
252            light_enabled: false,
253            light_diffuse: [1.0, 1.0, 1.0, 1.0],
254            light_ambient: [0.0, 0.0, 0.0, 1.0],
255            light_specular: [0.0, 0.0, 0.0, 1.0],
256            light_factor: 0.0,
257            light_kind: -1,
258            light_pos: [0.0, 0.0, 0.0, 0.0],
259            light_dir: [0.0, 0.0, -1.0, 0.0],
260            light_atten: [1.0, 0.0, 0.0, 5000.0],
261            light_cone: [0.0, 0.0, 1.0, 0.0],
262            mesh_runtime_lights: Vec::new(),
263            fog_enabled: false,
264            fog_color: [0.0, 0.0, 0.0, 1.0],
265            fog_near: 0.0,
266            fog_far: 0.0,
267            fog_scroll_x: 0.0,
268            fog_texture_image_id: None,
269            world_no: -1,
270            billboard: false,
271            mesh_file_name: None,
272            mesh_animation: crate::mesh3d::MeshAnimationState::default(),
273            mesh_kind: 0,
274            shadow_cast: false,
275            shadow_receive: false,
276            wipe_fx_mode: 0,
277            wipe_fx_params: [0.0; 4],
278            wipe_src_image_id: None,
279            tr: 255,
280            mono: 0,
281            reverse: 0,
282            bright: 0,
283            dark: 0,
284            color_rate: 0,
285            color_add_r: 0,
286            color_add_g: 0,
287            color_add_b: 0,
288            color_r: 0,
289            color_g: 0,
290            color_b: 0,
291            mask_mode: 0,
292            blend: SpriteBlend::Normal,
293            dst_clip: None,
294            src_clip: None,
295            order: 0,
296        }
297    }
298}
299
300#[derive(Debug, Clone)]
301pub struct Layer {
302    sprites: Vec<Sprite>,
303}
304
305impl Default for Layer {
306    fn default() -> Self {
307        Self::new()
308    }
309}
310
311impl Layer {
312    pub fn new() -> Self {
313        Self {
314            sprites: Vec::new(),
315        }
316    }
317
318    pub fn create_sprite(&mut self) -> SpriteId {
319        let id = self.sprites.len();
320        self.sprites.push(Sprite::default());
321        id
322    }
323
324    pub fn sprite(&self, id: SpriteId) -> Option<&Sprite> {
325        self.sprites.get(id)
326    }
327
328    pub fn sprite_mut(&mut self, id: SpriteId) -> Option<&mut Sprite> {
329        self.sprites.get_mut(id)
330    }
331
332    pub fn clear_all_sprites(&mut self) {
333        // Keep sprite IDs stable: do not shrink the sprite vector.
334        for s in &mut self.sprites {
335            s.image_id = None;
336            s.emote_render = None;
337            s.mask_image_id = None;
338            s.mask_offset_x = 0;
339            s.mask_offset_y = 0;
340            s.tonecurve_image_id = None;
341            s.tonecurve_row = 0.0;
342            s.tonecurve_sat = 0.0;
343            s.visible = false;
344            s.alpha = 255;
345            s.x = 0;
346            s.y = 0;
347            s.z = 0.0;
348            s.order = 0;
349            s.fit = SpriteFit::PixelRect;
350            s.size_mode = SpriteSizeMode::Intrinsic;
351            s.scale_x = 1.0;
352            s.scale_y = 1.0;
353            s.scale_z = 1.0;
354            s.rotate = 0.0;
355            s.rotate_x = 0.0;
356            s.rotate_y = 0.0;
357            s.pivot_x = 0.0;
358            s.pivot_y = 0.0;
359            s.pivot_z = 0.0;
360            s.camera_enabled = false;
361            s.camera_eye = [0.0, 0.0, -1000.0];
362            s.camera_target = [0.0, 0.0, 0.0];
363            s.camera_up = [0.0, 1.0, 0.0];
364            s.camera_view_angle_deg = 45.0;
365            s.culling = false;
366            s.alpha_test = false;
367            s.alpha_blend = true;
368            s.fog_use = false;
369            s.light_no = -1;
370            s.light_enabled = false;
371            s.light_diffuse = [1.0, 1.0, 1.0, 1.0];
372            s.light_ambient = [0.0, 0.0, 0.0, 1.0];
373            s.light_specular = [0.0, 0.0, 0.0, 1.0];
374            s.light_factor = 0.0;
375            s.light_kind = -1;
376            s.light_pos = [0.0, 0.0, 0.0, 0.0];
377            s.light_dir = [0.0, 0.0, -1.0, 0.0];
378            s.light_atten = [1.0, 0.0, 0.0, 5000.0];
379            s.light_cone = [0.0, 0.0, 1.0, 0.0];
380            s.mesh_runtime_lights.clear();
381            s.fog_enabled = false;
382            s.fog_color = [0.0, 0.0, 0.0, 1.0];
383            s.fog_near = 0.0;
384            s.fog_far = 0.0;
385            s.fog_scroll_x = 0.0;
386            s.fog_texture_image_id = None;
387            s.world_no = -1;
388            s.billboard = false;
389            s.mesh_kind = 0;
390            s.mesh_file_name = None;
391            s.mesh_animation = crate::mesh3d::MeshAnimationState::default();
392            s.shadow_cast = false;
393            s.shadow_receive = false;
394            s.wipe_fx_mode = 0;
395            s.wipe_fx_params = [0.0; 4];
396            s.wipe_src_image_id = None;
397            s.tr = 255;
398            s.mono = 0;
399            s.reverse = 0;
400            s.bright = 0;
401            s.dark = 0;
402            s.color_rate = 0;
403            s.color_add_r = 0;
404            s.color_add_g = 0;
405            s.color_add_b = 0;
406            s.color_r = 0;
407            s.color_g = 0;
408            s.color_b = 0;
409            s.blend = SpriteBlend::Normal;
410            s.dst_clip = None;
411            s.src_clip = None;
412        }
413    }
414
415    fn sprite_ids_sorted(&self) -> Vec<SpriteId> {
416        let mut ids: Vec<SpriteId> = (0..self.sprites.len()).collect();
417        ids.sort_by(|&a, &b| {
418            let oa = self.sprites[a].order;
419            let ob = self.sprites[b].order;
420            oa.cmp(&ob).then(a.cmp(&b))
421        });
422        ids
423    }
424}
425
426#[derive(Debug, Clone, Default)]
427pub struct RenderFrame {
428    /// Ordinary painter-ordered submission when no stage wipe is active.
429    pub sprites: Vec<RenderSprite>,
430    /// Original-engine two-render-target wipe submission.
431    pub wipe: Option<WipeRenderPlan>,
432}
433
434impl RenderFrame {
435    pub fn ordinary(sprites: Vec<RenderSprite>) -> Self {
436        Self {
437            sprites,
438            wipe: None,
439        }
440    }
441
442    pub fn submitted_sprite_count(&self) -> usize {
443        self.wipe.as_ref().map_or(self.sprites.len(), |wipe| {
444            wipe.under.len() + wipe.current.len() + wipe.next.len() + wipe.over.len()
445        })
446    }
447
448    /// Debug-only flattening. Actual wipe composition is always performed by wgpu.
449    pub fn debug_flatten(&self) -> Vec<RenderSprite> {
450        if let Some(wipe) = self.wipe.as_ref() {
451            let mut out = Vec::with_capacity(self.submitted_sprite_count());
452            out.extend(wipe.under.iter().cloned());
453            out.extend(wipe.current.iter().cloned());
454            out.extend(wipe.next.iter().cloned());
455            out.extend(wipe.over.iter().cloned());
456            out
457        } else {
458            self.sprites.clone()
459        }
460    }
461}
462
463#[derive(Debug, Clone)]
464pub struct WipeRenderPlan {
465    pub under: Vec<RenderSprite>,
466    pub current: Vec<RenderSprite>,
467    pub next: Vec<RenderSprite>,
468    pub over: Vec<RenderSprite>,
469    pub wipe_type: i32,
470    pub option: Vec<i32>,
471    pub progress: f32,
472    pub mask_image_id: Option<ImageHandle>,
473    /// One RNG sample is chosen when WIPE starts and stays fixed for its lifetime.
474    pub random_seed: u32,
475}
476
477#[derive(Debug, Clone)]
478pub struct RenderSprite {
479    pub layer_id: Option<LayerId>,
480    pub sprite_id: Option<SpriteId>,
481    /// Original Siglus C++ S_tnm_sorter.order. This is separate from
482    /// Sprite::order because Sprite::order is still used by older backend
483    /// storage paths and debug output. Final scene ordering, wipe ranges,
484    /// effects, and quake ranges must use this pair.
485    pub sorter_order: i32,
486    /// Original Siglus C++ S_tnm_sorter.layer. This can be much larger than
487    /// 1023, so it must not be packed into Sprite::order.
488    pub sorter_layer: i32,
489    /// Sorter of the top-level stage entity used by C_elm_stage::get_sprite_tree()
490    /// when selecting WIPE ranges. Descendant sprites retain their own sorter
491    /// for draw ordering/effects, but the whole object subtree is selected by
492    /// its top-level parent sorter in the original engine.
493    pub wipe_sorter_order: i32,
494    pub wipe_sorter_layer: i32,
495    /// Stage-form owner used to apply SCREEN/STAGE effects exactly as the
496    /// original two-stage render preparation does. `None` is reserved for
497    /// non-stage overlays such as message-back projections and the cursor.
498    pub stage_form_id: Option<u32>,
499    pub sprite: Sprite,
500}
501
502impl RenderSprite {
503    pub fn new(layer_id: Option<LayerId>, sprite_id: Option<SpriteId>, sprite: Sprite) -> Self {
504        let (sorter_order, sorter_layer) = unpack_sprite_order(sprite.order);
505        Self {
506            layer_id,
507            sprite_id,
508            sorter_order,
509            sorter_layer,
510            wipe_sorter_order: sorter_order,
511            wipe_sorter_layer: sorter_layer,
512            stage_form_id: None,
513            sprite,
514        }
515    }
516
517    pub fn with_sorter(
518        layer_id: Option<LayerId>,
519        sprite_id: Option<SpriteId>,
520        sorter_order: i32,
521        sorter_layer: i32,
522        sprite: Sprite,
523    ) -> Self {
524        Self {
525            layer_id,
526            sprite_id,
527            sorter_order,
528            sorter_layer,
529            wipe_sorter_order: sorter_order,
530            wipe_sorter_layer: sorter_layer,
531            stage_form_id: None,
532            sprite,
533        }
534    }
535
536    pub fn set_sorter(&mut self, order: i64, layer: i64) {
537        self.sorter_order = order.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
538        self.sorter_layer = layer.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
539        self.wipe_sorter_order = self.sorter_order;
540        self.wipe_sorter_layer = self.sorter_layer;
541    }
542
543    pub fn set_wipe_sorter(&mut self, order: i64, layer: i64) {
544        self.wipe_sorter_order = order.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
545        self.wipe_sorter_layer = layer.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
546    }
547
548    pub fn set_stage_form_owner(&mut self, form_id: u32) {
549        self.stage_form_id = Some(form_id);
550    }
551}
552
553fn unpack_sprite_order(order: i32) -> (i32, i32) {
554    if order.abs() >= 1024 {
555        (order.div_euclid(1024), order.rem_euclid(1024))
556    } else {
557        (0, order)
558    }
559}
560
561#[derive(Debug, Default)]
562pub struct LayerManager {
563    bg: Sprite,
564    layers: Vec<Layer>,
565}
566
567impl LayerManager {
568    pub fn new() -> Self {
569        Self::default()
570    }
571
572    pub fn bg_mut(&mut self) -> &mut Sprite {
573        &mut self.bg
574    }
575
576    pub fn set_bg_image(&mut self, image_id: ImageHandle) {
577        self.bg.image_id = Some(image_id);
578        self.bg.mask_image_id = None;
579        self.bg.mask_offset_x = 0;
580        self.bg.mask_offset_y = 0;
581        self.bg.tonecurve_image_id = None;
582        self.bg.tonecurve_row = 0.0;
583        self.bg.tonecurve_sat = 0.0;
584        self.bg.wipe_fx_mode = 0;
585        self.bg.wipe_fx_params = [0.0; 4];
586        self.bg.wipe_src_image_id = None;
587        self.bg.fit = SpriteFit::FullScreen;
588        self.bg.size_mode = SpriteSizeMode::Intrinsic;
589        self.bg.visible = true;
590        self.bg.order = i32::MIN;
591        self.bg.scale_x = 1.0;
592        self.bg.scale_y = 1.0;
593        self.bg.scale_z = 1.0;
594        self.bg.rotate = 0.0;
595        self.bg.rotate_x = 0.0;
596        self.bg.rotate_y = 0.0;
597        self.bg.pivot_x = 0.0;
598        self.bg.pivot_y = 0.0;
599        self.bg.pivot_z = 0.0;
600        self.bg.z = 0.0;
601        self.bg.camera_enabled = false;
602        self.bg.camera_eye = [0.0, 0.0, -1000.0];
603        self.bg.camera_target = [0.0, 0.0, 0.0];
604        self.bg.camera_up = [0.0, 1.0, 0.0];
605        self.bg.camera_view_angle_deg = 45.0;
606        self.bg.culling = false;
607        self.bg.alpha_test = false;
608        self.bg.alpha_blend = true;
609        self.bg.fog_use = false;
610        self.bg.light_no = -1;
611        self.bg.light_enabled = false;
612        self.bg.light_diffuse = [1.0, 1.0, 1.0, 1.0];
613        self.bg.light_ambient = [0.0, 0.0, 0.0, 1.0];
614        self.bg.light_specular = [0.0, 0.0, 0.0, 1.0];
615        self.bg.light_factor = 0.0;
616        self.bg.light_kind = -1;
617        self.bg.light_pos = [0.0, 0.0, 0.0, 0.0];
618        self.bg.light_dir = [0.0, 0.0, -1.0, 0.0];
619        self.bg.light_atten = [1.0, 0.0, 0.0, 5000.0];
620        self.bg.light_cone = [0.0, 0.0, 1.0, 0.0];
621        self.bg.mesh_runtime_lights.clear();
622        self.bg.fog_enabled = false;
623        self.bg.fog_color = [0.0, 0.0, 0.0, 1.0];
624        self.bg.fog_near = 0.0;
625        self.bg.fog_far = 0.0;
626        self.bg.fog_scroll_x = 0.0;
627        self.bg.fog_texture_image_id = None;
628        self.bg.world_no = -1;
629        self.bg.billboard = false;
630        self.bg.mesh_kind = 0;
631        self.bg.mesh_file_name = None;
632        self.bg.mesh_animation = crate::mesh3d::MeshAnimationState::default();
633        self.bg.shadow_cast = false;
634        self.bg.shadow_receive = false;
635        self.bg.wipe_fx_mode = 0;
636        self.bg.wipe_fx_params = [0.0; 4];
637        self.bg.wipe_src_image_id = None;
638        self.bg.tr = 255;
639        self.bg.mono = 0;
640        self.bg.reverse = 0;
641        self.bg.bright = 0;
642        self.bg.dark = 0;
643        self.bg.color_rate = 0;
644        self.bg.color_add_r = 0;
645        self.bg.color_add_g = 0;
646        self.bg.color_add_b = 0;
647        self.bg.color_r = 0;
648        self.bg.color_g = 0;
649        self.bg.color_b = 0;
650        self.bg.mask_mode = 0;
651        self.bg.blend = SpriteBlend::Normal;
652        self.bg.dst_clip = None;
653        self.bg.src_clip = None;
654    }
655
656    pub fn clear_bg(&mut self) {
657        self.bg.image_id = None;
658        self.bg.mask_image_id = None;
659        self.bg.mask_offset_x = 0;
660        self.bg.mask_offset_y = 0;
661        self.bg.tonecurve_image_id = None;
662        self.bg.tonecurve_row = 0.0;
663        self.bg.tonecurve_sat = 0.0;
664        self.bg.wipe_fx_mode = 0;
665        self.bg.wipe_fx_params = [0.0; 4];
666        self.bg.wipe_src_image_id = None;
667        self.bg.light_enabled = false;
668        self.bg.light_diffuse = [1.0, 1.0, 1.0, 1.0];
669        self.bg.light_ambient = [0.0, 0.0, 0.0, 1.0];
670        self.bg.light_specular = [0.0, 0.0, 0.0, 1.0];
671        self.bg.light_factor = 0.0;
672        self.bg.light_kind = -1;
673        self.bg.light_pos = [0.0, 0.0, 0.0, 0.0];
674        self.bg.light_dir = [0.0, 0.0, -1.0, 0.0];
675        self.bg.light_atten = [1.0, 0.0, 0.0, 5000.0];
676        self.bg.light_cone = [0.0, 0.0, 1.0, 0.0];
677        self.bg.mesh_runtime_lights.clear();
678        self.bg.fog_enabled = false;
679        self.bg.fog_color = [0.0, 0.0, 0.0, 1.0];
680        self.bg.fog_near = 0.0;
681        self.bg.fog_far = 0.0;
682        self.bg.fog_scroll_x = 0.0;
683        self.bg.fog_texture_image_id = None;
684        self.bg.mesh_kind = 0;
685        self.bg.mesh_file_name = None;
686        self.bg.mesh_animation = crate::mesh3d::MeshAnimationState::default();
687        self.bg.shadow_cast = false;
688        self.bg.shadow_receive = false;
689        self.bg.visible = false;
690    }
691
692    pub fn create_layer(&mut self) -> LayerId {
693        let id = self.layers.len();
694        self.layers.push(Layer::new());
695        id
696    }
697
698    pub fn layer(&self, id: LayerId) -> Option<&Layer> {
699        self.layers.get(id)
700    }
701
702    pub fn layer_mut(&mut self, id: LayerId) -> Option<&mut Layer> {
703        self.layers.get_mut(id)
704    }
705
706    pub fn clear_layer(&mut self, id: LayerId) {
707        if let Some(layer) = self.layers.get_mut(id) {
708            layer.clear_all_sprites();
709        }
710    }
711
712    pub fn clear_all(&mut self) {
713        self.clear_bg();
714        for layer in &mut self.layers {
715            layer.clear_all_sprites();
716        }
717    }
718
719    pub fn reset_runtime_effects(&mut self) {
720        self.bg.mask_image_id = None;
721        self.bg.mask_offset_x = 0;
722        self.bg.mask_offset_y = 0;
723        self.bg.tonecurve_image_id = None;
724        self.bg.tonecurve_row = 0.0;
725        self.bg.tonecurve_sat = 0.0;
726        self.bg.wipe_fx_mode = 0;
727        self.bg.wipe_fx_params = [0.0; 4];
728        self.bg.wipe_src_image_id = None;
729        self.bg.light_enabled = false;
730        self.bg.light_diffuse = [1.0, 1.0, 1.0, 1.0];
731        self.bg.light_ambient = [0.0, 0.0, 0.0, 1.0];
732        self.bg.light_specular = [0.0, 0.0, 0.0, 1.0];
733        self.bg.light_factor = 0.0;
734        self.bg.light_kind = -1;
735        self.bg.light_pos = [0.0, 0.0, 0.0, 0.0];
736        self.bg.light_dir = [0.0, 0.0, -1.0, 0.0];
737        self.bg.light_atten = [1.0, 0.0, 0.0, 5000.0];
738        self.bg.light_cone = [0.0, 0.0, 1.0, 0.0];
739        self.bg.mesh_runtime_lights.clear();
740        self.bg.fog_enabled = false;
741        self.bg.fog_color = [0.0, 0.0, 0.0, 1.0];
742        self.bg.fog_near = 0.0;
743        self.bg.fog_far = 0.0;
744        self.bg.fog_scroll_x = 0.0;
745        self.bg.fog_texture_image_id = None;
746        for layer in &mut self.layers {
747            for s in &mut layer.sprites {
748                s.mask_image_id = None;
749                s.mask_offset_x = 0;
750                s.mask_offset_y = 0;
751                s.tonecurve_image_id = None;
752                s.tonecurve_row = 0.0;
753                s.tonecurve_sat = 0.0;
754                s.wipe_fx_mode = 0;
755                s.wipe_fx_params = [0.0; 4];
756                s.wipe_src_image_id = None;
757                s.light_enabled = false;
758                s.light_diffuse = [1.0, 1.0, 1.0, 1.0];
759                s.light_ambient = [0.0, 0.0, 0.0, 1.0];
760                s.light_factor = 0.0;
761                s.fog_enabled = false;
762                s.fog_color = [0.0, 0.0, 0.0, 1.0];
763                s.fog_near = 0.0;
764                s.fog_far = 0.0;
765                s.fog_scroll_x = 0.0;
766                s.fog_texture_image_id = None;
767            }
768        }
769    }
770
771    pub fn render_list(&self) -> Vec<RenderSprite> {
772        self.render_list_excluding(|_, _| false)
773    }
774
775    /// Build the generic layer-backed submission while skipping sprite bindings
776    /// that are owned by the Siglus OBJECT tree.  OBJECT sprites are rebuilt from
777    /// the object tree later, so cloning them here only to discard them is wasted
778    /// frame memory and work.
779    pub fn render_list_excluding<F>(&self, mut exclude: F) -> Vec<RenderSprite>
780    where
781        F: FnMut(LayerId, SpriteId) -> bool,
782    {
783        let mut out = Vec::new();
784
785        if self.bg.visible
786            && self.bg.alpha > 0
787            && self.bg.tr > 0
788            && let Some(ref img) = self.bg.image_id
789        {
790            let mut bg = self.bg.clone();
791            bg.image_id = Some(img.clone());
792            out.push(RenderSprite::new(None, None, bg));
793        }
794
795        for (layer_id, layer) in self.layers.iter().enumerate() {
796            for sprite_id in layer.sprite_ids_sorted() {
797                if exclude(layer_id, sprite_id) {
798                    continue;
799                }
800                let s = &layer.sprites[sprite_id];
801                if !s.visible {
802                    continue;
803                }
804                if s.image_id.is_none() || s.alpha == 0 || s.tr == 0 {
805                    continue;
806                }
807                out.push(RenderSprite::new(
808                    Some(layer_id),
809                    Some(sprite_id),
810                    s.clone(),
811                ));
812            }
813        }
814
815        out
816    }
817}