From 857d2eefca8127cd679c7f392190120142bf8b9d Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 24 Nov 2024 08:51:34 +0100 Subject: [PATCH] added floats.interpolate(), math.interpolate(), and LERP example --- compiler/res/prog8lib/math.p8 | 8 + .../res/prog8lib/shared_floats_functions.p8 | 16 ++ compiler/res/prog8lib/virtual/floats.p8 | 15 ++ compiler/res/prog8lib/virtual/math.p8 | 8 + compiler/test/TestCompilerOnExamples.kt | 1 + docs/source/libraries.rst | 10 +- examples/cx16/interpolation.p8 | 192 ++++++++++++++++++ examples/test.p8 | 21 +- 8 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 examples/cx16/interpolation.p8 diff --git a/compiler/res/prog8lib/math.p8 b/compiler/res/prog8lib/math.p8 index 768b9cddf..93d380b4f 100644 --- a/compiler/res/prog8lib/math.p8 +++ b/compiler/res/prog8lib/math.p8 @@ -662,4 +662,12 @@ log2_tab cx16.r0++ return v0 + cx16.r0 } + + sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte { + ; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax] + ; There is no version for words because of lack of precision in the fixed point calculation there. + cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin) + cx16.r0 *= (outputMax-outputMin) + return cx16.r0H + outputMin + } } diff --git a/compiler/res/prog8lib/shared_floats_functions.p8 b/compiler/res/prog8lib/shared_floats_functions.p8 index 5d4130371..3a8312a71 100644 --- a/compiler/res/prog8lib/shared_floats_functions.p8 +++ b/compiler/res/prog8lib/shared_floats_functions.p8 @@ -12,6 +12,14 @@ txt { alias print_f = floats.print } +math { + %option merge, ignore_unused ; add functions to math + + alias lerpf = floats.lerp + alias lerpf_fast = floats.lerp_fast + alias interpolatef = floats.interpolate +} + floats { ; the floating point functions shared across compiler targets %option merge, no_symbol_prefixing, ignore_unused @@ -286,4 +294,12 @@ sub lerp_fast(float v0, float v1, float t) -> float { return v0 + t * (v1 - v0) } +sub interpolate(float v, float inputMin, float inputMax, float outputMin, float outputMax) -> float { + ; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax] + if outputMin==outputMax + return outputMin + v = (v - inputMin) / (inputMax - inputMin) + return v * (outputMax - outputMin) + outputMin +} + } diff --git a/compiler/res/prog8lib/virtual/floats.p8 b/compiler/res/prog8lib/virtual/floats.p8 index 2412f898d..767320b23 100644 --- a/compiler/res/prog8lib/virtual/floats.p8 +++ b/compiler/res/prog8lib/virtual/floats.p8 @@ -15,6 +15,13 @@ txt { alias print_f = floats.print } +math { + %option merge, ignore_unused ; add functions to math + + alias lerpf = floats.lerp + alias lerpf_fast = floats.lerp_fast +} + floats { @@ -233,4 +240,12 @@ sub lerp_fast(float v0, float v1, float t) -> float { return v0 + t * (v1 - v0) } +sub interpolate(float v, float inputMin, float inputMax, float outputMin, float outputMax) -> float { + ; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax] + if outputMin==outputMax + return outputMin + v = (v - inputMin) / (inputMax - inputMin) + return v * (outputMax - outputMin) + outputMin +} + } diff --git a/compiler/res/prog8lib/virtual/math.p8 b/compiler/res/prog8lib/virtual/math.p8 index 2a18c7b99..da1fe106b 100644 --- a/compiler/res/prog8lib/virtual/math.p8 +++ b/compiler/res/prog8lib/virtual/math.p8 @@ -417,4 +417,12 @@ math { cx16.r0++ return v0 + cx16.r0 } + + sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte { + ; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax] + ; There is no version for words because of lack of precision in the fixed point calculation there. + cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin) + cx16.r0 *= (outputMax-outputMin) + return cx16.r0H + outputMin + } } diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 8d8887020..81bea10e6 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -132,6 +132,7 @@ class TestCompilerOnExamplesCx16: FunSpec({ "cxlogo", "diskspeed", "fileseek", + "interpolation", "kefrenbars", "keyboardhandler", "life", diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 539787f41..be5e0fb02 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -697,10 +697,13 @@ Provides definitions for the ROM/Kernal subroutines and utility routines dealing Linear interpolation (LERP). Precise method, which guarantees v = v1 when t = 1. Returns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0.0, 1.0] -``lerp_fast(v0, v1, t)``` +``lerp_fast(v0, v1, t)`` Linear interpolation (LERP). Imprecise (but faster) method, which does not guarantee v = v1 when t = 1 Teturns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0.0, 1.0] +``interpolate(v, inputMin, inputMax, outputMin, outputMax)`` + Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax] + graphics -------- @@ -857,6 +860,11 @@ but perhaps the provided ones can be of service too. Returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535] Guarantees v = v1 when t = 65535. +``interpolate(v, inputMin, inputMax, outputMin, outputMax)`` + Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax] + All values are unsigned bytes. + (there is no version for word values because of lack of precision in the fixed point calculation there). + cx16logo -------- diff --git a/examples/cx16/interpolation.p8 b/examples/cx16/interpolation.p8 new file mode 100644 index 000000000..7e0009805 --- /dev/null +++ b/examples/cx16/interpolation.p8 @@ -0,0 +1,192 @@ +%import math +%import textio +%import sprites +%import floats + +; shows the use of the LERP (Linear intERPolation) routines that prog8 provides. +; math.lerp / math.lerpw / floats.lerp / floats.lerp_fast + +; LERP explained here: https://en.wikipedia.org/wiki/Linear_interpolation +; Here are a lot of easing functions you can use: +; https://blog.febucci.com/2018/08/easing-functions/ (links at the bottom) + + +main { + uword mx, my + + sub start() { + void cx16.set_screen_mode(3) + cx16.mouse_config2(1) + sprites.set_mousepointer_hand() + + repeat { + txt.cls() + txt.print("\n\n floats.lerp()") + sys.wait(60) + repeat 2 lerp_float() + + txt.cls() + txt.print("\n\n floats.lerp() with easing") + sys.wait(60) + repeat 2 lerp_float_easing() + + txt.cls() + txt.print("\n\n integer math.lerp()") + sys.wait(60) + repeat 2 lerp_normal() + + txt.cls() + txt.print("\n\n integer math.lerp() with easing") + sys.wait(60) + repeat 2 lerp_easing() + + txt.cls() + txt.print("\n\n floats.interpolate()") + sys.wait(60) + repeat 2 interpolate_float() + + txt.cls() + txt.print("\n\n integer math.interpolate()") + sys.wait(60) + repeat 2 interpolate_int() + } + } + + sub interpolate_float() { + ubyte tt + for tt in 0 to 255 step 3 { + sys.waitvsync() + mx = floats.interpolate(tt as float, 0, 255, 50, 250) as uword + my = floats.interpolate(tt as float, 0, 255, 30, 150) as uword + cx16.mouse_set_pos(mx, my) + } + + for tt in 255 downto 0 step -3 { + sys.waitvsync() + mx = floats.interpolate(tt as float, 0, 255, 50, 250) as uword + my = floats.interpolate(tt as float, 0, 255, 30, 150) as uword + cx16.mouse_set_pos(mx, my) + } + } + + sub interpolate_int() { + ubyte tt + for tt in 50 to 250 step 3 { + sys.waitvsync() + mx = math.interpolate(tt, 50, 250, 50, 250) + my = math.interpolate(tt, 50, 250, 30, 150) + cx16.mouse_set_pos(mx, my) + } + + for tt in 250 downto 50 step -3 { + sys.waitvsync() + mx = math.interpolate(tt, 50, 250, 50, 250) + my = math.interpolate(tt, 50, 250, 30, 150) + cx16.mouse_set_pos(mx, my) + } + } + + sub lerp_float() { + ubyte tt + for tt in 0 to 255 step 3 { + sys.waitvsync() + mx = floats.lerp(50, 250, tt as float/256) as uword + my = floats.lerp(30, 150, tt as float/256) as uword + cx16.mouse_set_pos(mx, my) + } + + for tt in 255 downto 0 step -3 { + sys.waitvsync() + mx = floats.lerp(50, 250, tt as float/256) as uword + my = floats.lerp(30, 150, tt as float/256) as uword + cx16.mouse_set_pos(mx, my) + } + } + + sub lerp_float_easing() { + float e + ubyte tt + + for tt in 0 to 255 step 2 { + sys.waitvsync() + e = ease(tt) + mx = floats.lerp(50, 250, e) as uword + my = floats.lerp(30, 150, e) as uword + cx16.mouse_set_pos(mx, my) + } + + for tt in 255 downto 0 step -2 { + sys.waitvsync() + e = 1 - ease(255-tt) + mx = floats.lerp(50, 250, e) as uword + my = floats.lerp(30, 150, e) as uword + cx16.mouse_set_pos(mx, my) + } + + sub ease(ubyte t) -> float { + ; bounce + const float n1 = 7.5625 + const float d1 = 2.75 + float x = t as float/256 + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + x = x - 1.5 / d1; + return n1 * x * x + 0.75; + } else if (x < 2.5 / d1) { + x = x - 2.25 / d1; + return n1 * x * x + 0.9375; + } else { + x = x - 2.625 / d1; + return n1 * x * x + 0.984375; + } + } + + sub ease2(ubyte t) -> float { + ; 'smootherstep' + float x = t as float/256 + return x * x * x * (x * (x * 6 - 15) + 10) + } + } + + sub lerp_normal() { + ubyte tt + for tt in 0 to 255 step 3 { + sys.waitvsync() + mx = math.lerp(50, 250, tt) + my = math.lerp(30, 150, tt) + cx16.mouse_set_pos(mx, my) + } + + for tt in 255 downto 0 step -3 { + sys.waitvsync() + mx = math.lerp(50, 250, tt) + my = math.lerp(30, 150, tt) + cx16.mouse_set_pos(mx, my) + } + } + + sub lerp_easing() { + ubyte e + ubyte tt + for tt in 0 to 255 step 3 { + sys.waitvsync() + e = ease(tt) + mx = math.lerp(50, 250, e) + my = math.lerp(30, 150, e) + cx16.mouse_set_pos(mx, my) + } + + for tt in 255 downto 0 step -3 { + sys.waitvsync() + e = ease(tt) + mx = math.lerp(50, 250, e) + my = math.lerp(30, 150, e) + cx16.mouse_set_pos(mx, my) + } + + sub ease(ubyte t) -> ubyte { + return msb(t as uword*t) ; 'squaring' + } + } +} diff --git a/examples/test.p8 b/examples/test.p8 index 559b7d347..38c8054e4 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,10 +1,25 @@ +%import floats +%import math %import textio %zeropage basicsafe main { - alias print = txt.print_ub - sub start() { - print(42) + floats.print(floats.interpolate(0, 0, 10, 1000, 2000)) + txt.spc() + txt.print_uw(math.interpolate(10, 10, 20, 100, 200)) + txt.nl() + floats.print(floats.interpolate(2.22, 0, 10, 1000, 2000)) + txt.spc() + txt.print_uw(math.interpolate(12, 10, 20, 100, 200)) + txt.nl() + floats.print(floats.interpolate(5.0, 0, 10, 1000, 2000)) + txt.spc() + txt.print_uw(math.interpolate(15, 10, 20, 100, 200)) + txt.nl() + floats.print(floats.interpolate(10, 0, 10, 1000, 2000)) + txt.spc() + txt.print_uw(math.interpolate(20, 10, 20, 100, 200)) + txt.nl() } }