forked from StrafesNET/roblox-bot-player
Compare commits
8 Commits
xeno-debug
...
ratio
| Author | SHA1 | Date | |
|---|---|---|---|
|
51bbc93116
|
|||
|
8c5e80d860
|
|||
|
413e88d1ec
|
|||
|
8c5ff71af2
|
|||
|
4528e6b0c2
|
|||
|
834aa4263f
|
|||
|
98e2b72418
|
|||
|
385fa57f0a
|
@@ -5,6 +5,8 @@ use strafesnet_roblox_bot_player::{bot,head,time,graphics};
|
|||||||
use strafesnet_graphics::setup;
|
use strafesnet_graphics::setup;
|
||||||
use strafesnet_common::physics::Time as PhysicsTime;
|
use strafesnet_common::physics::Time as PhysicsTime;
|
||||||
|
|
||||||
|
mod ratio;
|
||||||
|
|
||||||
// Hack to keep the code compiling,
|
// Hack to keep the code compiling,
|
||||||
// SurfaceTarget::Canvas is not available in IDE for whatever reason.
|
// SurfaceTarget::Canvas is not available in IDE for whatever reason.
|
||||||
struct ToSurfaceTarget(web_sys::HtmlCanvasElement);
|
struct ToSurfaceTarget(web_sys::HtmlCanvasElement);
|
||||||
@@ -141,9 +143,10 @@ impl PlaybackHead{
|
|||||||
self.head.set_paused(time,paused);
|
self.head.set_paused(time,paused);
|
||||||
}
|
}
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_scale(&mut self,time:f64,scale_num:i64,scale_den:u64){
|
pub fn set_scale(&mut self,time:f64,scale:f64){
|
||||||
let time=time::from_float(time).unwrap();
|
let time=time::from_float(time).unwrap();
|
||||||
self.head.set_scale(time,strafesnet_common::integer::Ratio64::new(scale_num,scale_den).unwrap());
|
let scale=crate::ratio::ratio_from_float(scale).unwrap();
|
||||||
|
self.head.set_scale(time,scale);
|
||||||
}
|
}
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn seek_to(&mut self,time:f64,new_time:f64){
|
pub fn seek_to(&mut self,time:f64,new_time:f64){
|
||||||
|
|||||||
130
wasm-module/src/ratio.rs
Normal file
130
wasm-module/src/ratio.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
use strafesnet_common::integer::Ratio64;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RatioFromFloatError{
|
||||||
|
Nan,
|
||||||
|
Overflow,
|
||||||
|
Underflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn f64_into_parts(f: f64) -> (u64, i16, i8) {
|
||||||
|
let bits: u64 = f.to_bits();
|
||||||
|
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
|
||||||
|
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
|
||||||
|
let mantissa = if exponent == 0 {
|
||||||
|
(bits & 0xfffffffffffff) << 1
|
||||||
|
} else {
|
||||||
|
(bits & 0xfffffffffffff) | 0x10000000000000
|
||||||
|
};
|
||||||
|
// Exponent bias + mantissa shift
|
||||||
|
exponent -= 1023 + 52;
|
||||||
|
(mantissa, exponent, sign)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ratio_from_float(value:f64)->Result<Ratio64,RatioFromFloatError>{
|
||||||
|
// Handle special values first
|
||||||
|
match value.classify(){
|
||||||
|
core::num::FpCategory::Nan=>return Err(RatioFromFloatError::Nan),
|
||||||
|
core::num::FpCategory::Zero=>return Ok(Ratio64::ZERO),
|
||||||
|
core::num::FpCategory::Subnormal
|
||||||
|
|core::num::FpCategory::Normal
|
||||||
|
|core::num::FpCategory::Infinite=>{
|
||||||
|
if value<i64::MIN as f64{
|
||||||
|
return Err(RatioFromFloatError::Underflow);
|
||||||
|
}
|
||||||
|
if (i64::MAX as f64)<value{
|
||||||
|
return Err(RatioFromFloatError::Overflow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// value = sign * mantissa * 2 ^ exponent
|
||||||
|
let (mantissa,exponent,sign)=f64_into_parts(value);
|
||||||
|
|
||||||
|
if 0<=exponent{
|
||||||
|
// we know value < i64::MIN, so it must just be an integer.
|
||||||
|
return Ok(Ratio64::new((sign as i64*mantissa as i64)<<exponent,1).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if exponent< -62-52{
|
||||||
|
// very small number is not representable
|
||||||
|
return Ok(Ratio64::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
if -63<exponent{
|
||||||
|
// exactly representable
|
||||||
|
return Ok(Ratio64::new(sign as i64*mantissa as i64,1<<-exponent).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the denominator is necessarily bigger than the numerator at this point.
|
||||||
|
|
||||||
|
// create exact float num/den ratio
|
||||||
|
let mut in_num=mantissa as u128;
|
||||||
|
let mut in_den=1u128<<-exponent;
|
||||||
|
|
||||||
|
// a+b*c
|
||||||
|
fn ma(a:i64,b:i64,c:i64)->Option<i64>{
|
||||||
|
a.checked_add(b.checked_mul(c)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut p_prev=1i64;
|
||||||
|
let mut q_prev=0i64;
|
||||||
|
let mut p_cur=0i64;
|
||||||
|
let mut q_cur=1i64;
|
||||||
|
|
||||||
|
// compute continued fraction
|
||||||
|
loop{
|
||||||
|
let whole=in_den/in_num;
|
||||||
|
|
||||||
|
if whole==0{
|
||||||
|
// we have depleted the input fraction and created an exact representation
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i64::MAX as u128)<whole{
|
||||||
|
// cannot continue fraction
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(p_next)=ma(p_prev,p_cur,whole as i64)else{
|
||||||
|
// overflow
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let Some(q_next)=ma(q_prev,q_cur,whole as i64)else{
|
||||||
|
// overflow
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
p_prev=p_cur;
|
||||||
|
q_prev=q_cur;
|
||||||
|
p_cur=p_next;
|
||||||
|
q_cur=q_next;
|
||||||
|
(in_num,in_den)=(in_den-in_num*whole,in_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Ratio64::new(p_cur,q_cur as u64).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn req(r0:Ratio64,r1:Ratio64){
|
||||||
|
println!("r0={r0:?} r1={r1:?}");
|
||||||
|
assert_eq!(r0.num(),r1.num(),"Nums not eq");
|
||||||
|
assert_eq!(r0.den(),r1.den(),"Dens not eq");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test(){
|
||||||
|
req(ratio_from_float(2.0).unwrap(),Ratio64::new(2,1).unwrap());
|
||||||
|
req(ratio_from_float(1.0).unwrap(),Ratio64::new(1,1).unwrap());
|
||||||
|
req(ratio_from_float(0.5).unwrap(),Ratio64::new(1,2).unwrap());
|
||||||
|
req(ratio_from_float(1.1).unwrap(),Ratio64::new(2476979795053773,2251799813685248).unwrap());
|
||||||
|
req(ratio_from_float(0.8).unwrap(),Ratio64::new(3602879701896397,4503599627370496).unwrap());
|
||||||
|
req(ratio_from_float(0.61).unwrap(),Ratio64::new(5494391545392005,9007199254740992).unwrap());
|
||||||
|
req(ratio_from_float(0.01).unwrap(),Ratio64::new(5764607523034235,576460752303423488).unwrap());
|
||||||
|
req(ratio_from_float(0.001).unwrap(),Ratio64::new(1152921504606847,1152921504606846976).unwrap());
|
||||||
|
req(ratio_from_float(0.00001).unwrap(),Ratio64::new(89605456633725,8960545663372499267).unwrap());
|
||||||
|
req(ratio_from_float(0.00000000001).unwrap(),Ratio64::new(35204848,3520484800000000213).unwrap());
|
||||||
|
req(ratio_from_float(0.000000000000000001).unwrap(),Ratio64::new(2,1999999999999999857).unwrap());
|
||||||
|
req(ratio_from_float(2222222222222.0).unwrap(),Ratio64::new(2222222222222,1).unwrap());
|
||||||
|
req(ratio_from_float(core::f64::consts::PI).unwrap(),Ratio64::new(884279719003555,281474976710656).unwrap());
|
||||||
|
}
|
||||||
@@ -44,20 +44,11 @@ function elapsed() {
|
|||||||
const control_speed = document.getElementById("control_speed");
|
const control_speed = document.getElementById("control_speed");
|
||||||
|
|
||||||
var paused = false;
|
var paused = false;
|
||||||
var speed = 0;
|
var scale = 1;
|
||||||
function set_speed(new_speed) {
|
function set_scale(new_scale) {
|
||||||
speed = new_speed;
|
scale = new_scale;
|
||||||
var speed_num = null;
|
playback.set_scale(elapsed(), scale);
|
||||||
var speed_den = null;
|
control_speed.value = `${scale.toPrecision(3)}x`;
|
||||||
if (new_speed < 0) {
|
|
||||||
speed_num = BigInt(4) ** BigInt(-new_speed);
|
|
||||||
speed_den = BigInt(5) ** BigInt(-new_speed);
|
|
||||||
} else {
|
|
||||||
speed_num = BigInt(5) ** BigInt(new_speed);
|
|
||||||
speed_den = BigInt(4) ** BigInt(new_speed);
|
|
||||||
}
|
|
||||||
playback.set_scale(elapsed(), speed_num, speed_den);
|
|
||||||
control_speed.value = `${((5 / 4) ** new_speed).toPrecision(3)}x`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controls
|
// Controls
|
||||||
@@ -75,25 +66,24 @@ document.getElementById("control_backward").addEventListener("click", (e) => {
|
|||||||
playback.seek_backward(2.0);
|
playback.seek_backward(2.0);
|
||||||
});
|
});
|
||||||
document.getElementById("control_slower").addEventListener("click", (e) => {
|
document.getElementById("control_slower").addEventListener("click", (e) => {
|
||||||
set_speed(Math.max(speed - 1, -27));
|
set_scale((scale * 4) / 5);
|
||||||
});
|
});
|
||||||
const regex = new RegExp("^([^x]*)x?$");
|
const regex = new RegExp("^([^x]*)x?$");
|
||||||
control_speed.addEventListener("change", (e) => {
|
control_speed.addEventListener("change", (e) => {
|
||||||
const parsed = regex.exec(e.target.value);
|
const parsed = regex.exec(e.target.value);
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
set_speed(0);
|
set_scale(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const input = Number(parsed.at(1));
|
const input = Number(parsed.at(1));
|
||||||
if (Number.isNaN(input)) {
|
if (Number.isNaN(input)) {
|
||||||
set_speed(0);
|
set_scale(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rounded = Math.round(Math.log(input) / Math.log(5 / 4));
|
set_scale(input);
|
||||||
set_speed(Math.max(-27, Math.min(27, rounded)));
|
|
||||||
});
|
});
|
||||||
document.getElementById("control_faster").addEventListener("click", (e) => {
|
document.getElementById("control_faster").addEventListener("click", (e) => {
|
||||||
set_speed(Math.min(speed + 1, 27));
|
set_scale((scale * 5) / 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
|
|||||||
Reference in New Issue
Block a user