mirror of
https://github.com/QuinnPainter/sandstone.git
synced 2025-06-19 03:25:37 -04:00
Add affine sprite support
This commit is contained in:
parent
6db7d7e513
commit
8ca5cb048c
@ -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)]
|
||||
pub struct SavedSpriteExtension {
|
||||
pub graphic_asset: String,
|
||||
pub sprite_type: SavedSpriteType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@ -87,7 +101,7 @@ pub enum SavedNodeExtension {
|
||||
RectCollider(SavedRectColliderExtension),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct SavedTransform {
|
||||
pub x: fixed::types::I20F12,
|
||||
pub y: fixed::types::I20F12,
|
||||
|
@ -3,12 +3,13 @@ use imgui::{Ui, TreeNodeFlags};
|
||||
use stable_vec::StableVec;
|
||||
use crate::{project_data::ProjectData, Selected};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct Transform {
|
||||
pub x: fixed::types::I20F12,
|
||||
pub y: fixed::types::I20F12,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NodeExtension {
|
||||
None,
|
||||
Sprite(SpriteExtension),
|
||||
@ -31,7 +32,17 @@ impl NodeExtension {
|
||||
pub fn from_saved(saved_extension: sandstone_common::SavedNodeExtension) -> Self {
|
||||
match saved_extension {
|
||||
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::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 {
|
||||
match self {
|
||||
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::RectCollider(c) => sandstone_common::SavedNodeExtension::RectCollider(sandstone_common::SavedRectColliderExtension { width: c.width, height: c.height }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SpriteExtension {
|
||||
pub graphic_asset: String,
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AffineSpriteData {
|
||||
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 active_main: bool,
|
||||
pub active_sub: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct RectColliderExtension {
|
||||
pub width: fixed::types::I20F12,
|
||||
pub height: fixed::types::I20F12,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Node {
|
||||
pub child_index: Option<NonZeroUsize>,
|
||||
pub parent_index: Option<usize>,
|
||||
|
@ -1,5 +1,5 @@
|
||||
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) {
|
||||
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) => {
|
||||
ui.checkbox("Active for Main Engine", &mut c.active_main);
|
||||
|
@ -1,5 +1,5 @@
|
||||
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_Y: u32 = 192;
|
||||
@ -87,9 +87,21 @@ impl WorldEditor {
|
||||
NodeExtension::Sprite(s) => {
|
||||
if let Some(asset) = project_data.graphical_assets.get(&s.graphic_asset) {
|
||||
let (width, height) = asset.size.to_dimensions();
|
||||
let (width, height) = (width as f32, height as f32);
|
||||
let p_min = node_canvas_pos;
|
||||
let p_max = [p_min[0] + width as f32, p_min[1] + height as f32];
|
||||
draw_list.add_image(asset.texture.unwrap(), p_min, p_max).build();
|
||||
let p_max = [p_min[0] + width, p_min[1] + height];
|
||||
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 {
|
||||
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]) {
|
||||
let top_left = top_left.map(|x| x - SELECTED_OUTLINE_THICKNESS);
|
||||
let bottom_right = bottom_right.map(|x| x + SELECTED_OUTLINE_THICKNESS);
|
||||
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||
ironds = { git = "https://github.com/QuinnPainter/ironds", features = ["arm9"] }
|
||||
sandstone_common = { path = "../../common" }
|
||||
fixed = "1.23"
|
||||
cordic = "0.1.5"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
|
@ -46,6 +46,7 @@ impl NodeExtensionPools {
|
||||
NodeExtensionHandle::Sprite(self.sprite_pool.add(sprite::SpriteExtension {
|
||||
node_handle,
|
||||
graphic_asset: s.graphic_asset.clone(),
|
||||
sprite_type: s.sprite_type,
|
||||
}))
|
||||
},
|
||||
sandstone_common::SavedNodeExtension::Camera(c) => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{pool::Handle, node::{Node, camera::{ActiveCameras, CameraExtension}}, hierarchy::Hierarchy, HashMap};
|
||||
use alloc::string::String;
|
||||
use fixed::types::*;
|
||||
use ironds::display::{obj, GfxEngine};
|
||||
use sandstone_common::{SavedGameData, SpriteSize};
|
||||
|
||||
@ -10,6 +11,7 @@ const SIZEOF_TILE: usize = (8 * 8) / 2;
|
||||
pub struct SpriteExtension {
|
||||
pub node_handle: Handle<Node>,
|
||||
pub graphic_asset: String,
|
||||
pub sprite_type: sandstone_common::SavedSpriteType,
|
||||
}
|
||||
|
||||
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 mut cur_sprite_index = 0;
|
||||
let mut cur_affine_index = 0;
|
||||
for sprite in hierarchy.node_ext_pools.sprite_pool.iter() {
|
||||
let node = hierarchy.object_pool.borrow(sprite.node_handle);
|
||||
if node.global_enabled == false { continue; }
|
||||
@ -111,27 +114,61 @@ impl SpriteExtensionHandler {
|
||||
|
||||
let screen_x = node.global_transform.x - cam_x;
|
||||
let screen_y = node.global_transform.y - cam_y;
|
||||
if screen_y < 192 && screen_y > -64 && screen_x < 256 && screen_x > -128 {
|
||||
let screen_x = (screen_x.to_num::<i32>() & 0x1FF) as u16;
|
||||
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;
|
||||
if !(screen_y < 192 && screen_y > -64 && screen_x < 256 && screen_x > -128) {
|
||||
continue;
|
||||
}
|
||||
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 {
|
||||
obj::set_sprite(engine, i, obj::DISABLED_SPRITE);
|
||||
|
@ -36,6 +36,17 @@
|
||||
),
|
||||
node_extension: Sprite((
|
||||
graphic_asset: "playerShip2_small",
|
||||
sprite_type: Affine((
|
||||
rotation: (
|
||||
bits: 4096,
|
||||
),
|
||||
scale_x: (
|
||||
bits: 8192,
|
||||
),
|
||||
scale_y: (
|
||||
bits: 4096,
|
||||
),
|
||||
)),
|
||||
)),
|
||||
script_type_id: Some(1),
|
||||
enabled: true,
|
||||
@ -119,6 +130,7 @@
|
||||
),
|
||||
node_extension: Sprite((
|
||||
graphic_asset: "enemyRed3",
|
||||
sprite_type: Normal,
|
||||
)),
|
||||
script_type_id: None,
|
||||
enabled: true,
|
||||
@ -138,6 +150,7 @@
|
||||
),
|
||||
node_extension: Sprite((
|
||||
graphic_asset: "enemyRed3",
|
||||
sprite_type: Normal,
|
||||
)),
|
||||
script_type_id: None,
|
||||
enabled: true,
|
||||
@ -157,6 +170,7 @@
|
||||
),
|
||||
node_extension: Sprite((
|
||||
graphic_asset: "enemyRed3",
|
||||
sprite_type: Normal,
|
||||
)),
|
||||
script_type_id: None,
|
||||
enabled: true,
|
||||
@ -225,6 +239,7 @@
|
||||
),
|
||||
node_extension: Sprite((
|
||||
graphic_asset: "laserBlue04",
|
||||
sprite_type: Normal,
|
||||
)),
|
||||
script_type_id: Some(2),
|
||||
enabled: true,
|
||||
@ -258,20 +273,20 @@
|
||||
],
|
||||
graphical_assets: {
|
||||
"laserBlue04": (
|
||||
path: "assets/laserBlue04.png",
|
||||
path: "assets\\laserBlue04.png",
|
||||
size: _8x16,
|
||||
),
|
||||
"enemyRed3": (
|
||||
path: "assets/enemyRed3.png",
|
||||
size: _32x32,
|
||||
),
|
||||
"playerShip2_small": (
|
||||
path: "assets/playerShip2_small.png",
|
||||
path: "assets\\enemyRed3.png",
|
||||
size: _32x32,
|
||||
),
|
||||
"playerShip2_blue": (
|
||||
path: "assets/playerShip2_blue.png",
|
||||
path: "assets\\playerShip2_blue.png",
|
||||
size: _64x64,
|
||||
),
|
||||
"playerShip2_small": (
|
||||
path: "assets\\playerShip2_small.png",
|
||||
size: _32x32,
|
||||
),
|
||||
},
|
||||
)
|
Loading…
Reference in New Issue
Block a user