Add affine sprite support

This commit is contained in:
Quinn Painter 2023-04-09 20:06:04 +01:00
parent 6db7d7e513
commit 8ca5cb048c
8 changed files with 204 additions and 40 deletions

View File

@ -62,9 +62,23 @@ impl SpriteSize {
} }
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct SavedAffineSpriteData {
pub rotation: fixed::types::I20F12,
pub scale_x: fixed::types::I20F12,
pub scale_y: fixed::types::I20F12,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum SavedSpriteType {
Normal,
Affine(SavedAffineSpriteData),
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct SavedSpriteExtension { pub struct SavedSpriteExtension {
pub graphic_asset: String, pub graphic_asset: String,
pub sprite_type: SavedSpriteType,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -87,7 +101,7 @@ pub enum SavedNodeExtension {
RectCollider(SavedRectColliderExtension), RectCollider(SavedRectColliderExtension),
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct SavedTransform { pub struct SavedTransform {
pub x: fixed::types::I20F12, pub x: fixed::types::I20F12,
pub y: fixed::types::I20F12, pub y: fixed::types::I20F12,

View File

@ -3,12 +3,13 @@ use imgui::{Ui, TreeNodeFlags};
use stable_vec::StableVec; use stable_vec::StableVec;
use crate::{project_data::ProjectData, Selected}; use crate::{project_data::ProjectData, Selected};
#[derive(Default)] #[derive(Default, Clone, Copy, Debug)]
pub struct Transform { pub struct Transform {
pub x: fixed::types::I20F12, pub x: fixed::types::I20F12,
pub y: fixed::types::I20F12, pub y: fixed::types::I20F12,
} }
#[derive(Debug)]
pub enum NodeExtension { pub enum NodeExtension {
None, None,
Sprite(SpriteExtension), Sprite(SpriteExtension),
@ -31,7 +32,17 @@ impl NodeExtension {
pub fn from_saved(saved_extension: sandstone_common::SavedNodeExtension) -> Self { pub fn from_saved(saved_extension: sandstone_common::SavedNodeExtension) -> Self {
match saved_extension { match saved_extension {
sandstone_common::SavedNodeExtension::None => NodeExtension::None, sandstone_common::SavedNodeExtension::None => NodeExtension::None,
sandstone_common::SavedNodeExtension::Sprite(s) => NodeExtension::Sprite(SpriteExtension { graphic_asset: s.graphic_asset }), sandstone_common::SavedNodeExtension::Sprite(s) => NodeExtension::Sprite(SpriteExtension {
graphic_asset: s.graphic_asset,
sprite_type: match s.sprite_type {
sandstone_common::SavedSpriteType::Normal => SpriteType::Normal,
sandstone_common::SavedSpriteType::Affine(a) => SpriteType::Affine(AffineSpriteData {
rotation: a.rotation,
scale_x: a.scale_x,
scale_y: a.scale_y,
}),
},
}),
sandstone_common::SavedNodeExtension::Camera(c) => NodeExtension::Camera(CameraExtension { active_main: c.active_main, active_sub: c.active_sub }), sandstone_common::SavedNodeExtension::Camera(c) => NodeExtension::Camera(CameraExtension { active_main: c.active_main, active_sub: c.active_sub }),
sandstone_common::SavedNodeExtension::RectCollider(c) => NodeExtension::RectCollider(RectColliderExtension { width: c.width, height: c.height }) sandstone_common::SavedNodeExtension::RectCollider(c) => NodeExtension::RectCollider(RectColliderExtension { width: c.width, height: c.height })
} }
@ -40,30 +51,66 @@ impl NodeExtension {
pub fn to_saved(&self) -> sandstone_common::SavedNodeExtension { pub fn to_saved(&self) -> sandstone_common::SavedNodeExtension {
match self { match self {
NodeExtension::None => sandstone_common::SavedNodeExtension::None, NodeExtension::None => sandstone_common::SavedNodeExtension::None,
NodeExtension::Sprite(s) => sandstone_common::SavedNodeExtension::Sprite(sandstone_common::SavedSpriteExtension { graphic_asset: s.graphic_asset.clone() }), NodeExtension::Sprite(s) => sandstone_common::SavedNodeExtension::Sprite(sandstone_common::SavedSpriteExtension {
graphic_asset: s.graphic_asset.clone(),
sprite_type: match s.sprite_type {
SpriteType::Normal => sandstone_common::SavedSpriteType::Normal,
SpriteType::Affine(a) => sandstone_common::SavedSpriteType::Affine(sandstone_common::SavedAffineSpriteData {
rotation: a.rotation,
scale_x: a.scale_x,
scale_y: a.scale_y,
}),
},
}),
NodeExtension::Camera(c) => sandstone_common::SavedNodeExtension::Camera(sandstone_common::SavedCameraExtension { active_main: c.active_main, active_sub: c.active_sub }), NodeExtension::Camera(c) => sandstone_common::SavedNodeExtension::Camera(sandstone_common::SavedCameraExtension { active_main: c.active_main, active_sub: c.active_sub }),
NodeExtension::RectCollider(c) => sandstone_common::SavedNodeExtension::RectCollider(sandstone_common::SavedRectColliderExtension { width: c.width, height: c.height }), NodeExtension::RectCollider(c) => sandstone_common::SavedNodeExtension::RectCollider(sandstone_common::SavedRectColliderExtension { width: c.width, height: c.height }),
} }
} }
} }
#[derive(Default)] #[derive(Clone, Copy, Debug)]
pub struct SpriteExtension { pub struct AffineSpriteData {
pub graphic_asset: String, pub rotation: fixed::types::I20F12,
pub scale_x: fixed::types::I20F12,
pub scale_y: fixed::types::I20F12,
} }
#[derive(Default)] impl Default for AffineSpriteData {
fn default() -> Self {
Self {
rotation: fixed::types::I20F12::lit("0"),
scale_x: fixed::types::I20F12::lit("1"),
scale_y: fixed::types::I20F12::lit("1"),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum SpriteType {
#[default]
Normal,
Affine(AffineSpriteData),
}
#[derive(Default, Debug)]
pub struct SpriteExtension {
pub graphic_asset: String,
pub sprite_type: SpriteType,
}
#[derive(Default, Clone, Copy, Debug)]
pub struct CameraExtension { pub struct CameraExtension {
pub active_main: bool, pub active_main: bool,
pub active_sub: bool, pub active_sub: bool,
} }
#[derive(Default)] #[derive(Default, Clone, Copy, Debug)]
pub struct RectColliderExtension { pub struct RectColliderExtension {
pub width: fixed::types::I20F12, pub width: fixed::types::I20F12,
pub height: fixed::types::I20F12, pub height: fixed::types::I20F12,
} }
#[derive(Debug)]
pub struct Node { pub struct Node {
pub child_index: Option<NonZeroUsize>, pub child_index: Option<NonZeroUsize>,
pub parent_index: Option<usize>, pub parent_index: Option<usize>,

View File

@ -1,5 +1,5 @@
use imgui::Ui; use imgui::Ui;
use crate::{hierarchy::{Hierarchy, NodeExtension, SpriteExtension, CameraExtension, RectColliderExtension}, project_data::ProjectData, Selected}; use crate::{hierarchy::{Hierarchy, NodeExtension, SpriteExtension, CameraExtension, RectColliderExtension, SpriteType, AffineSpriteData}, project_data::ProjectData, Selected};
pub fn draw_inspector(ui: &Ui, hierarchy: &mut Hierarchy, project_data: &mut ProjectData, selected: &mut Selected) { pub fn draw_inspector(ui: &Ui, hierarchy: &mut Hierarchy, project_data: &mut ProjectData, selected: &mut Selected) {
ui.window("Inspector") ui.window("Inspector")
@ -67,6 +67,30 @@ fn node_inspector(ui: &Ui, hierarchy: &mut Hierarchy, project_data: &mut Project
} }
} }
} }
let mut affine = !matches!(s.sprite_type, SpriteType::Normal);
if ui.checkbox("Affine Sprite", &mut affine) {
if affine {
s.sprite_type = SpriteType::Affine(AffineSpriteData::default());
} else {
s.sprite_type = SpriteType::Normal;
}
}
if let SpriteType::Affine(a) = &mut s.sprite_type {
// Rotation input
let mut rotation: f32 = a.rotation.to_num::<f32>();
imgui::Drag::new("Rotation")
.range(0.0, fixed::types::I20F12::MAX.to_num::<f32>())
.build(ui, &mut rotation);
a.rotation = fixed::types::I20F12::from_num(rotation);
// Scale input
let mut scale: [f32; 2] = [a.scale_x.to_num::<f32>(), a.scale_y.to_num::<f32>()];
imgui::Drag::new("Scale")
.range(0.0, fixed::types::I20F12::MAX.to_num::<f32>())
.build_array(ui, &mut scale);
a.scale_x = fixed::types::I20F12::from_num(scale[0]);
a.scale_y = fixed::types::I20F12::from_num(scale[1]);
}
}, },
NodeExtension::Camera(c) => { NodeExtension::Camera(c) => {
ui.checkbox("Active for Main Engine", &mut c.active_main); ui.checkbox("Active for Main Engine", &mut c.active_main);

View File

@ -1,5 +1,5 @@
use imgui::{Ui, ImColor32}; use imgui::{Ui, ImColor32};
use crate::{hierarchy::{Hierarchy, NodeExtension}, project_data::ProjectData, Selected}; use crate::{hierarchy::{Hierarchy, NodeExtension, SpriteType}, project_data::ProjectData, Selected};
const DS_SCREEN_X: u32 = 256; const DS_SCREEN_X: u32 = 256;
const DS_SCREEN_Y: u32 = 192; const DS_SCREEN_Y: u32 = 192;
@ -87,9 +87,21 @@ impl WorldEditor {
NodeExtension::Sprite(s) => { NodeExtension::Sprite(s) => {
if let Some(asset) = project_data.graphical_assets.get(&s.graphic_asset) { if let Some(asset) = project_data.graphical_assets.get(&s.graphic_asset) {
let (width, height) = asset.size.to_dimensions(); let (width, height) = asset.size.to_dimensions();
let (width, height) = (width as f32, height as f32);
let p_min = node_canvas_pos; let p_min = node_canvas_pos;
let p_max = [p_min[0] + width as f32, p_min[1] + height as f32]; let p_max = [p_min[0] + width, p_min[1] + height];
draw_list.add_image(asset.texture.unwrap(), p_min, p_max).build(); if let SpriteType::Affine(a) = s.sprite_type {
let (width, height) = (width * a.scale_x.to_num::<f32>(), height * a.scale_y.to_num::<f32>());
let p_max = [p_min[0] + width, p_min[1] + height];
let center = [p_min[0] + (width / 2.0), p_min[1] + (height / 2.0)];
let top_left = rotate_point(p_min, center, a.rotation.to_num::<f32>());
let top_right = rotate_point([p_max[0], p_min[1]], center, a.rotation.to_num::<f32>());
let bottom_left = rotate_point([p_min[0], p_max[1]], center, a.rotation.to_num::<f32>());
let bottom_right = rotate_point(p_max, center, a.rotation.to_num::<f32>());
draw_list.add_image_quad(asset.texture.unwrap(), top_left, top_right, bottom_right, bottom_left).build();
} else {
draw_list.add_image(asset.texture.unwrap(), p_min, p_max).build();
}
if node_selected { if node_selected {
draw_selected_rect_around(draw_list, p_min, p_max); draw_selected_rect_around(draw_list, p_min, p_max);
} }
@ -140,6 +152,19 @@ impl WorldEditor {
} }
} }
fn rotate_point(point: [f32; 2], center: [f32; 2], radians: f32) -> [f32; 2] {
let sin = radians.sin();
let cos = radians.cos();
let adj_x = point[0] - center[0];
let adj_y = point[1] - center[1];
let rot_x = adj_x * cos - adj_y * sin;
let rot_y = adj_x * sin + adj_y * cos;
[rot_x + center[0], rot_y + center[1]]
}
fn draw_selected_rect_around(draw_list: &imgui::DrawListMut, top_left: [f32; 2], bottom_right: [f32; 2]) { fn draw_selected_rect_around(draw_list: &imgui::DrawListMut, top_left: [f32; 2], bottom_right: [f32; 2]) {
let top_left = top_left.map(|x| x - SELECTED_OUTLINE_THICKNESS); let top_left = top_left.map(|x| x - SELECTED_OUTLINE_THICKNESS);
let bottom_right = bottom_right.map(|x| x + SELECTED_OUTLINE_THICKNESS); let bottom_right = bottom_right.map(|x| x + SELECTED_OUTLINE_THICKNESS);

View File

@ -7,6 +7,7 @@ edition = "2021"
ironds = { git = "https://github.com/QuinnPainter/ironds", features = ["arm9"] } ironds = { git = "https://github.com/QuinnPainter/ironds", features = ["arm9"] }
sandstone_common = { path = "../../common" } sandstone_common = { path = "../../common" }
fixed = "1.23" fixed = "1.23"
cordic = "0.1.5"
[profile.dev] [profile.dev]
opt-level = 3 opt-level = 3

View File

@ -46,6 +46,7 @@ impl NodeExtensionPools {
NodeExtensionHandle::Sprite(self.sprite_pool.add(sprite::SpriteExtension { NodeExtensionHandle::Sprite(self.sprite_pool.add(sprite::SpriteExtension {
node_handle, node_handle,
graphic_asset: s.graphic_asset.clone(), graphic_asset: s.graphic_asset.clone(),
sprite_type: s.sprite_type,
})) }))
}, },
sandstone_common::SavedNodeExtension::Camera(c) => { sandstone_common::SavedNodeExtension::Camera(c) => {

View File

@ -1,5 +1,6 @@
use crate::{pool::Handle, node::{Node, camera::{ActiveCameras, CameraExtension}}, hierarchy::Hierarchy, HashMap}; use crate::{pool::Handle, node::{Node, camera::{ActiveCameras, CameraExtension}}, hierarchy::Hierarchy, HashMap};
use alloc::string::String; use alloc::string::String;
use fixed::types::*;
use ironds::display::{obj, GfxEngine}; use ironds::display::{obj, GfxEngine};
use sandstone_common::{SavedGameData, SpriteSize}; use sandstone_common::{SavedGameData, SpriteSize};
@ -10,6 +11,7 @@ const SIZEOF_TILE: usize = (8 * 8) / 2;
pub struct SpriteExtension { pub struct SpriteExtension {
pub node_handle: Handle<Node>, pub node_handle: Handle<Node>,
pub graphic_asset: String, pub graphic_asset: String,
pub sprite_type: sandstone_common::SavedSpriteType,
} }
pub(crate) struct SpriteExtensionHandler { pub(crate) struct SpriteExtensionHandler {
@ -102,6 +104,7 @@ impl SpriteExtensionHandler {
let (cam_x, cam_y) = (camera_node.global_transform.x, camera_node.global_transform.y); let (cam_x, cam_y) = (camera_node.global_transform.x, camera_node.global_transform.y);
let mut cur_sprite_index = 0; let mut cur_sprite_index = 0;
let mut cur_affine_index = 0;
for sprite in hierarchy.node_ext_pools.sprite_pool.iter() { for sprite in hierarchy.node_ext_pools.sprite_pool.iter() {
let node = hierarchy.object_pool.borrow(sprite.node_handle); let node = hierarchy.object_pool.borrow(sprite.node_handle);
if node.global_enabled == false { continue; } if node.global_enabled == false { continue; }
@ -111,27 +114,61 @@ impl SpriteExtensionHandler {
let screen_x = node.global_transform.x - cam_x; let screen_x = node.global_transform.x - cam_x;
let screen_y = node.global_transform.y - cam_y; let screen_y = node.global_transform.y - cam_y;
if screen_y < 192 && screen_y > -64 && screen_x < 256 && screen_x > -128 { if !(screen_y < 192 && screen_y > -64 && screen_x < 256 && screen_x > -128) {
let screen_x = (screen_x.to_num::<i32>() & 0x1FF) as u16; continue;
let screen_y = (screen_y.to_num::<i32>() & 0xFF) as u8;
obj::set_sprite(engine, cur_sprite_index, obj::Sprite::NormalSprite(obj::NormalSprite::new()
.with_x(screen_x)
.with_y(screen_y)
.with_disable(false)
.with_h_flip(false)
.with_v_flip(false)
.with_mode(0) // Normal mode
.with_mosaic(false)
.with_palette_type(false) // 16/16
.with_shape(shape) // square
.with_size(size) // 8x8
.with_tile(vram_mapping.tile_index)
.with_priority(0)
.with_palette(vram_mapping.pal_index)
));
cur_sprite_index += 1;
} }
let screen_x = (screen_x.to_num::<i32>() & 0x1FF) as u16;
let screen_y = (screen_y.to_num::<i32>() & 0xFF) as u8;
match sprite.sprite_type {
sandstone_common::SavedSpriteType::Normal => {
obj::set_sprite(engine, cur_sprite_index, obj::Sprite::NormalSprite(obj::NormalSprite::new()
.with_x(screen_x)
.with_y(screen_y)
.with_disable(false)
.with_h_flip(false)
.with_v_flip(false)
.with_mode(0) // Normal mode
.with_mosaic(false)
.with_palette_type(false) // 16/16
.with_shape(shape)
.with_size(size)
.with_tile(vram_mapping.tile_index)
.with_priority(0)
.with_palette(vram_mapping.pal_index)
));
}
sandstone_common::SavedSpriteType::Affine(affine) => {
// Construct an affine transformation matrix for rotation and scale:
// |pa, pb| = |cos(angle) / xscale, -sin(angle) / xscale|
// |pc, pd| |sin(angle) / yscale, cos(angle) / yscale |
// https://www.coranac.com/tonc/text/affobj.htm
let (sin, cos) = cordic::sin_cos(-affine.rotation);
obj::set_affine_param(engine, cur_affine_index, obj::AffineParameter {
pa: I8F8::from_num(cos / affine.scale_x),
pb: I8F8::from_num(-sin / affine.scale_x),
pc: I8F8::from_num(sin / affine.scale_y),
pd: I8F8::from_num(cos / affine.scale_y),
});
obj::set_sprite(engine, cur_sprite_index, obj::Sprite::AffineSprite(obj::AffineSprite::new()
.with_x(screen_x)
.with_y(screen_y)
.with_double_size(true)
.with_mode(0)
.with_mosaic(false)
.with_palette_type(false)
.with_shape(shape)
.with_size(size)
.with_tile(vram_mapping.tile_index)
.with_priority(0)
.with_palette(vram_mapping.pal_index)
.with_affine_param(cur_affine_index)
));
cur_affine_index += 1;
}
}
cur_sprite_index += 1;
} }
for i in cur_sprite_index..128 { for i in cur_sprite_index..128 {
obj::set_sprite(engine, i, obj::DISABLED_SPRITE); obj::set_sprite(engine, i, obj::DISABLED_SPRITE);

View File

@ -36,6 +36,17 @@
), ),
node_extension: Sprite(( node_extension: Sprite((
graphic_asset: "playerShip2_small", graphic_asset: "playerShip2_small",
sprite_type: Affine((
rotation: (
bits: 4096,
),
scale_x: (
bits: 8192,
),
scale_y: (
bits: 4096,
),
)),
)), )),
script_type_id: Some(1), script_type_id: Some(1),
enabled: true, enabled: true,
@ -119,6 +130,7 @@
), ),
node_extension: Sprite(( node_extension: Sprite((
graphic_asset: "enemyRed3", graphic_asset: "enemyRed3",
sprite_type: Normal,
)), )),
script_type_id: None, script_type_id: None,
enabled: true, enabled: true,
@ -138,6 +150,7 @@
), ),
node_extension: Sprite(( node_extension: Sprite((
graphic_asset: "enemyRed3", graphic_asset: "enemyRed3",
sprite_type: Normal,
)), )),
script_type_id: None, script_type_id: None,
enabled: true, enabled: true,
@ -157,6 +170,7 @@
), ),
node_extension: Sprite(( node_extension: Sprite((
graphic_asset: "enemyRed3", graphic_asset: "enemyRed3",
sprite_type: Normal,
)), )),
script_type_id: None, script_type_id: None,
enabled: true, enabled: true,
@ -225,6 +239,7 @@
), ),
node_extension: Sprite(( node_extension: Sprite((
graphic_asset: "laserBlue04", graphic_asset: "laserBlue04",
sprite_type: Normal,
)), )),
script_type_id: Some(2), script_type_id: Some(2),
enabled: true, enabled: true,
@ -258,20 +273,20 @@
], ],
graphical_assets: { graphical_assets: {
"laserBlue04": ( "laserBlue04": (
path: "assets/laserBlue04.png", path: "assets\\laserBlue04.png",
size: _8x16, size: _8x16,
), ),
"enemyRed3": ( "enemyRed3": (
path: "assets/enemyRed3.png", path: "assets\\enemyRed3.png",
size: _32x32,
),
"playerShip2_small": (
path: "assets/playerShip2_small.png",
size: _32x32, size: _32x32,
), ),
"playerShip2_blue": ( "playerShip2_blue": (
path: "assets/playerShip2_blue.png", path: "assets\\playerShip2_blue.png",
size: _64x64, size: _64x64,
), ),
"playerShip2_small": (
path: "assets\\playerShip2_small.png",
size: _32x32,
),
}, },
) )