Skip to content

Instantly share code, notes, and snippets.

@forrestthewoods
Created May 21, 2024 02:30
Show Gist options
  • Save forrestthewoods/bfecfdb96125f8e66ad2d98bf7d49b94 to your computer and use it in GitHub Desktop.
Save forrestthewoods/bfecfdb96125f8e66ad2d98bf7d49b94 to your computer and use it in GitHub Desktop.
SIQuantity :: struct($DataType: Type,
$LengthNum: int, $LengthDenom: int, $LengthExp: int,
$MassNum: int, $MassDenom: int, $MassExp: int,
$TimeNum: int, $TimeDenom: int, $TimeExp: int)
{
amount : DataType;
}
Meters :: #bake_arguments SIQuantity(
LengthNum=1, LengthDenom=1, LengthExp=1,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=0, TimeDenom=0, TimeExp=0);
Seconds :: #bake_arguments SIQuantity(
LengthNum=0, LengthDenom=0, LengthExp=0,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=1, TimeDenom=1, TimeExp=1);
Meters2 :: #bake_arguments SIQuantity(
LengthNum=1, LengthDenom=1, LengthExp=2,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=0, TimeDenom=0, TimeExp=0);
MetersPerSecond :: #bake_arguments SIQuantity(
LengthNum=1, LengthDenom=1, LengthExp=1,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=1, TimeDenom=1, TimeExp=-1);
MetersPerSecond2 :: #bake_arguments SIQuantity(
LengthNum=1, LengthDenom=1, LengthExp=1,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=1, TimeDenom=1, TimeExp=-2);
Meters2PerSecond2 :: #bake_arguments SIQuantity(
LengthNum=1, LengthDenom=1, LengthExp=2,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=1, TimeDenom=1, TimeExp=-2);
Kilometers :: #bake_arguments SIQuantity(
LengthNum=1000, LengthDenom=1, LengthExp=1,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=0, TimeDenom=0, TimeExp=0);
KilometersPerSecond :: #bake_arguments SIQuantity(
LengthNum=1000, LengthDenom=1, LengthExp=1,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=1, TimeDenom=1, TimeExp=-1);
KilometersPerSecond2 :: #bake_arguments SIQuantity(
LengthNum=1000, LengthDenom=1, LengthExp=1,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=1, TimeDenom=1, TimeExp=-2);
find_param :: (ti: *Type_Info_Struct, name: string, $T: Type) -> T {
placeholder: T;
for param: ti.specified_parameters {
if param.name == name {
if param.type != type_info(T) {
print("\nFATAL ERROR: find_param(%, %) expected type [%] but found [%]\n", ti.name, name, T, param.type.type);
assert(false);
return placeholder;
}
value := (cast(*T) *ti.constant_storage[param.offset_into_constant_storage]).*;
return value;
}
}
print("FATAL ERROR: Failed to find parameter [%]\n", name);
assert(false);
return placeholder;
}
operator + :: (a:$T/SIQuantity, b:T) -> T
{
result : T;
result.amount = a.amount + b.amount;
return result;
}
operator - :: (a:$T/SIQuantity, b:T) -> T
{
result : T;
result.amount = a.amount - b.amount;
return result;
}
operator * :: (a:$T/SIQuantity, b:$U/SIQuantity) -> $RET
#modify {
// Extract polymorphic constants from TypeInfo
ti1 := cast(*Type_Info_Struct)(T);
datatype_a := find_param(ti1, "DataType", Type);
ln1 := find_param(ti1, "LengthNum", int);
ld1 := find_param(ti1, "LengthDenom", int);
le1 := find_param(ti1, "LengthExp", int);
mn1 := find_param(ti1, "MassNum", int);
md1 := find_param(ti1, "MassDenom", int);
me1 := find_param(ti1, "MassExp", int);
tn1 := find_param(ti1, "TimeNum", int);
td1 := find_param(ti1, "TimeDenom", int);
te1 := find_param(ti1, "TimeExp", int);
ti2 := cast(*Type_Info_Struct)(U);
datatype_b := find_param(ti2, "DataType", Type);
ln2 := find_param(ti2, "LengthNum", int);
ld2 := find_param(ti2, "LengthDenom", int);
le2 := find_param(ti2, "LengthExp", int);
mn2 := find_param(ti2, "MassNum", int);
md2 := find_param(ti2, "MassDenom", int);
me2 := find_param(ti2, "MassExp", int);
tn2 := find_param(ti2, "TimeNum", int);
td2 := find_param(ti2, "TimeDenom", int);
te2 := find_param(ti2, "TimeExp", int);
// Ensure we aren't mixing storage types
if datatype_a != datatype_b {
msg := tprint("\nFATAL ERROR: Quantities must have same DataType.\n T: [%]\n U: [%]", T, U);
return false, msg;
}
// Ratios must be same OR one must be zero
ln_ok := ln1 == ln2 || ln1 == 0 || ln2 == 0;
ld_ok := ld1 == ld2 || ld1 == 0 || ld2 == 0;
mn_ok := mn1 == mn2 || mn1 == 0 || mn2 == 0;
md_ok := md1 == md2 || md1 == 0 || md2 == 0;
tn_ok := tn1 == tn2 || tn1 == 0 || tn2 == 0;
td_ok := td1 == td2 || td1 == 0 || td2 == 0;
if !ln_ok || !ld_ok || !mn_ok || !md_ok || !tn_ok || !td_ok {
msg := tprint("\nFATAL ERROR: Incompatible ratios. \n [%] \n [%]\n", T, U);
return false, msg;
}
// Compute new exponents
// If exponent is 0 then reduce ratio to 0
le := le1 + le2;
ln := ifx le == 0 then 0 else ln1|ln2;
ld := ifx le == 0 then 0 else ld1|ld2;
me := me1 + me2;
mn := ifx me == 0 then 0 else mn1|mn2;
md := ifx me == 0 then 0 else md1|md2;
te := te1 + te2;
tn := ifx te == 0 then 0 else tn1|tn2;
td := ifx te == 0 then 0 else td1|td2;
// Compute final return type
RET = #dynamic_specialize SIQuantity(
DataType=datatype_a,
LengthNum=ln, LengthDenom=ld, LengthExp=le,
MassNum=mn, MassDenom=md, MassExp=me,
TimeNum=tn, TimeDenom=td, TimeExp=te);
return true;
}
{
result : RET;
result.amount = a.amount * b.amount;
return result;
}
operator / :: (a:$T/SIQuantity, b:$U/SIQuantity) -> $RET
#modify {
ti1 := cast(*Type_Info_Struct)(T);
datatype_a := find_param(ti1, "DataType", Type);
ln1 := find_param(ti1, "LengthNum", int);
ld1 := find_param(ti1, "LengthDenom", int);
le1 := find_param(ti1, "LengthExp", int);
mn1 := find_param(ti1, "MassNum", int);
md1 := find_param(ti1, "MassDenom", int);
me1 := find_param(ti1, "MassExp", int);
tn1 := find_param(ti1, "TimeNum", int);
td1 := find_param(ti1, "TimeDenom", int);
te1 := find_param(ti1, "TimeExp", int);
ti2 := cast(*Type_Info_Struct)(U);
datatype_b := find_param(ti2, "DataType", Type);
ln2 := find_param(ti2, "LengthNum", int);
ld2 := find_param(ti2, "LengthDenom", int);
le2 := find_param(ti2, "LengthExp", int);
mn2 := find_param(ti2, "MassNum", int);
md2 := find_param(ti2, "MassDenom", int);
me2 := find_param(ti2, "MassExp", int);
tn2 := find_param(ti2, "TimeNum", int);
td2 := find_param(ti2, "TimeDenom", int);
te2 := find_param(ti2, "TimeExp", int);
if datatype_a != datatype_b {
msg := tprint("\nFATAL ERROR: Quantities must have same DataType.\n T: [%]\n U: [%]", T, U);
return false, msg;
}
// Ratios must be same OR one must be zero
ln_ok := ln1 == ln2 || ln1 == 0 || ln2 == 0;
ld_ok := ld1 == ld2 || ld1 == 0 || ld2 == 0;
mn_ok := mn1 == mn2 || mn1 == 0 || mn2 == 0;
md_ok := md1 == md2 || md1 == 0 || md2 == 0;
tn_ok := tn1 == tn2 || tn1 == 0 || tn2 == 0;
td_ok := td1 == td2 || td1 == 0 || td2 == 0;
if !ln_ok || !ld_ok || !mn_ok || !md_ok || !tn_ok || !td_ok
{
msg := tprint("\nFATAL ERROR: Incompatible ratios. \n[%] \n[%]\n", T, U);
return false, msg;
}
// Compute new exponents
// If exponent is 0 then reduce ratio to 0
le := le1 - le2;
ln := ifx le == 0 then 0 else ln1|ln2;
ld := ifx le == 0 then 0 else ld1|ld2;
me := me1 - me2;
mn := ifx me == 0 then 0 else mn1|mn2;
md := ifx me == 0 then 0 else md1|md2;
te := te1 - te2;
tn := ifx te == 0 then 0 else tn1|tn2;
td := ifx te == 0 then 0 else td1|td2;
RET = #dynamic_specialize SIQuantity(
DataType=datatype_a,
LengthNum=ln, LengthDenom=ld, LengthExp=le,
MassNum=mn, MassDenom=md, MassExp=me,
TimeNum=tn, TimeDenom=td, TimeExp=te);
return true;
}
{
result : RET;
result.amount = a.amount / b.amount;
return result;
}
// Quantities can always be multiplied by a pure number
operator * :: (a: $T/SIQuantity, b: T.DataType) -> T #symmetric {
result : T;
result.amount = a.amount * b;
return result;
}
operator / :: (a: $T/SIQuantity, b: T.DataType) -> T {
result : T;
result.amount = a.amount / b;
return result;
}
sqrt :: (a:$T/SIQuantity) -> $RET
#modify {
ti1 := cast(*Type_Info_Struct)(T);
datatype := find_param(ti1, "DataType", Type);
ln1 := find_param(ti1, "LengthNum", int);
ld1 := find_param(ti1, "LengthDenom", int);
le1 := find_param(ti1, "LengthExp", int);
mn1 := find_param(ti1, "MassNum", int);
md1 := find_param(ti1, "MassDenom", int);
me1 := find_param(ti1, "MassExp", int);
tn1 := find_param(ti1, "TimeNum", int);
td1 := find_param(ti1, "TimeDenom", int);
te1 := find_param(ti1, "TimeExp", int);
// Exponents must be divisible by two
if le1%2 != 0 || me1 %2 != 0 || te1%2 != 0 {
print("\nFATAL ERROR: Can not sqrt non-even exponents. \n[%]\n", T);
return false;
}
// Create new exponents
le := le1 / 2;
me := me1 / 2;
te := te1 / 2;
RET = #dynamic_specialize SIQuantity(
DataType=datatype,
LengthNum=ln1, LengthDenom=ld1, LengthExp=le,
MassNum=mn1, MassDenom=md1, MassExp=me,
TimeNum=tn1, TimeDenom=td1, TimeExp=te);
return true;
}
{
result : RET;
result.amount = sqrt(a.amount);
return result;
}
calc_ballistic_range :: (
speed: MetersPerSecond(float),
gravity: MetersPerSecond2(float),
initial_height: Meters(float)
) -> Meters(float)
{
assert(speed.amount > 0.0);
assert(gravity.amount > 0.0);
assert(initial_height.amount >= 0.0);
d2r : float: cast(float)0.01745329252;
angle : float: 45.0 * d2r;
ang_cos := cos(angle);
ang_sin := sin(angle);
range : Meters(float) = (speed * ang_cos / gravity)
* (speed * ang_sin + sqrt(speed * speed * ang_sin * ang_sin + 2.0 * gravity * initial_height));
return range;
}
calc_ballistic_range2 :: (
speed: $T/SIQuantity,
gravity: $U/SIQuantity,
initial_height: $V/SIQuantity
) -> $R
#modify {
// Assume return type uses the numerator/denominator of speed
// If gravity/height have different num/denom there will be a compile error
ti := cast(*Type_Info_Struct)(T);
dt := find_param(ti, "DataType", Type);
ln := find_param(ti, "LengthNum", int);
ld := find_param(ti, "LengthDenom", int);
R = #dynamic_specialize SIQuantity(
DataType=dt,
LengthNum=ln, LengthDenom=ld, LengthExp=1,
MassNum=0, MassDenom=0, MassExp=0,
TimeNum=0, TimeDenom=0, TimeExp=0);
return true;
}
{
// Make sure units are correct
#assert T.LengthExp == 1 && T.MassExp == 0 && T.TimeExp == -1;
#assert U.LengthExp == 1 && U.MassExp == 0 && U.TimeExp == -2;
#assert V.LengthExp == 1 && V.MassExp == 0 && V.TimeExp == 0;
assert(speed.amount > 0.0);
assert(gravity.amount > 0.0);
assert(initial_height.amount >= 0.0);
d2r : speed.DataType: xx 0.01745329252;
angle :: 45.0 * d2r;
ang_cos := cos(angle);
ang_sin := sin(angle);
range := (speed * ang_cos / gravity)
* (speed * ang_sin + sqrt(speed * speed * ang_sin * ang_sin + 2.0 * gravity * initial_height));
return range;
}
main :: () {
s := MetersPerSecond(float).{5};
g := MetersPerSecond2(float).{9.8};
h := Meters(float).{1};
range := calc_ballistic_range2(s, g, h);
print("%\n", range);
s2 := KilometersPerSecond(float64).{.005};
g2 := KilometersPerSecond2(float64).{.0098};
h2 := Kilometers(float64).{0.001};
range2 := calc_ballistic_range2(s2, g2, h2);
print("%\n", range2);
}
#import "Basic";
#import "Math";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment