Easing function tool

stinkhead7ds
Ashley N. 2023-10-21 20:40:50 -04:00
parent 1ae417af5c
commit 9e1014bbd9
5 changed files with 109 additions and 2 deletions

View File

@ -19,4 +19,6 @@ linked-hash-map = "0.5.6"
convert_case = "0.6.0"
roxmltree = "0.18.0"
regex = "1.9.5"
euclid = "0.22.9"
euclid = "0.22.9"
flo_curves = "0.7.2"
fixed = "1.24.0"

View File

@ -1,6 +1,6 @@
use std::{error::Error, fs::File, io::Write, path::Path, collections::HashMap};
use clap::Parser;
use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::print_good, level::{converter::get_tiled_tilemap, system::{get_tiles, get_code, get_tilemap, get_collision_map, get_objs, get_sprites}}};
use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::print_good, level::{converter::get_tiled_tilemap, system::{get_tiles, get_code, get_tilemap, get_collision_map, get_objs, get_sprites}}, easing::get_cubic_bezier};
use super::settings::{Args, Tools, TileOutputFormat, TileOrder};
pub fn run_command() -> Result<(), Box<dyn Error>> {
@ -129,6 +129,25 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
code_asm.write_all( &get_code( &tiled_file, "testlevel", "levels/" )?.as_bytes() )?;
print_good( "exported level.asm" );
}
Tools::Easing { output_directory, interval, point_1, point_2 } => {
let cp1: Vec<&str> = point_1.split( "," ).collect();
if cp1.len() != 2 {
return Err( format!( "invalid format for point_1: {}", point_1 ) )?;
}
let ( cp1_x, cp1_y ): ( f64, f64 ) = ( cp1[ 0 ].parse()?, cp1[ 1 ].parse()? );
let cp2: Vec<&str> = point_1.split( "," ).collect();
if cp2.len() != 2 {
return Err( format!( "invalid format for point_2: {}", point_2 ) )?;
}
let ( cp2_x, cp2_y ): ( f64, f64 ) = ( cp2[ 0 ].parse()?, cp2[ 1 ].parse()? );
let mut easing = File::create( format!( "{}curve.bin", output_directory ) )?;
easing.write_all( &get_cubic_bezier( ( 0.0, 0.0 ), ( cp1_x, cp1_y ), ( cp2_x, cp2_y ), ( 1.0, 1.0 ), interval )? )?;
print_good( "exported curve.bin" );
}
};
Ok( () )

View File

@ -124,5 +124,27 @@ pub enum Tools {
/// Console system type
#[arg(short, long, value_enum, default_value_t=SystemType::Md)]
console: SystemType
},
#[command(name = "easing")]
#[command(about = "Generate an easing curve given a time interval and control points.")]
Easing {
/// Output directory for artifacts
#[arg(short, long, default_value_t=String::from("./"))]
output_directory: String,
/// Time span of the easing curve (in 1/60 s increments)
#[arg(short,long)]
interval: u16,
/// First control point of the cubic bezier (format: <x>,<y>)
#[arg(long)]
point_1: String,
/// Second control point of the cubic bezier (format: <x>,<y>)
#[arg(long)]
point_2: String
}
}

63
src/reskit/easing.rs Normal file
View File

@ -0,0 +1,63 @@
use std::error::Error;
use fixed::FixedI32;
use fixed::types::extra::U16;
use flo_curves::*;
use flo_curves::{bezier, Coord2};
use super::utility::print_info;
/*
stinkhead7ds easing curve description:
e.g. going from 120 to 140
easing function is cubic bezier going from 0.0 to 1.0
140-120=20 increments
0.0 is +0 and 1.0 is +20
so e.g. if you read a 0.5 at the given increment it's 0.5 * 20 = +10
curves are exported from reskit sampled at 1/60 increments
reskit accepts arguments for control points and exports 16.16 fixed point binary files
so a 1 second animation is 60 increments, 2 seconds is 120, etc.
each iteration be like:
1. take cubic bezier sample and post increment
2. the value at this iteration is (final - beginning) * sample
a. so example (140-120)*0.5 for a linear curve sampled right in the middle
3. round the value to the nearest whole integer
4. do not go back to 1 if we exhausted the curve
need fixed point arithmetic multiplication
*/
/**
* Output a binary for a cubic bezier defined by the given four points and the
* time interval defined as number of 1/60 jiffies.
*/
pub fn get_cubic_bezier( p0: (f64, f64), c0: (f64, f64), c1: (f64, f64), p1: (f64, f64), jiffies: u16 ) -> Result<Vec<u8>, Box<dyn Error>> {
let mut result: Vec<u8> = Vec::new();
// 2 bytes: Number of frames this curve spans
result.extend( jiffies.to_be_bytes() );
let curve = bezier::Curve::from_points(
Coord2( p0.0, p0.1 ),
( Coord2( c0.0, c0.1 ), Coord2( c1.0, c1.1 ) ),
Coord2( p1.0, p1.1 )
);
let mut current_jiffy = 0.0;
// (jiffies) of 16.16 fixed point integers: Each point in the curve at the given frame
for i in 0..jiffies {
let point = curve.point_at_pos( current_jiffy );
let x = point.1;
print_info( &format!( "at iteration {}, curve is {:?}, 16.16 format will be {}", i, point, x ) );
current_jiffy += 1.0 / jiffies as f64;
let as_fixed = FixedI32::<U16>::from_num( x );
result.extend( as_fixed.to_be_bytes() );
}
Ok( result )
}

View File

@ -1,4 +1,5 @@
pub mod cli;
pub mod easing;
pub mod level;
pub mod soundtrack;
pub mod tileset;