diff --git a/Numeric/CubicCurve.hpp b/Numeric/CubicCurve.hpp new file mode 100644 index 000000000..ad714b887 --- /dev/null +++ b/Numeric/CubicCurve.hpp @@ -0,0 +1,69 @@ +// +// CubicCurve.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/10/2025. +// Copyright © 2025 Thomas Harte. All rights reserved. +// + +#pragma once + +#include + +/*! + Provides a cubic Bezier-based timing function. +*/ +struct CubicCurve { + CubicCurve(const float c1x, const float c1y, const float c2x, const float c2y) : + c1(c1x, c1y), c2(c2x, c2y) + { + assert(0.0f <= c1x); assert(c1x <= 1.0f); + assert(0.0f <= c1y); assert(c1y <= 1.0f); + assert(0.0f <= c2x); assert(c2x <= 1.0f); + assert(0.0f <= c2y); assert(c2y <= 1.0f); + } + + /// @returns A standard ease-in-out animation curve. + static CubicCurve easeInOut() { + return CubicCurve(0.42f, 0.0f, 0.58f, 1.0f); + } + + /// @returns The value for y given x, in range [0.0, 1.0]. + float value(const float x) const { + return axis(t(x), 1); + } + +private: + /// @returns The value for @c t that generates the value @c x. + float t(const float x) const { + static constexpr float Precision = 0.01f; + float bounds[2] = {0.0f, 1.0f}; + const auto midpoint = [&] { return (bounds[0] + bounds[1]) * 0.5f; }; + + while(bounds[1] > bounds[0] + Precision) { + const float mid = midpoint(); + const float value = axis(mid, 0); + if(value > x) { + bounds[1] = mid; + } else { + bounds[0] = mid; + } + } + return midpoint(); + } + + /// @returns The value for axis @c index at time @c t. + float axis(const float t, int index) const { + const float f1 = t * c1[index]; + const float f2 = t * c2[index] + (1.0f - t) * c1[index]; + const float f3 = t + (1.0f - t) * c2[index]; + + const float c1 = t * f2 + (1.0f - t) * f1; + const float c2 = t * f3 + (1.0f - t) * f2; + + return t * c2 + (1.0f - t) * c1; + } + + float c1[2]; + float c2[2]; +}; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 0241801e9..5f250f4f4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1780,6 +1780,7 @@ 4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 6522Storage.hpp; path = Implementation/6522Storage.hpp; sourceTree = ""; }; 4B8334911F5E24FF0097E338 /* C1540Base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = C1540Base.hpp; path = Implementation/C1540Base.hpp; sourceTree = ""; }; 4B8334941F5E25B60097E338 /* C1540.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = C1540.cpp; path = Implementation/C1540.cpp; sourceTree = ""; }; + 4B847B9F2E920C7500774B9B /* CubicCurve.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = CubicCurve.hpp; path = /Users/thomasharte/Projects/CLK/Numeric/CubicCurve.hpp; sourceTree = ""; }; 4B85322922778E4200F26553 /* Comparative68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Comparative68000.hpp; sourceTree = ""; }; 4B85322E2277ABDD00F26553 /* tos100.trace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = tos100.trace.txt.gz; sourceTree = ""; }; 4B8671EA2D8B40D8009E1610 /* Descriptors.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Descriptors.hpp; sourceTree = ""; }; @@ -3147,6 +3148,7 @@ 4BD191D5219113B80042E144 /* OpenGL */, 4BB8616B24E22DC500A00E03 /* ScanTargets */, 4BD060A41FE49D3C006E14BE /* Speaker */, + 4B847B9F2E920C7500774B9B /* CubicCurve.hpp */, ); name = Outputs; path = ../../Outputs;