From 931d9cf01448013c3cfdcc881cf8b0035f82ed9d Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 24 Apr 2024 13:14:12 -0400 Subject: [PATCH] math module --- .clang-format | 2 +- CMakeLists.txt | 17 + include/common/luax.hpp | 10 +- include/modules/math/BezierCurve.hpp | 54 +++ include/modules/math/MathModule.hpp | 101 ++++ include/modules/math/RandomGenerator.hpp | 84 ++++ include/modules/math/Transform.hpp | 80 ++++ include/modules/math/wrap_BezierCurve.hpp | 42 ++ include/modules/math/wrap_Math.lua | 184 ++++++++ include/modules/math/wrap_MathModule.hpp | 29 ++ include/modules/math/wrap_RandomGenerator.hpp | 28 ++ include/modules/math/wrap_RandomGenerator.lua | 96 ++++ include/modules/math/wrap_Transform.hpp | 46 ++ platform/cafe/source/boot.cpp | 2 + source/common/luax.cpp | 36 +- source/modules/love/love.cpp | 7 +- source/modules/love/scripts/boot.lua | 4 +- source/modules/math/BezierCurve.cpp | 252 ++++++++++ source/modules/math/MathModule.cpp | 204 +++++++++ source/modules/math/RandomGenerator.cpp | 101 ++++ source/modules/math/Transform.cpp | 102 +++++ source/modules/math/wrap_BezierCurve.cpp | 236 ++++++++++ source/modules/math/wrap_Math.lua | 184 ++++++++ source/modules/math/wrap_MathModule.cpp | 432 ++++++++++++++++++ source/modules/math/wrap_RandomGenerator.cpp | 129 ++++++ source/modules/math/wrap_RandomGenerator.lua | 96 ++++ source/modules/math/wrap_Transform.cpp | 336 ++++++++++++++ 27 files changed, 2882 insertions(+), 12 deletions(-) create mode 100644 include/modules/math/BezierCurve.hpp create mode 100644 include/modules/math/MathModule.hpp create mode 100644 include/modules/math/RandomGenerator.hpp create mode 100644 include/modules/math/Transform.hpp create mode 100644 include/modules/math/wrap_BezierCurve.hpp create mode 100644 include/modules/math/wrap_Math.lua create mode 100644 include/modules/math/wrap_MathModule.hpp create mode 100644 include/modules/math/wrap_RandomGenerator.hpp create mode 100644 include/modules/math/wrap_RandomGenerator.lua create mode 100644 include/modules/math/wrap_Transform.hpp create mode 100644 source/modules/math/BezierCurve.cpp create mode 100644 source/modules/math/MathModule.cpp create mode 100644 source/modules/math/RandomGenerator.cpp create mode 100644 source/modules/math/Transform.cpp create mode 100644 source/modules/math/wrap_BezierCurve.cpp create mode 100644 source/modules/math/wrap_Math.lua create mode 100644 source/modules/math/wrap_MathModule.cpp create mode 100644 source/modules/math/wrap_RandomGenerator.cpp create mode 100644 source/modules/math/wrap_RandomGenerator.lua create mode 100644 source/modules/math/wrap_Transform.cpp diff --git a/.clang-format b/.clang-format index 880d192b..6b039fe6 100644 --- a/.clang-format +++ b/.clang-format @@ -76,7 +76,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: AfterColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 100 +ColumnLimit: 110 CommentPragmas: "^ IWYU pragma:" QualifierAlignment: Leave CompactNamespaces: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 86a89c55..947b7a02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,15 @@ add_library(wuff ) target_link_libraries(${PROJECT_NAME} PRIVATE wuff) +# noise1234 +add_library(noise1234 + libraries/noise1234/noise1234.cpp + libraries/noise1234/noise1234.h + libraries/noise1234/simplexnoise1234.cpp + libraries/noise1234/simplexnoise1234.h +) +target_link_libraries(${PROJECT_NAME} PRIVATE noise1234) + if (NINTENDO_WIIU) target_link_libraries(${PROJECT_NAME} PRIVATE vorbisfile vorbis) else() @@ -335,6 +344,14 @@ source/modules/joystick/wrap_Joystick.cpp source/modules/joystick/wrap_JoystickModule.cpp source/modules/keyboard/wrap_Keyboard.cpp source/modules/love/love.cpp +source/modules/math/BezierCurve.cpp +source/modules/math/MathModule.cpp +source/modules/math/RandomGenerator.cpp +source/modules/math/Transform.cpp +source/modules/math/wrap_BezierCurve.cpp +source/modules/math/wrap_MathModule.cpp +source/modules/math/wrap_RandomGenerator.cpp +source/modules/math/wrap_Transform.cpp source/modules/sensor/Sensor.cpp source/modules/sensor/wrap_Sensor.cpp source/modules/sound/Decoder.cpp diff --git a/include/common/luax.hpp b/include/common/luax.hpp index f35a5108..2c8e04cd 100644 --- a/include/common/luax.hpp +++ b/include/common/luax.hpp @@ -256,8 +256,14 @@ namespace love int luax_convobj(lua_State* L, int index, const char* module, const char* function); - int luax_convobj(lua_State* L, std::span indices, const char* module, - const char* function); + int luax_convobj(lua_State* L, std::span indices, const char* module, const char* function); + + void luax_gettypemetatable(lua_State* L, const Type& type); + + void luax_runwrapper(lua_State* L, const char* filedata, size_t datalen, const char* filename, + const Type& type); + + lua_Number luax_checknumberclamped01(lua_State* L, int index); // #endregion diff --git a/include/modules/math/BezierCurve.hpp b/include/modules/math/BezierCurve.hpp new file mode 100644 index 00000000..dfb63087 --- /dev/null +++ b/include/modules/math/BezierCurve.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "common/Object.hpp" +#include "common/Vector.hpp" + +#include + +namespace love +{ + class BezierCurve : public Object + { + public: + static Type type; + + BezierCurve(const std::vector& controlPoints); + + size_t getDegree() const + { + return this->controlPoints.size() - 1; + } + + BezierCurve getDerivative() const; + + const Vector2& getControlPoint(int i) const; + + void setControlPoint(int i, const Vector2& point); + + void insertControlPoint(const Vector2& point, int pos = -1); + + void removeControlPoint(int i); + + size_t getControlPointCount() const + { + return this->controlPoints.size(); + } + + void translate(const Vector2& offset); + + void rotate(double phi, const Vector2& center); + + void scale(double scale, const Vector2& center); + + Vector2 evaluate(double time) const; + + BezierCurve* getSegment(double start, double end) const; + + std::vector render(int accuracy = 4) const; + + std::vector renderSegment(double start, double end, int accuracy = 4) const; + + private: + std::vector controlPoints; + }; +} // namespace love diff --git a/include/modules/math/MathModule.hpp b/include/modules/math/MathModule.hpp new file mode 100644 index 00000000..961e7f0b --- /dev/null +++ b/include/modules/math/MathModule.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include "modules/math/RandomGenerator.hpp" + +#include "common/Module.hpp" +#include "common/StrongRef.hpp" +#include "common/Vector.hpp" +#include "common/int.hpp" +#include "common/math.hpp" + +#include "noise1234.h" +#include "simplexnoise1234.h" + +#include + +namespace love +{ + class BezierCurve; + class Transform; + + struct Triangle + { + Triangle(const Vector2& x, const Vector2& y, const Vector2& z) : a(x), b(y), c(z) + {} + + Vector2 a, b, c; + }; + + std::vector triangulate(const std::vector& polygon); + + bool isConvex(const std::vector& polygon); + + float gammaToLinear(float c); + + float linearToGamma(float c); + + static inline double simplexNoise1(double x) + { + return SimplexNoise1234::noise(x) * 0.5 + 0.5; + } + + static inline double simplexNoise2(double x, double y) + { + return SimplexNoise1234::noise(x, y) * 0.5 + 0.5; + } + + static inline double simplexNoise3(double x, double y, double z) + { + return SimplexNoise1234::noise(x, y, z) * 0.5 + 0.5; + } + + static inline double simplexNoise4(double x, double y, double z, double w) + { + return SimplexNoise1234::noise(x, y, z, w) * 0.5 + 0.5; + } + + static inline double perlinNoise1(double x) + { + return Noise1234::noise(x) * 0.5 + 0.5; + } + + static inline double perlinNoise2(double x, double y) + { + return Noise1234::noise(x, y) * 0.5 + 0.5; + } + + static inline double perlinNoise3(double x, double y, double z) + { + return Noise1234::noise(x, y, z) * 0.5 + 0.5; + } + + static inline double perlinNoise4(double x, double y, double z, double w) + { + return Noise1234::noise(x, y, z, w) * 0.5 + 0.5; + } + + class Math : public Module + { + public: + Math(); + + virtual ~Math(); + + RandomGenerator* getRandomGenerator() + { + return this->random.get(); + } + + RandomGenerator* newRandomGenerator() const; + + BezierCurve* newBezierCurve(const std::vector& points) const; + + Transform* newTransform() const; + + Transform* newTransform(float x, float y, float angle, float sx, float sy, float ox, float oy, + float kx, float ky) const; + + private: + StrongRef random; + }; +} // namespace love diff --git a/include/modules/math/RandomGenerator.hpp b/include/modules/math/RandomGenerator.hpp new file mode 100644 index 00000000..8061f5fd --- /dev/null +++ b/include/modules/math/RandomGenerator.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "common/Exception.hpp" +#include "common/Object.hpp" +#include "common/int.hpp" +#include "common/math.hpp" + +#include +#include + +namespace love +{ + class RandomGenerator : public Object + { + public: + static Type type; + + union Seed + { + uint64_t b64; + struct + { +#if defined(LOVE_BIG_ENDIAN) + uint32_t high; + uint32_t low; +#else + uint32_t low; + uint32_t high; +#endif + } b32; + }; + + RandomGenerator(); + + virtual ~RandomGenerator() + {} + + uint64_t rand(); + + inline double random() + { + uint64_t value = rand(); + + union + { + uint64_t i; + double d; + } u; + + u.i = ((0x3FFULL) << 52) | (value >> 12); + return u.d - 1.0; + } + + inline double random(double max) + { + return random() * max; + } + + inline double random(double min, double max) + { + return random() * (max - min) + min; + } + + double randomNormal(double stddev); + + void setSeed(Seed seed); + + Seed getSeed() const; + + void setState(const std::string& state); + + std::string getState() const; + + private: + static constexpr auto DEFAULT_LOW = 0xCBBF7A44; + static constexpr auto DEFAULT_HIGH = 0x0139408D; + + static constexpr auto MULTIPLIER = 2685821657736338717ULL; + + Seed seed; + Seed state; + double lastRandomNormal; + }; +} // namespace love diff --git a/include/modules/math/Transform.hpp b/include/modules/math/Transform.hpp new file mode 100644 index 00000000..55fca208 --- /dev/null +++ b/include/modules/math/Transform.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "common/Map.hpp" +#include "common/Matrix.hpp" +#include "common/Object.hpp" +#include "common/Vector.hpp" + +namespace love +{ + class Transform : public Object + { + public: + enum MatrixLayout + { + MATRIX_ROW_MAJOR, + MATRIX_COLUMN_MAJOR, + MATRIX_MAX_ENUM + }; + + static Type type; + + Transform(); + + Transform(const Matrix4& m); + + Transform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky); + + virtual ~Transform(); + + Transform* clone(); + + Transform* inverse(); + + void apply(Transform* other); + + void translate(float x, float y); + + void rotate(float angle); + + void scale(float x, float y); + + void shear(float x, float y); + + void reset(); + + void setTransformation(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, + float ky); + + Vector2 transformPoint(Vector2 point) const; + + Vector2 inverseTransformPoint(Vector2 point); + + const Matrix4& getMatrix() const; + + void setMatrix(const Matrix4& m); + + // clang-format off + STRINGMAP_DECLARE(MatrixLayouts, MatrixLayout, + { "row", MATRIX_ROW_MAJOR }, + { "column", MATRIX_COLUMN_MAJOR } + ); + // clang-format on + + private: + inline const Matrix4& getInverseMatrix() + { + if (this->inverseDirty) + { + this->inverseMatrix = matrix.inverse(); + this->inverseDirty = false; + } + + return this->inverseMatrix; + } + + Matrix4 matrix; + bool inverseDirty; + Matrix4 inverseMatrix; + }; +} // namespace love diff --git a/include/modules/math/wrap_BezierCurve.hpp b/include/modules/math/wrap_BezierCurve.hpp new file mode 100644 index 00000000..6a117786 --- /dev/null +++ b/include/modules/math/wrap_BezierCurve.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/math/BezierCurve.hpp" + +namespace love +{ + BezierCurve* luax_checkbeziercurve(lua_State* L, int index); + + int open_beziercurve(lua_State* L); +} // namespace love + +namespace Wrap_BezierCurve +{ + int getDegree(lua_State* L); + + int getDerivative(lua_State* L); + + int getControlPoint(lua_State* L); + + int setControlPoint(lua_State* L); + + int insertControlPoint(lua_State* L); + + int removeControlPoint(lua_State* L); + + int getControlPointCount(lua_State* L); + + int translate(lua_State* L); + + int rotate(lua_State* L); + + int scale(lua_State* L); + + int evaluate(lua_State* L); + + int getSegment(lua_State* L); + + int render(lua_State* L); + + int renderSegment(lua_State* L); +} // namespace Wrap_BezierCurve diff --git a/include/modules/math/wrap_Math.lua b/include/modules/math/wrap_Math.lua new file mode 100644 index 00000000..c89012c7 --- /dev/null +++ b/include/modules/math/wrap_Math.lua @@ -0,0 +1,184 @@ +R"luastring"--( +-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string. +-- There is a matching delimiter at the bottom of the file. + +--[[ +Copyright (c) 2006-2024 LOVE Development Team + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +--]] + +local love_math, ffifuncspointer_str = ... + +local type, tonumber, error = type, tonumber, error +local floor = math.floor +local min, max = math.min, math.max + +local function clamp01(x) + return min(max(x, 0), 1) +end + +local rng = love_math._getRandomGenerator() + +function love_math.random(l, u) + return rng:random(l, u) +end + +function love_math.randomNormal(stddev, mean) + return rng:randomNormal(stddev, mean) +end + +function love_math.setRandomSeed(low, high) + return rng:setSeed(low, high) +end + +function love_math.getRandomSeed() + return rng:getSeed() +end + +function love_math.setRandomState(state) + return rng:setState(state) +end + +function love_math.getRandomState() + return rng:getState() +end + +function love_math.colorToBytes(r, g, b, a) + if type(r) == "table" then + r, g, b, a = r[1], r[2], r[3], r[4] + end + r = floor(clamp01(r) * 255 + 0.5) + g = floor(clamp01(g) * 255 + 0.5) + b = floor(clamp01(b) * 255 + 0.5) + a = a ~= nil and floor(clamp01(a) * 255 + 0.5) or nil + return r, g, b, a +end + +function love_math.colorFromBytes(r, g, b, a) + if type(r) == "table" then + r, g, b, a = r[1], r[2], r[3], r[4] + end + r = clamp01(floor(r + 0.5) / 255) + g = clamp01(floor(g + 0.5) / 255) + b = clamp01(floor(b + 0.5) / 255) + a = a ~= nil and clamp01(floor(a + 0.5) / 255) or nil + return r, g, b, a +end + +if type(jit) ~= "table" or not jit.status() then + -- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT + -- compiler is disabled. + return +end + +local status, ffi = pcall(require, "ffi") +if not status then return end + +-- Matches the struct declaration in wrap_Math.cpp. +pcall(ffi.cdef, [[ +typedef struct FFI_Math +{ + double (*snoise1)(double x); + double (*snoise2)(double x, double y); + double (*snoise3)(double x, double y, double z); + double (*snoise4)(double x, double y, double z, double w); + double (*pnoise1)(double x); + double (*pnoise2)(double x, double y); + double (*pnoise3)(double x, double y, double z); + double (*pnoise4)(double x, double y, double z, double w); + + float (*gammaToLinear)(float c); + float (*linearToGamma)(float c); +} FFI_Math; +]]) + +local ffifuncs = ffi.cast("FFI_Math **", ffifuncspointer_str)[0] +local love = require("love") + +-- Overwrite some regular love.math functions with FFI implementations. + +function love_math.noise(x, y, z, w) + love.markDeprecated(2, "love.math.noise", "function", "replaced", "love.math.perlinNoise or love.math.simplexNoise") + + if w ~= nil then + return tonumber(ffifuncs.pnoise4(x, y, z, w)) + elseif z ~= nil then + return tonumber(ffifuncs.pnoise3(x, y, z)) + elseif y ~= nil then + return tonumber(ffifuncs.snoise2(x, y)) + else + return tonumber(ffifuncs.snoise1(x)) + end +end + +function love_math.perlinNoise(x, y, z, w) + if w ~= nil then + return tonumber(ffifuncs.pnoise4(x, y, z, w)) + elseif z ~= nil then + return tonumber(ffifuncs.pnoise3(x, y, z)) + elseif y ~= nil then + return tonumber(ffifuncs.pnoise2(x, y)) + else + return tonumber(ffifuncs.pnoise1(x)) + end +end + +function love_math.simplexNoise(x, y, z, w) + if w ~= nil then + return tonumber(ffifuncs.snoise4(x, y, z, w)) + elseif z ~= nil then + return tonumber(ffifuncs.snoise3(x, y, z)) + elseif y ~= nil then + return tonumber(ffifuncs.snoise2(x, y)) + else + return tonumber(ffifuncs.snoise1(x)) + end +end + +local function gammaToLinear(c) + if c ~= nil then + return tonumber(ffifuncs.gammaToLinear(clamp01(c))) + end + return c +end + +function love_math.gammaToLinear(r, g, b, a) + if type(r) == "table" then + local t = r + return gammaToLinear(t[1]), gammaToLinear(t[2]), gammaToLinear(t[3]), t[4] + end + return gammaToLinear(r), gammaToLinear(g), gammaToLinear(b), a +end + +local function linearToGamma(c) + if c ~= nil then + return tonumber(ffifuncs.linearToGamma(clamp01(c))) + end + return c +end + +function love_math.linearToGamma(r, g, b, a) + if type(r) == "table" then + local t = r + return linearToGamma(t[1]), linearToGamma(t[2]), linearToGamma(t[3]), t[4] + end + return linearToGamma(r), linearToGamma(g), linearToGamma(b), a +end + +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/include/modules/math/wrap_MathModule.hpp b/include/modules/math/wrap_MathModule.hpp new file mode 100644 index 00000000..ee9d2ab3 --- /dev/null +++ b/include/modules/math/wrap_MathModule.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/math/MathModule.hpp" + +namespace Wrap_MathModule +{ + int _getRandomGenerator(lua_State* L); + + int newRandomGenerator(lua_State* L); + + int newBezierCurve(lua_State* L); + + int newTransform(lua_State* L); + + int triangulate(lua_State* L); + + int isConvex(lua_State* L); + + int gammaToLinear(lua_State* L); + + int linearToGamma(lua_State* L); + + int perlinNoise(lua_State* L); + + int simplexNoise(lua_State* L); + + int open(lua_State* L); +} // namespace Wrap_MathModule diff --git a/include/modules/math/wrap_RandomGenerator.hpp b/include/modules/math/wrap_RandomGenerator.hpp new file mode 100644 index 00000000..4ba65292 --- /dev/null +++ b/include/modules/math/wrap_RandomGenerator.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/math/RandomGenerator.hpp" + +namespace love +{ + RandomGenerator* luax_checkrandomgenerator(lua_State* L, int index); + + RandomGenerator::Seed luax_checkrandomseed(lua_State* L, int index); + + int open_randomgenerator(lua_State* L); +} // namespace love + +namespace Wrap_RandomGenerator +{ + int _random(lua_State* L); + + int randomNormal(lua_State* L); + + int setSeed(lua_State* L); + + int getSeed(lua_State* L); + + int setState(lua_State* L); + + int getState(lua_State* L); +} // namespace Wrap_RandomGenerator diff --git a/include/modules/math/wrap_RandomGenerator.lua b/include/modules/math/wrap_RandomGenerator.lua new file mode 100644 index 00000000..e394833e --- /dev/null +++ b/include/modules/math/wrap_RandomGenerator.lua @@ -0,0 +1,96 @@ +R"luastring"--( +-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string. +-- There is a matching delimiter at the bottom of the file. + +--[[ +Copyright (c) 2006-2024 LOVE Development Team + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +--]] + +local RandomGenerator_mt, ffifuncspointer_str = ... +local RandomGenerator = RandomGenerator_mt.__index + +local type, tonumber, error = type, tonumber, error +local floor = math.floor + +local _random = RandomGenerator._random + +local function getrandom(r, l, u) + if u ~= nil then + if type(r) ~= "number" then error("bad argument #1 to 'random' (number expected)", 2) end + if type(l) ~= "number" then error("bad argument #2 to 'random' (number expected)", 2) end + return floor(r * (u - l + 1)) + l + elseif l ~= nil then + if type(l) ~= "number" then error("bad argument #1 to 'random' (number expected)", 2) end + return floor(r * l) + 1 + else + return r + end +end + +function RandomGenerator:random(l, u) + local r = _random(self) + return getrandom(r, l, u) +end + +if type(jit) ~= "table" or not jit.status() then + -- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT + -- compiler is disabled. + return +end + +local status, ffi = pcall(require, "ffi") +if not status then return end + +pcall(ffi.cdef, [[ +typedef struct Proxy Proxy; + +typedef struct FFI_RandomGenerator +{ + double (*random)(Proxy *p); + double (*randomNormal)(Proxy *p, double stddev, double mean); +} FFI_RandomGenerator; +]]) + +local ffifuncs = ffi.cast("FFI_RandomGenerator **", ffifuncspointer_str)[0] + + +-- Overwrite some regular love.math functions with FFI implementations. + +function RandomGenerator:random(l, u) + -- TODO: This should ideally be handled inside ffifuncs.random + if self == nil then error("bad argument #1 to 'random' (RandomGenerator expected, got no value)", 2) end + local r = tonumber(ffifuncs.random(self)) + return getrandom(r, l, u) +end + +function RandomGenerator:randomNormal(stddev, mean) + -- TODO: This should ideally be handled inside ffifuncs.randomNormal + if self == nil then error("bad argument #1 to 'randomNormal' (RandomGenerator expected, got no value)", 2) end + + stddev = stddev == nil and 1 or stddev + mean = mean == nil and 0 or mean + + if type(stddev) ~= "number" then error("bad argument #1 to 'randomNormal' (number expected)", 2) end + if type(mean) ~= "number" then error("bad argument #2 to 'randomNormal' (number expected)", 2) end + + return tonumber(ffifuncs.randomNormal(self, stddev, mean)) +end + +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/include/modules/math/wrap_Transform.hpp b/include/modules/math/wrap_Transform.hpp new file mode 100644 index 00000000..506794a7 --- /dev/null +++ b/include/modules/math/wrap_Transform.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/math/Transform.hpp" + +namespace love +{ + Transform* luax_checktransform(lua_State* L, int index); + + void luax_checkmatrix(lua_State* L, int index, Transform::MatrixLayout layout, float elements[16]); + + int open_transform(lua_State* L); +} // namespace love + +namespace Wrap_Transform +{ + int clone(lua_State* L); + + int inverse(lua_State* L); + + int apply(lua_State* L); + + int isAffine2DTransform(lua_State* L); + + int translate(lua_State* L); + + int rotate(lua_State* L); + + int scale(lua_State* L); + + int shear(lua_State* L); + + int reset(lua_State* L); + + int setTransformation(lua_State* L); + + int setMatrix(lua_State* L); + + int getMatrix(lua_State* L); + + int transformPoint(lua_State* L); + + int inverseTransformPoint(lua_State* L); + + int __mul(lua_State* L); +} // namespace Wrap_Transform diff --git a/platform/cafe/source/boot.cpp b/platform/cafe/source/boot.cpp index 7ec5d4c4..9e723f2a 100644 --- a/platform/cafe/source/boot.cpp +++ b/platform/cafe/source/boot.cpp @@ -20,6 +20,8 @@ #include +#include "utility/logfile.hpp" + namespace love { // clang-format off diff --git a/source/common/luax.cpp b/source/common/luax.cpp index 8a023b1f..cc8245a3 100644 --- a/source/common/luax.cpp +++ b/source/common/luax.cpp @@ -7,7 +7,7 @@ #include #include -#include "utility/logfile.hpp" +#include namespace love { @@ -474,8 +474,7 @@ namespace love } } - Variant luax_checkvariant(lua_State* L, int index, bool allowuserdata, - std::set* tableSet) + Variant luax_checkvariant(lua_State* L, int index, bool allowuserdata, std::set* tableSet) { size_t len; const char* str; @@ -544,8 +543,7 @@ namespace love lua_pop(L, 1); const auto& p = table->pairs.back(); - if (p.first.getType() == Variant::UNKNOWN || - p.second.getType() == Variant::UNKNOWN) + if (p.first.getType() == Variant::UNKNOWN || p.second.getType() == Variant::UNKNOWN) { success = false; break; @@ -795,6 +793,34 @@ namespace love return 0; } + void luax_gettypemetatable(lua_State* L, const Type& type) + { + const char* name = type.getName(); + lua_getfield(L, LUA_REGISTRYINDEX, name); + } + + void luax_runwrapper(lua_State* L, const char* filedata, size_t datalen, const char* filename, + const Type& type) + { + luax_gettypemetatable(L, type); + + if (lua_istable(L, -1)) + { + const auto name = std::format("=[love \"{:s}\"]", filename); + luaL_loadbuffer(L, filedata, datalen, name.c_str()); + lua_pushvalue(L, -2); + lua_pushnil(L); + lua_call(L, 2, 0); + } + + lua_pop(L, 1); + } + + lua_Number luax_checknumberclamped01(lua_State* L, int index) + { + return std::clamp((float)luaL_checknumber(L, index), 0.0f, 1.0f); + } + // #endregion // #region Registry diff --git a/source/modules/love/love.cpp b/source/modules/love/love.cpp index 4c516345..cb8cef6a 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -10,6 +10,7 @@ #include "modules/graphics/wrap_Graphics.hpp" #include "modules/joystick/wrap_JoystickModule.hpp" #include "modules/keyboard/wrap_Keyboard.hpp" +#include "modules/math/wrap_MathModule.hpp" #include "modules/sensor/wrap_Sensor.hpp" #include "modules/sound/wrap_Sound.hpp" #include "modules/system/wrap_System.hpp" @@ -48,7 +49,7 @@ static constexpr char nogame_lua[] = { }; // clang-format off -static constexpr std::array modules = +static constexpr std::array modules = {{ { "love.audio", Wrap_Audio::open }, { "love.data", Wrap_DataModule::open }, @@ -57,6 +58,7 @@ static constexpr std::array modules = { "love.graphics", Wrap_Graphics::open }, { "love.joystick", Wrap_JoystickModule::open }, { "love.keyboard", Wrap_Keyboard::open }, + { "love.math", Wrap_MathModule::open }, { "love.sensor", Wrap_Sensor::open }, { "love.sound", Wrap_Sound::open }, { "love.system", Wrap_System::open }, @@ -139,8 +141,7 @@ static int love_setDeprecationOutput(lua_State* L) return 0; } -static void luax_addcompatibilityalias(lua_State* L, const char* module, const char* name, - const char* alias) +static void luax_addcompatibilityalias(lua_State* L, const char* module, const char* name, const char* alias) { lua_getglobal(L, module); diff --git a/source/modules/love/scripts/boot.lua b/source/modules/love/scripts/boot.lua index 88c74341..6d7bd4a2 100644 --- a/source/modules/love/scripts/boot.lua +++ b/source/modules/love/scripts/boot.lua @@ -176,7 +176,7 @@ function love.init() image = false, graphics = true, audio = true, - math = false, + math = true, physics = false, sensor = true, sound = true, @@ -315,6 +315,8 @@ function love.init() local success, msg = pcall(require, "love." .. v) if v == "audio" and not success and msg:find("ndsp") then error(msg) + else + print(msg) end end end diff --git a/source/modules/math/BezierCurve.cpp b/source/modules/math/BezierCurve.cpp new file mode 100644 index 00000000..075375e8 --- /dev/null +++ b/source/modules/math/BezierCurve.cpp @@ -0,0 +1,252 @@ +#include "common/Exception.hpp" + +#include "modules/math/BezierCurve.hpp" + +#include +#include + +namespace love +{ + static void subdivide(std::vector& points, int k) + { + if (k <= 0) + return; + + // subdivision using de casteljau - subdivided control polygons are + // on the 'edges' of the computation scheme, e.g: + // + // ------LEFT-------> + // b00 b10 b20 b30 + // b01 b11 b21 .--- + // b02 b12 .---' + // b03 .---'RIGHT + // <--' + // + // the subdivided control polygon is: + // b00, b10, b20, b30, b21, b12, b03 + std::vector left, right; + + left.reserve(points.size()); + right.reserve(points.size()); + + for (size_t step = 1; step < points.size(); ++step) + { + left.push_back(points[0]); + right.push_back(points[points.size() - step]); + + for (size_t i = 0; i < points.size() - step; ++i) + points[i] = (points[i] + points[i + 1]) * .5; + } + + left.push_back(points[0]); + right.push_back(points[0]); + + // recurse + subdivide(left, k - 1); + subdivide(right, k - 1); + + // merge (right is in reversed order) + // By this point the 'left' array has a point at the end that's collinear + // with other points. It's still needed for the subdivide calls above but we + // can get rid of it here. + points.resize(left.size() + right.size() - 2); + + for (size_t i = 0; i < left.size() - 1; ++i) + points[i] = left[i]; + + for (size_t i = 1; i < right.size(); ++i) + points[i - 1 + left.size() - 1] = right[right.size() - i - 1]; + } + + Type BezierCurve::type("BezierCurve", &Object::type); + + BezierCurve::BezierCurve(const std::vector& controlPoints) : controlPoints(controlPoints) + {} + + BezierCurve BezierCurve::getDerivative() const + { + if (this->getDegree() < 1) + throw love::Exception("Cannot derive a curve of degree < 1."); + + std::vector differences(this->controlPoints.size() - 1); + const float degree = this->getDegree(); + + for (size_t index = 0; index < differences.size(); ++index) + differences[index] = (this->controlPoints[index + 1] - this->controlPoints[index]) * degree; + + return BezierCurve(differences); + } + + const Vector2& BezierCurve::getControlPoint(int index) const + { + if (this->controlPoints.size() == 0) + throw love::Exception("Curve contains no control points."); + + while (index < 0) + index += this->controlPoints.size(); + + while ((size_t)index >= this->controlPoints.size()) + index -= this->controlPoints.size(); + + return this->controlPoints[index]; + } + + void BezierCurve::setControlPoint(int index, const Vector2& point) + { + if (this->controlPoints.size() == 0) + throw love::Exception("Curve contains no control points."); + + while (index < 0) + index += this->controlPoints.size(); + + while ((size_t)index >= this->controlPoints.size()) + index -= this->controlPoints.size(); + + this->controlPoints[index] = point; + } + + void BezierCurve::insertControlPoint(const Vector2& point, int index) + { + if (this->controlPoints.size() == 0) + index = 0; + + while (index < 0) + index += this->controlPoints.size(); + + while ((size_t)index > this->controlPoints.size()) + index -= this->controlPoints.size(); + + this->controlPoints.insert(this->controlPoints.begin() + index, point); + } + + void BezierCurve::removeControlPoint(int index) + { + if (this->controlPoints.size() == 0) + throw love::Exception("Curve contains no control points."); + + while (index < 0) + index += this->controlPoints.size(); + + while ((size_t)index >= this->controlPoints.size()) + index -= this->controlPoints.size(); + + this->controlPoints.erase(this->controlPoints.begin() + index); + } + + void BezierCurve::translate(const Vector2& offset) + { + for (size_t index = 0; index < this->controlPoints.size(); ++index) + this->controlPoints[index] += offset; + } + + void BezierCurve::rotate(double phi, const Vector2& center) + { + const float c = std::cos(phi); + const float s = std::sin(phi); + + for (size_t index = 0; index < this->controlPoints.size(); ++index) + { + Vector2 point = this->controlPoints[index] - center; + + this->controlPoints[index].x = c * point.x - s * point.y + center.x; + this->controlPoints[index].y = s * point.x + c * point.y + center.y; + } + } + + void BezierCurve::scale(double scale, const Vector2& center) + { + for (size_t index = 0; index < this->controlPoints.size(); ++index) + this->controlPoints[index] = (this->controlPoints[index] - center) * scale + center; + } + + Vector2 BezierCurve::evaluate(double time) const + { + if (time < 0 || time > 1) + throw love::Exception("Invalid evaluation parameter: must be between 0 and 1"); + + if (this->controlPoints.size() < 2) + throw love::Exception("Invalid Bezier curve: Not enough control points."); + + std::vector points(this->controlPoints); + + for (size_t step = 1; step < this->controlPoints.size(); ++step) + { + for (size_t index = 0; index < this->controlPoints.size() - step; ++index) + points[index] = points[index] * (1 - time) + points[index + 1] * time; + } + + return points[0]; + } + + BezierCurve* BezierCurve::getSegment(double start, double end) const + { + if (start < 0 || end > 1) + throw love::Exception("Invalid segment parameters: must be between 0 and 1"); + + if (end <= start) + throw love::Exception("Invalid segment parameters: end must be greater than start"); + + std::vector points(this->controlPoints); + std::vector left {}, right {}; + + for (size_t step = 1; step < points.size(); ++step) + { + left.push_back(points[0]); + for (size_t index = 0; index < points.size() - step; ++index) + points[index] += (points[index + 1] - points[index]) * end; + } + left.push_back(points[0]); + + double subdivision = start / end; + for (size_t step = 1; step < left.size(); ++step) + { + right.push_back(left[left.size() - step]); + for (size_t index = 0; index < left.size() - step; ++index) + left[index] += (left[index + 1] - left[index]) * subdivision; + } + right.push_back(left[0]); + + std::reverse(right.begin(), right.end()); + + return new BezierCurve(right); + } + + std::vector BezierCurve::render(int accuracy) const + { + if (this->controlPoints.size() < 2) + throw love::Exception("Invalid Bezier curve: Not enough control points."); + + std::vector points(this->controlPoints); + subdivide(points, accuracy); + + return points; + } + + std::vector BezierCurve::renderSegment(double start, double end, int accuracy) const + { + if (this->controlPoints.size() < 2) + throw love::Exception("Invalid Bezier curve: Not enough control points."); + + std::vector points(this->controlPoints); + subdivide(points, accuracy); + + if (start == end) + points.clear(); + else if (start < end) + { + size_t startIndex = (size_t)(start * points.size()); + size_t endIndex = (size_t)(end * points.size() + 0.5); + + return std::vector(points.begin() + startIndex, points.begin() + endIndex); + } + else if (end > start) + { + size_t startIndex = (size_t)(end * points.size() + 0.5); + size_t endIndex = (size_t)(start * points.size()); + + return std::vector(points.begin() + startIndex, points.begin() + endIndex); + } + + return points; + } +} // namespace love diff --git a/source/modules/math/MathModule.cpp b/source/modules/math/MathModule.cpp new file mode 100644 index 00000000..ea63c406 --- /dev/null +++ b/source/modules/math/MathModule.cpp @@ -0,0 +1,204 @@ +#include "modules/math/MathModule.hpp" + +#include "common/Map.hpp" +#include "common/Vector.hpp" +#include "common/int.hpp" + +#include "modules/math/BezierCurve.hpp" +#include "modules/math/Transform.hpp" + +#include +#include + +#include + +namespace love +{ + inline bool isOrientedCCW(const Vector2& a, const Vector2& b, const Vector2& c) + { + return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) >= 0; + } + + inline bool onSameSide(const Vector2& a, const Vector2& b, const Vector2& c, const Vector2& d) + { + float px = d.x - c.x, py = d.y - c.y; + + float l = px * (a.y - c.y) - py * (a.x - c.x); + float m = px * (b.y - c.y) - py * (b.x - c.x); + + return l * m >= 0; + } + + inline bool pointInTriangle(const Vector2& p, const Vector2& a, const Vector2& b, const Vector2& c) + { + return onSameSide(p, a, b, c) && onSameSide(p, b, a, c) && onSameSide(p, c, a, b); + } + + bool anyPointInTriangle(const std::list& vertices, const Vector2& a, const Vector2& b, + const Vector2& c) + { + for (const Vector2* p : vertices) + { + if ((p != &a) && (p != &b) && (p != &c) && pointInTriangle(*p, a, b, c)) // oh god... + return true; + } + + return false; + } + + inline bool isEar(const Vector2& a, const Vector2& b, const Vector2& c, + const std::list& vertices) + { + return isOrientedCCW(a, b, c) && !anyPointInTriangle(vertices, a, b, c); + } + + std::vector triangulate(const std::vector& polygon) + { + if (polygon.size() < 3) + throw love::Exception("Not a polygon"); + else if (polygon.size() == 3) + return std::vector(1, Triangle(polygon[0], polygon[1], polygon[2])); + + // collect list of connections and record leftmost item to check if the polygon + // has the expected winding + std::vector next_idx(polygon.size()), prev_idx(polygon.size()); + size_t idx_lm = 0; + + for (size_t i = 0; i < polygon.size(); ++i) + { + const love::Vector2 &lm = polygon[idx_lm], &p = polygon[i]; + if (p.x < lm.x || (p.x == lm.x && p.y < lm.y)) + idx_lm = i; + + next_idx[i] = i + 1; + prev_idx[i] = i - 1; + } + + next_idx[next_idx.size() - 1] = 0; + prev_idx[0] = prev_idx.size() - 1; + + // check if the polygon has the expected winding and reverse polygon if needed + if (!isOrientedCCW(polygon[prev_idx[idx_lm]], polygon[idx_lm], polygon[next_idx[idx_lm]])) + next_idx.swap(prev_idx); + + // collect list of concave polygons + std::list concave_vertices; + for (size_t i = 0; i < polygon.size(); ++i) + { + if (!isOrientedCCW(polygon[prev_idx[i]], polygon[i], polygon[next_idx[i]])) + concave_vertices.push_back(&polygon[i]); + } + + // triangulation according to kong + std::vector triangles; + size_t n_vertices = polygon.size(); + size_t current = 1, skipped = 0, next, prev; + + while (n_vertices > 3) + { + next = next_idx[current]; + prev = prev_idx[current]; + const Vector2 &a = polygon[prev], &b = polygon[current], &c = polygon[next]; + if (isEar(a, b, c, concave_vertices)) + { + triangles.push_back(Triangle(a, b, c)); + + next_idx[prev] = next; + prev_idx[next] = prev; + concave_vertices.remove(&b); + --n_vertices; + skipped = 0; + } + else if (++skipped > n_vertices) + throw love::Exception("Cannot triangulate polygon."); + + current = next; + } + + next = next_idx[current]; + prev = prev_idx[current]; + + triangles.push_back(Triangle(polygon[prev], polygon[current], polygon[next])); + + return triangles; + } + + bool isConvex(const std::vector& polygon) + { + if (polygon.size() < 3) + return false; + + // a polygon is convex if all corners turn in the same direction + // turning direction can be determined using the cross-product of + // the forward difference vectors + size_t i = polygon.size() - 2, j = polygon.size() - 1, k = 0; + + Vector2 p(polygon[j] - polygon[i]); + Vector2 q(polygon[k] - polygon[j]); + + float winding = Vector2::cross(p, q); + + while (k + 1 < polygon.size()) + { + i = j; + j = k; + k++; + p = polygon[j] - polygon[i]; + q = polygon[k] - polygon[j]; + + if (Vector2::cross(p, q) * winding < 0) + return false; + } + + return true; + } + + float gammaToLinear(float c) + { + if (c <= 0.04045f) + return c / 12.92f; + else + return std::pow((c + 0.055f) / 1.055f, 2.4f); + } + + float linearToGamma(float c) + { + if (c <= 0.0031308f) + return c * 12.92f; + else + return 1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f; + } + + Math::Math() : Module(M_MATH, "love.math") + { + RandomGenerator::Seed seed {}; + seed.b64 = (uint64_t)time(nullptr); + + this->random.set(new RandomGenerator(), Acquire::NO_RETAIN); + this->random->setSeed(seed); + } + + Math::~Math() + {} + + RandomGenerator* Math::newRandomGenerator() const + { + return new RandomGenerator(); + } + + BezierCurve* Math::newBezierCurve(const std::vector& points) const + { + return new BezierCurve(points); + } + + Transform* Math::newTransform() const + { + return new Transform(); + } + + Transform* Math::newTransform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, + float ky) const + { + return new Transform(x, y, a, sx, sy, ox, oy, kx, ky); + } +} // namespace love diff --git a/source/modules/math/RandomGenerator.cpp b/source/modules/math/RandomGenerator.cpp new file mode 100644 index 00000000..68de6019 --- /dev/null +++ b/source/modules/math/RandomGenerator.cpp @@ -0,0 +1,101 @@ +#include "modules/math/RandomGenerator.hpp" + +#include + +#include +#include + +namespace love +{ + + static uint64_t wangHash64(uint64_t key) + { + key = (~key) + (key << 21); // key = (key << 21) - key - 1; + key = key ^ (key >> 24); + key = (key + (key << 3)) + (key << 8); // key * 265 + key = key ^ (key >> 14); + key = (key + (key << 2)) + (key << 4); // key * 21 + key = key ^ (key >> 28); + key = key + (key << 31); + + return key; + } + + Type RandomGenerator::type("RandomGenerator", &Object::type); + + RandomGenerator::RandomGenerator() : lastRandomNormal(std::numeric_limits::infinity()) + { + Seed newSeed {}; + newSeed.b32.low = RandomGenerator::DEFAULT_LOW; + newSeed.b32.high = RandomGenerator::DEFAULT_HIGH; + + this->setSeed(newSeed); + } + + uint64_t RandomGenerator::rand() + { + this->state.b64 ^= (this->state.b64 >> 12); + this->state.b64 ^= (this->state.b64 << 25); + this->state.b64 ^= (this->state.b64 >> 27); + + return this->state.b64 * RandomGenerator::MULTIPLIER; + } + + double RandomGenerator::randomNormal(double stddev) + { + if (this->lastRandomNormal != std::numeric_limits::infinity()) + { + double last = this->lastRandomNormal; + this->lastRandomNormal = std::numeric_limits::infinity(); + + return last * stddev; + } + + double root = std::sqrt(-2.0 * std::log(1.0 - this->random())); + double phi = 2.0 * LOVE_M_PI * (1.0 - this->random()); + + this->lastRandomNormal = root * std::cos(phi); + + return root * std::sin(phi) * stddev; + } + + void RandomGenerator::setSeed(RandomGenerator::Seed newSeed) + { + this->seed = newSeed; + + do + { + newSeed.b64 = wangHash64(newSeed.b64); + } while (newSeed.b64 == 0); + + this->state = newSeed; + this->lastRandomNormal = std::numeric_limits::infinity(); + } + + RandomGenerator::Seed RandomGenerator::getSeed() const + { + return this->seed; + } + + void RandomGenerator::setState(const std::string& stateString) + { + if (stateString.find("0x") != 0 || stateString.size() < 3) + throw love::Exception("Invalid random state: {:s}", stateString); + + Seed newState {}; + char* end = nullptr; + + newState.b64 = std::strtoull(stateString.c_str(), &end, 16); + + if (end != nullptr && *end != 0) + throw love::Exception("Invalid random state: {:s}", stateString); + + this->state = newState; + this->lastRandomNormal = std::numeric_limits::infinity(); + } + + std::string RandomGenerator::getState() const + { + return std::format("0x{:016x}", this->state.b64); + } +} // namespace love diff --git a/source/modules/math/Transform.cpp b/source/modules/math/Transform.cpp new file mode 100644 index 00000000..ee3dfb23 --- /dev/null +++ b/source/modules/math/Transform.cpp @@ -0,0 +1,102 @@ +#include "modules/math/Transform.hpp" + +namespace love +{ + Type Transform::type("Transform", &Object::type); + + Transform::Transform() : matrix(), inverseDirty(true), inverseMatrix() + {} + + Transform::Transform(const Matrix4& matrix) : matrix(matrix), inverseDirty(true), inverseMatrix() + {} + + Transform::Transform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, + float ky) : + matrix(x, y, a, sx, sy, ox, oy, kx, ky), + inverseDirty(true), + inverseMatrix() + {} + + Transform::~Transform() + {} + + Transform* Transform::clone() + { + return new Transform(*this); + } + + Transform* Transform::inverse() + { + return new Transform(this->getInverseMatrix()); + } + + void Transform::apply(Transform* other) + { + this->matrix *= other->getMatrix(); + this->inverseDirty = true; + } + + void Transform::translate(float x, float y) + { + this->matrix.translate(x, y); + this->inverseDirty = true; + } + + void Transform::rotate(float angle) + { + this->matrix.rotate(angle); + this->inverseDirty = true; + } + + void Transform::scale(float x, float y) + { + this->matrix.scale(x, y); + this->inverseDirty = true; + } + + void Transform::shear(float x, float y) + { + this->matrix.shear(x, y); + this->inverseDirty = true; + } + + void Transform::reset() + { + this->matrix.setIdentity(); + this->inverseDirty = true; + } + + void Transform::setTransformation(float x, float y, float a, float sx, float sy, float ox, float oy, + float kx, float ky) + { + this->matrix.setTransformation(x, y, a, sx, sy, ox, oy, kx, ky); + this->inverseDirty = true; + } + + Vector2 Transform::transformPoint(Vector2 point) const + { + Vector2 result {}; + this->matrix.transformXY(&result, &point, 1); + + return result; + } + + Vector2 Transform::inverseTransformPoint(Vector2 point) + { + Vector2 result {}; + this->getInverseMatrix().transformXY(&result, &point, 1); + + return result; + } + + const Matrix4& Transform::getMatrix() const + { + return this->matrix; + } + + void Transform::setMatrix(const Matrix4& matrix) + { + this->matrix = matrix; + this->inverseDirty = true; + } +} // namespace love diff --git a/source/modules/math/wrap_BezierCurve.cpp b/source/modules/math/wrap_BezierCurve.cpp new file mode 100644 index 00000000..9415d394 --- /dev/null +++ b/source/modules/math/wrap_BezierCurve.cpp @@ -0,0 +1,236 @@ +#include "common/Exception.hpp" + +#include "modules/math/wrap_BezierCurve.hpp" + +#include + +using namespace love; + +int Wrap_BezierCurve::getDegree(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + lua_pushinteger(L, self->getDegree()); + + return 1; +} + +int Wrap_BezierCurve::getDerivative(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + auto* derivative = new BezierCurve(self->getDerivative()); + + luax_pushtype(L, derivative); + derivative->release(); + + return 1; +} + +int Wrap_BezierCurve::getControlPoint(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + int index = luaL_checkinteger(L, 2); + + if (index > 0) + index--; + + luax_catchexcept(L, [&]() { + Vector2 point = self->getControlPoint(index); + lua_pushnumber(L, point.x); + lua_pushnumber(L, point.y); + }); + + return 2; +} + +int Wrap_BezierCurve::setControlPoint(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + int index = luaL_checkinteger(L, 2); + float x = luaL_checknumber(L, 3); + float y = luaL_checknumber(L, 4); + + if (index > 0) + index--; + + luax_catchexcept(L, [&]() { self->setControlPoint(index, Vector2(x, y)); }); + + return 0; +} + +int Wrap_BezierCurve::insertControlPoint(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + float x = luaL_checknumber(L, 2); + float y = luaL_checknumber(L, 3); + int index = luaL_optinteger(L, 4, -1); + + if (index > 0) + index--; + + luax_catchexcept(L, [&]() { self->insertControlPoint(Vector2(x, y), index); }); + + return 0; +} + +int Wrap_BezierCurve::removeControlPoint(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + int index = luaL_checkinteger(L, 2); + + if (index > 0) + index--; + + luax_catchexcept(L, [&]() { self->removeControlPoint(index); }); + + return 0; +} + +int Wrap_BezierCurve::getControlPointCount(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + lua_pushinteger(L, self->getControlPointCount()); + + return 1; +} + +int Wrap_BezierCurve::translate(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + float x = luaL_checknumber(L, 2); + float y = luaL_checknumber(L, 3); + + luax_catchexcept(L, [&]() { self->translate(Vector2(x, y)); }); + + return 0; +} + +int Wrap_BezierCurve::rotate(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + double phi = luaL_checknumber(L, 2); + float x = luaL_optnumber(L, 3, 0.0f); + float y = luaL_optnumber(L, 4, 0.0f); + + luax_catchexcept(L, [&]() { self->rotate(phi, Vector2(x, y)); }); + + return 0; +} + +int Wrap_BezierCurve::scale(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + double scale = luaL_checknumber(L, 2); + float x = luaL_optnumber(L, 3, 0.0f); + float y = luaL_optnumber(L, 4, 0.0f); + + luax_catchexcept(L, [&]() { self->scale(scale, Vector2(x, y)); }); + + return 0; +} + +int Wrap_BezierCurve::evaluate(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + double time = luaL_checknumber(L, 2); + + luax_catchexcept(L, [&]() { + Vector2 point = self->evaluate(time); + lua_pushnumber(L, point.x); + lua_pushnumber(L, point.y); + }); + + return 2; +} + +int Wrap_BezierCurve::getSegment(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + double start = luaL_checknumber(L, 2); + double end = luaL_checknumber(L, 3); + + BezierCurve* segment = nullptr; + + luax_catchexcept(L, [&]() { segment = self->getSegment(start, end); }); + + luax_pushtype(L, segment); + segment->release(); + + return 1; +} + +int Wrap_BezierCurve::render(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + int accuracy = luaL_optinteger(L, 2, 5); + + std::vector points {}; + luax_catchexcept(L, [&]() { points = self->render(accuracy); }); + + lua_createtable(L, points.size() * 2, 0); + for (size_t index = 0; index < points.size(); ++index) + { + lua_pushnumber(L, points[index].x); + lua_rawseti(L, -2, 2 * index + 1); + + lua_pushnumber(L, points[index].y); + lua_rawseti(L, -2, 2 * index + 2); + } + + return 1; +} + +int Wrap_BezierCurve::renderSegment(lua_State* L) +{ + auto* self = luax_checkbeziercurve(L, 1); + double start = luaL_checknumber(L, 2); + double end = luaL_checknumber(L, 3); + int accuracy = luaL_optinteger(L, 4, 5); + + std::vector points {}; + luax_catchexcept(L, [&]() { points = self->renderSegment(start, end, accuracy); }); + + lua_createtable(L, points.size() * 2, 0); + for (size_t index = 0; index < points.size(); ++index) + { + lua_pushnumber(L, points[index].x); + lua_rawseti(L, -2, 2 * index + 1); + + lua_pushnumber(L, points[index].y); + lua_rawseti(L, -2, 2 * index + 2); + } + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "getDegree", Wrap_BezierCurve::getDegree }, + { "getDerivative", Wrap_BezierCurve::getDerivative }, + { "getControlPoint", Wrap_BezierCurve::getControlPoint }, + { "setControlPoint", Wrap_BezierCurve::setControlPoint }, + { "insertControlPoint", Wrap_BezierCurve::insertControlPoint }, + { "removeControlPoint", Wrap_BezierCurve::removeControlPoint }, + { "getControlPointCount", Wrap_BezierCurve::getControlPointCount }, + { "translate", Wrap_BezierCurve::translate }, + { "rotate", Wrap_BezierCurve::rotate }, + { "scale", Wrap_BezierCurve::scale }, + { "evaluate", Wrap_BezierCurve::evaluate }, + { "getSegment", Wrap_BezierCurve::getSegment }, + { "render", Wrap_BezierCurve::render }, + { "renderSegment", Wrap_BezierCurve::renderSegment } +}; +// clang-format on + +namespace love +{ + BezierCurve* luax_checkbeziercurve(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_beziercurve(lua_State* L) + { + return luax_register_type(L, &BezierCurve::type, functions); + } +} // namespace love diff --git a/source/modules/math/wrap_Math.lua b/source/modules/math/wrap_Math.lua new file mode 100644 index 00000000..c89012c7 --- /dev/null +++ b/source/modules/math/wrap_Math.lua @@ -0,0 +1,184 @@ +R"luastring"--( +-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string. +-- There is a matching delimiter at the bottom of the file. + +--[[ +Copyright (c) 2006-2024 LOVE Development Team + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +--]] + +local love_math, ffifuncspointer_str = ... + +local type, tonumber, error = type, tonumber, error +local floor = math.floor +local min, max = math.min, math.max + +local function clamp01(x) + return min(max(x, 0), 1) +end + +local rng = love_math._getRandomGenerator() + +function love_math.random(l, u) + return rng:random(l, u) +end + +function love_math.randomNormal(stddev, mean) + return rng:randomNormal(stddev, mean) +end + +function love_math.setRandomSeed(low, high) + return rng:setSeed(low, high) +end + +function love_math.getRandomSeed() + return rng:getSeed() +end + +function love_math.setRandomState(state) + return rng:setState(state) +end + +function love_math.getRandomState() + return rng:getState() +end + +function love_math.colorToBytes(r, g, b, a) + if type(r) == "table" then + r, g, b, a = r[1], r[2], r[3], r[4] + end + r = floor(clamp01(r) * 255 + 0.5) + g = floor(clamp01(g) * 255 + 0.5) + b = floor(clamp01(b) * 255 + 0.5) + a = a ~= nil and floor(clamp01(a) * 255 + 0.5) or nil + return r, g, b, a +end + +function love_math.colorFromBytes(r, g, b, a) + if type(r) == "table" then + r, g, b, a = r[1], r[2], r[3], r[4] + end + r = clamp01(floor(r + 0.5) / 255) + g = clamp01(floor(g + 0.5) / 255) + b = clamp01(floor(b + 0.5) / 255) + a = a ~= nil and clamp01(floor(a + 0.5) / 255) or nil + return r, g, b, a +end + +if type(jit) ~= "table" or not jit.status() then + -- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT + -- compiler is disabled. + return +end + +local status, ffi = pcall(require, "ffi") +if not status then return end + +-- Matches the struct declaration in wrap_Math.cpp. +pcall(ffi.cdef, [[ +typedef struct FFI_Math +{ + double (*snoise1)(double x); + double (*snoise2)(double x, double y); + double (*snoise3)(double x, double y, double z); + double (*snoise4)(double x, double y, double z, double w); + double (*pnoise1)(double x); + double (*pnoise2)(double x, double y); + double (*pnoise3)(double x, double y, double z); + double (*pnoise4)(double x, double y, double z, double w); + + float (*gammaToLinear)(float c); + float (*linearToGamma)(float c); +} FFI_Math; +]]) + +local ffifuncs = ffi.cast("FFI_Math **", ffifuncspointer_str)[0] +local love = require("love") + +-- Overwrite some regular love.math functions with FFI implementations. + +function love_math.noise(x, y, z, w) + love.markDeprecated(2, "love.math.noise", "function", "replaced", "love.math.perlinNoise or love.math.simplexNoise") + + if w ~= nil then + return tonumber(ffifuncs.pnoise4(x, y, z, w)) + elseif z ~= nil then + return tonumber(ffifuncs.pnoise3(x, y, z)) + elseif y ~= nil then + return tonumber(ffifuncs.snoise2(x, y)) + else + return tonumber(ffifuncs.snoise1(x)) + end +end + +function love_math.perlinNoise(x, y, z, w) + if w ~= nil then + return tonumber(ffifuncs.pnoise4(x, y, z, w)) + elseif z ~= nil then + return tonumber(ffifuncs.pnoise3(x, y, z)) + elseif y ~= nil then + return tonumber(ffifuncs.pnoise2(x, y)) + else + return tonumber(ffifuncs.pnoise1(x)) + end +end + +function love_math.simplexNoise(x, y, z, w) + if w ~= nil then + return tonumber(ffifuncs.snoise4(x, y, z, w)) + elseif z ~= nil then + return tonumber(ffifuncs.snoise3(x, y, z)) + elseif y ~= nil then + return tonumber(ffifuncs.snoise2(x, y)) + else + return tonumber(ffifuncs.snoise1(x)) + end +end + +local function gammaToLinear(c) + if c ~= nil then + return tonumber(ffifuncs.gammaToLinear(clamp01(c))) + end + return c +end + +function love_math.gammaToLinear(r, g, b, a) + if type(r) == "table" then + local t = r + return gammaToLinear(t[1]), gammaToLinear(t[2]), gammaToLinear(t[3]), t[4] + end + return gammaToLinear(r), gammaToLinear(g), gammaToLinear(b), a +end + +local function linearToGamma(c) + if c ~= nil then + return tonumber(ffifuncs.linearToGamma(clamp01(c))) + end + return c +end + +function love_math.linearToGamma(r, g, b, a) + if type(r) == "table" then + local t = r + return linearToGamma(t[1]), linearToGamma(t[2]), linearToGamma(t[3]), t[4] + end + return linearToGamma(r), linearToGamma(g), linearToGamma(b), a +end + +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/source/modules/math/wrap_MathModule.cpp b/source/modules/math/wrap_MathModule.cpp new file mode 100644 index 00000000..ea7377a6 --- /dev/null +++ b/source/modules/math/wrap_MathModule.cpp @@ -0,0 +1,432 @@ +#include "modules/math/wrap_MathModule.hpp" + +#include "modules/math/wrap_BezierCurve.hpp" +#include "modules/math/wrap_RandomGenerator.hpp" +#include "modules/math/wrap_Transform.hpp" + +#include +#include + +static constexpr char wrap_math_lua[] = { +#include "modules/math/wrap_Math.lua" +}; + +using namespace love; + +#define instance() (Module::getInstance(Module::M_MATH)) + +int Wrap_MathModule::_getRandomGenerator(lua_State* L) +{ + auto* random = instance()->getRandomGenerator(); + luax_pushtype(L, random); + + return 1; +} + +int Wrap_MathModule::newRandomGenerator(lua_State* L) +{ + RandomGenerator::Seed seed {}; + if (lua_gettop(L) > 0) + seed = luax_checkrandomseed(L, 1); + + RandomGenerator* random = instance()->newRandomGenerator(); + + if (lua_gettop(L) > 0) + { + bool shouldError = false; + + try + { + random->setSeed(seed); + } + catch (love::Exception& e) + { + random->release(); + shouldError = true; + lua_pushstring(L, e.what()); + } + + if (shouldError) + return luaL_error(L, "%s", lua_tostring(L, -1)); + } + + luax_pushtype(L, random); + random->release(); + + return 1; +} + +int Wrap_MathModule::newBezierCurve(lua_State* L) +{ + std::vector points {}; + + if (lua_istable(L, 1)) + { + int top = luax_objlen(L, 1); + points.reserve(top / 2); + + for (int index = 1; index <= top; index += 2) + { + lua_rawgeti(L, 1, index); + lua_rawgeti(L, 1, index + 1); + + Vector2 point {}; + point.x = luaL_checknumber(L, -2); + point.y = luaL_checknumber(L, -1); + + points.push_back(point); + + lua_pop(L, 2); + } + } + else + { + int top = lua_gettop(L); + points.reserve(top / 2); + + for (int index = 1; index <= top; index += 2) + { + Vector2 point {}; + point.x = luaL_checknumber(L, index); + point.y = luaL_checknumber(L, index + 1); + + points.push_back(point); + } + } + + auto* bezierCurve = instance()->newBezierCurve(points); + + luax_pushtype(L, bezierCurve); + bezierCurve->release(); + + return 1; +} + +int Wrap_MathModule::newTransform(lua_State* L) +{ + Transform* transform = nullptr; + + if (lua_isnoneornil(L, 1)) + transform = instance()->newTransform(); + else + { + float x = (float)luaL_checknumber(L, 1); + float y = (float)luaL_checknumber(L, 2); + float a = (float)luaL_optnumber(L, 3, 0.0); + float sx = (float)luaL_optnumber(L, 4, 1.0); + float sy = (float)luaL_optnumber(L, 5, sx); + float ox = (float)luaL_optnumber(L, 6, 0.0); + float oy = (float)luaL_optnumber(L, 7, 0.0); + float kx = (float)luaL_optnumber(L, 8, 0.0); + float ky = (float)luaL_optnumber(L, 9, 0.0); + + transform = instance()->newTransform(x, y, a, sx, sy, ox, oy, kx, ky); + } + + luax_pushtype(L, transform); + transform->release(); + + return 1; +} + +int Wrap_MathModule::triangulate(lua_State* L) +{ + std::vector vertices {}; + + if (lua_istable(L, 1)) + { + int top = luax_objlen(L, 1); + vertices.reserve(top / 2); + + for (int index = 1; index <= top; index += 2) + { + lua_rawgeti(L, 1, index); + lua_rawgeti(L, 1, index + 1); + + Vector2 point {}; + point.x = luaL_checknumber(L, -2); + point.y = luaL_checknumber(L, -1); + + vertices.push_back(point); + + lua_pop(L, 2); + } + } + else + { + int top = lua_gettop(L); + vertices.reserve(top / 2); + + for (int index = 1; index <= top; index += 2) + { + Vector2 point {}; + point.x = luaL_checknumber(L, index); + point.y = luaL_checknumber(L, index + 1); + + vertices.push_back(point); + } + } + + if (vertices.size() < 3) + return luaL_error(L, "Need at least 3 vertices to triangulate"); + + std::vector triangles {}; + + luax_catchexcept(L, [&]() { + if (vertices.size() == 3) + triangles.push_back(Triangle(vertices[0], vertices[1], vertices[2])); + else + triangles = love::triangulate(vertices); + }); + + lua_createtable(L, triangles.size(), 0); + + for (int index = 0; index < (int)triangles.size(); ++index) + { + const Triangle& triangle = triangles[index]; + + lua_createtable(L, 6, 0); + + lua_pushnumber(L, triangle.a.x); + lua_rawseti(L, -2, 1); + + lua_pushnumber(L, triangle.a.y); + lua_rawseti(L, -2, 2); + + lua_pushnumber(L, triangle.b.x); + lua_rawseti(L, -2, 3); + + lua_pushnumber(L, triangle.b.y); + lua_rawseti(L, -2, 4); + + lua_pushnumber(L, triangle.c.x); + lua_rawseti(L, -2, 5); + + lua_pushnumber(L, triangle.c.y); + lua_rawseti(L, -2, 6); + + lua_rawseti(L, -2, index + 1); + } + + return 1; +} + +int Wrap_MathModule::isConvex(lua_State* L) +{ + std::vector vertices {}; + + if (lua_istable(L, 1)) + { + int top = luax_objlen(L, 1); + vertices.reserve(top / 2); + + for (int index = 1; index <= top; index += 2) + { + lua_rawgeti(L, 1, index); + lua_rawgeti(L, 1, index + 1); + + Vector2 point {}; + point.x = luaL_checknumber(L, -2); + point.y = luaL_checknumber(L, -1); + + vertices.push_back(point); + + lua_pop(L, 2); + } + } + else + { + int top = lua_gettop(L); + vertices.reserve(top / 2); + + for (int index = 1; index <= top; index += 2) + { + Vector2 point {}; + point.x = luaL_checknumber(L, index); + point.y = luaL_checknumber(L, index + 1); + + vertices.push_back(point); + } + } + + luax_pushboolean(L, love::isConvex(vertices)); + + return 1; +} + +static int getGammaArguments(lua_State* L, float color[4]) +{ + int numComponents = 0; + + if (lua_istable(L, 1)) + { + int length = luax_objlen(L, 1); + + for (int index = 1; index <= length; index++) + { + lua_rawgeti(L, 1, index); + color[index - 1] = luax_checknumberclamped01(L, -1); + numComponents++; + } + + lua_pop(L, numComponents); + } + else + { + int length = lua_gettop(L); + + for (int index = 1; index <= length && index <= 4; index++) + { + color[index - 1] = luax_checknumberclamped01(L, index); + numComponents++; + } + } + + if (numComponents == 0) + luaL_checknumber(L, 1); + + return numComponents; +} + +int Wrap_MathModule::gammaToLinear(lua_State* L) +{ + float color[4] {}; + int numComponents = getGammaArguments(L, color); + + for (int index = 0; index < numComponents; index++) + { + if (index < 3) + color[index] = love::gammaToLinear(color[index]); + + lua_pushnumber(L, color[index]); + } + + return numComponents; +} + +int Wrap_MathModule::linearToGamma(lua_State* L) +{ + float color[4] {}; + int numComponents = getGammaArguments(L, color); + + for (int index = 0; index < numComponents; index++) + { + if (index < 3) + color[index] = love::linearToGamma(color[index]); + + lua_pushnumber(L, color[index]); + } + + return numComponents; +} + +int Wrap_MathModule::simplexNoise(lua_State* L) +{ + int argc = std::clamp(lua_gettop(L), 1, 4); + double args[4] {}; + + for (int index = 0; index < argc; index++) + args[index] = luaL_checknumber(L, index + 1); + + double value = 0.0; + + switch (argc) + { + case 1: + value = love::simplexNoise1(args[0]); + break; + case 2: + value = love::simplexNoise2(args[0], args[1]); + break; + case 3: + value = love::simplexNoise3(args[0], args[1], args[2]); + break; + case 4: + value = love::simplexNoise4(args[0], args[1], args[2], args[3]); + break; + } + + lua_pushnumber(L, value); + + return 1; +} + +int Wrap_MathModule::perlinNoise(lua_State* L) +{ + int argc = std::clamp(lua_gettop(L), 1, 4); + double args[4] {}; + + for (int index = 0; index < argc; index++) + args[index] = luaL_checknumber(L, index + 1); + + double value = 0.0; + + switch (argc) + { + case 1: + value = love::perlinNoise1(args[0]); + break; + case 2: + value = love::perlinNoise2(args[0], args[1]); + break; + case 3: + value = love::perlinNoise3(args[0], args[1], args[2]); + break; + case 4: + value = love::perlinNoise4(args[0], args[1], args[2], args[3]); + break; + } + + lua_pushnumber(L, value); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "_getRandomGenerator", Wrap_MathModule::_getRandomGenerator }, + { "newRandomGenerator", Wrap_MathModule::newRandomGenerator }, + { "newBezierCurve", Wrap_MathModule::newBezierCurve }, + { "newTransform", Wrap_MathModule::newTransform }, + { "triangulate", Wrap_MathModule::triangulate }, + { "isConvex", Wrap_MathModule::isConvex }, + { "gammaToLinear", Wrap_MathModule::gammaToLinear }, + { "linearToGamma", Wrap_MathModule::linearToGamma }, + { "simplexNoise", Wrap_MathModule::simplexNoise }, + { "perlinNoise", Wrap_MathModule::perlinNoise } +}; + +static constexpr lua_CFunction types[] = +{ + love::open_randomgenerator, + love::open_beziercurve, + love::open_transform +}; +// clang-format on + +int Wrap_MathModule::open(lua_State* L) +{ + auto* instance = instance(); + + if (instance == nullptr) + luax_catchexcept(L, [&]() { instance = new Math(); }); + else + instance->retain(); + + WrappedModule module {}; + module.instance = instance; + module.name = "math"; + module.type = &Module::type; + module.functions = functions; + module.types = types; + + int result = luax_register_module(L, module); + + luaL_loadbuffer(L, wrap_math_lua, sizeof(wrap_math_lua), "=[love \"wrap_Math.lua\"]"); + lua_pushvalue(L, -2); + lua_pushnil(L); + lua_call(L, 2, 0); + + return result; +} diff --git a/source/modules/math/wrap_RandomGenerator.cpp b/source/modules/math/wrap_RandomGenerator.cpp new file mode 100644 index 00000000..2e15a1f7 --- /dev/null +++ b/source/modules/math/wrap_RandomGenerator.cpp @@ -0,0 +1,129 @@ +#include "modules/math/wrap_RandomGenerator.hpp" + +#include +#include + +static constexpr char rng_lua[] = { +#include "modules/math/wrap_RandomGenerator.lua" +}; + +using namespace love; + +int Wrap_RandomGenerator::_random(lua_State* L) +{ + auto* self = luax_checkrandomgenerator(L, 1); + + lua_pushnumber(L, self->random()); + + return 1; +} + +int Wrap_RandomGenerator::randomNormal(lua_State* L) +{ + auto* self = luax_checkrandomgenerator(L, 1); + double stddev = luaL_optnumber(L, 2, 1.0); + double mean = luaL_optnumber(L, 3, 0.0); + + double r = self->randomNormal(stddev); + + lua_pushnumber(L, r + mean); + + return 1; +} + +int Wrap_RandomGenerator::setSeed(lua_State* L) +{ + auto* self = luax_checkrandomgenerator(L, 1); + + luax_catchexcept(L, [&] { self->setSeed(luax_checkrandomseed(L, 2)); }); + + return 0; +} + +int Wrap_RandomGenerator::getSeed(lua_State* L) +{ + auto* self = luax_checkrandomgenerator(L, 1); + + RandomGenerator::Seed seed = self->getSeed(); + + lua_pushnumber(L, seed.b32.low); + lua_pushnumber(L, seed.b32.high); + + return 2; +} + +int Wrap_RandomGenerator::setState(lua_State* L) +{ + auto* self = luax_checkrandomgenerator(L, 1); + + luax_catchexcept(L, [&] { self->setState(luaL_checkstring(L, 2)); }); + + return 0; +} + +int Wrap_RandomGenerator::getState(lua_State* L) +{ + auto* self = luax_checkrandomgenerator(L, 1); + + std::string state = self->getState(); + + luax_pushstring(L, state); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "_random", Wrap_RandomGenerator::_random }, + { "randomNormal", Wrap_RandomGenerator::randomNormal }, + { "setSeed", Wrap_RandomGenerator::setSeed }, + { "getSeed", Wrap_RandomGenerator::getSeed }, + { "setState", Wrap_RandomGenerator::setState }, + { "getState", Wrap_RandomGenerator::getState } +}; +// clang-format on + +namespace love +{ + RandomGenerator* luax_checkrandomgenerator(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + template + static T checkrandomseed_part(lua_State* L, int index) + { + double number = luaL_checknumber(L, index); + double infinity = std::numeric_limits::infinity(); + + if (number == infinity || number == -infinity || number != number) + luaL_argerror(L, index, "invalid random seed."); + + return (T)number; + } + + RandomGenerator::Seed luax_checkrandomseed(lua_State* L, int index) + { + RandomGenerator::Seed seed {}; + + if (!lua_isnoneornil(L, index + 1)) + { + seed.b32.low = checkrandomseed_part(L, index); + seed.b32.high = checkrandomseed_part(L, index + 1); + } + else + seed.b64 = checkrandomseed_part(L, index); + + return seed; + } + + int open_randomgenerator(lua_State* L) + { + int result = luax_register_type(L, &RandomGenerator::type, functions); + + luax_runwrapper(L, rng_lua, sizeof(rng_lua), "RandomGenerator.lua", RandomGenerator::type); + + return result; + } +} // namespace love diff --git a/source/modules/math/wrap_RandomGenerator.lua b/source/modules/math/wrap_RandomGenerator.lua new file mode 100644 index 00000000..e394833e --- /dev/null +++ b/source/modules/math/wrap_RandomGenerator.lua @@ -0,0 +1,96 @@ +R"luastring"--( +-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string. +-- There is a matching delimiter at the bottom of the file. + +--[[ +Copyright (c) 2006-2024 LOVE Development Team + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +--]] + +local RandomGenerator_mt, ffifuncspointer_str = ... +local RandomGenerator = RandomGenerator_mt.__index + +local type, tonumber, error = type, tonumber, error +local floor = math.floor + +local _random = RandomGenerator._random + +local function getrandom(r, l, u) + if u ~= nil then + if type(r) ~= "number" then error("bad argument #1 to 'random' (number expected)", 2) end + if type(l) ~= "number" then error("bad argument #2 to 'random' (number expected)", 2) end + return floor(r * (u - l + 1)) + l + elseif l ~= nil then + if type(l) ~= "number" then error("bad argument #1 to 'random' (number expected)", 2) end + return floor(r * l) + 1 + else + return r + end +end + +function RandomGenerator:random(l, u) + local r = _random(self) + return getrandom(r, l, u) +end + +if type(jit) ~= "table" or not jit.status() then + -- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT + -- compiler is disabled. + return +end + +local status, ffi = pcall(require, "ffi") +if not status then return end + +pcall(ffi.cdef, [[ +typedef struct Proxy Proxy; + +typedef struct FFI_RandomGenerator +{ + double (*random)(Proxy *p); + double (*randomNormal)(Proxy *p, double stddev, double mean); +} FFI_RandomGenerator; +]]) + +local ffifuncs = ffi.cast("FFI_RandomGenerator **", ffifuncspointer_str)[0] + + +-- Overwrite some regular love.math functions with FFI implementations. + +function RandomGenerator:random(l, u) + -- TODO: This should ideally be handled inside ffifuncs.random + if self == nil then error("bad argument #1 to 'random' (RandomGenerator expected, got no value)", 2) end + local r = tonumber(ffifuncs.random(self)) + return getrandom(r, l, u) +end + +function RandomGenerator:randomNormal(stddev, mean) + -- TODO: This should ideally be handled inside ffifuncs.randomNormal + if self == nil then error("bad argument #1 to 'randomNormal' (RandomGenerator expected, got no value)", 2) end + + stddev = stddev == nil and 1 or stddev + mean = mean == nil and 0 or mean + + if type(stddev) ~= "number" then error("bad argument #1 to 'randomNormal' (number expected)", 2) end + if type(mean) ~= "number" then error("bad argument #2 to 'randomNormal' (number expected)", 2) end + + return tonumber(ffifuncs.randomNormal(self, stddev, mean)) +end + +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/source/modules/math/wrap_Transform.cpp b/source/modules/math/wrap_Transform.cpp new file mode 100644 index 00000000..3b9dc068 --- /dev/null +++ b/source/modules/math/wrap_Transform.cpp @@ -0,0 +1,336 @@ +#include "modules/math/wrap_Transform.hpp" + +using namespace love; + +int Wrap_Transform::clone(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto* clone = self->clone(); + + luax_pushtype(L, clone); + clone->release(); + + return 1; +} + +int Wrap_Transform::inverse(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto* inverse = self->inverse(); + + luax_pushtype(L, inverse); + inverse->release(); + + return 1; +} + +int Wrap_Transform::apply(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto* other = luax_checktransform(L, 2); + + self->apply(other); + + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::isAffine2DTransform(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + + luax_pushboolean(L, self->getMatrix().isAffine2DTransform()); + + return 1; +} + +int Wrap_Transform::translate(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto x = static_cast(luaL_checknumber(L, 2)); + auto y = static_cast(luaL_checknumber(L, 3)); + + self->translate(x, y); + + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::rotate(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto angle = static_cast(luaL_checknumber(L, 2)); + + self->rotate(angle); + + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::scale(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto x = static_cast(luaL_checknumber(L, 2)); + auto y = static_cast(luaL_checknumber(L, 3)); + + self->scale(x, y); + + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::shear(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto x = static_cast(luaL_checknumber(L, 2)); + auto y = static_cast(luaL_checknumber(L, 3)); + + self->shear(x, y); + + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::reset(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + self->reset(); + + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::setTransformation(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + + float x = luaL_optnumber(L, 2, 0.0f); + float y = luaL_optnumber(L, 3, 0.0f); + float angle = luaL_optnumber(L, 4, 0.0f); + float sx = luaL_optnumber(L, 5, 0.0f); + float sy = luaL_optnumber(L, 6, sx); + float ox = luaL_optnumber(L, 7, 0.0f); + float oy = luaL_optnumber(L, 8, 0.0f); + float kx = luaL_optnumber(L, 9, 0.0f); + float ky = luaL_optnumber(L, 10, 0.0f); + + self->setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); + + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::setMatrix(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto layout = Transform::MATRIX_ROW_MAJOR; + + int index = 2; + if (lua_type(L, index) == LUA_TSTRING) + { + const char* layoutName = lua_tostring(L, index); + if (!Transform::getConstant(layoutName, layout)) + return luax_enumerror(L, "matrix layout", Transform::MatrixLayouts, layoutName); + + index++; + } + + float elements[16] {}; + luax_checkmatrix(L, index, layout, elements); + + self->setMatrix(Matrix4(elements)); + lua_pushvalue(L, 1); + + return 1; +} + +int Wrap_Transform::getMatrix(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + const float* elements = self->getMatrix().getElements(); + + for (int row = 0; row < 4; row++) + { + for (int column = 0; column < 4; column++) + lua_pushnumber(L, elements[column * 4 + row]); + } + + return 16; +} + +int Wrap_Transform::transformPoint(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + + Vector2 point {}; + point.x = luaL_checknumber(L, 2); + point.y = luaL_checknumber(L, 3); + + point = self->transformPoint(point); + + lua_pushnumber(L, point.x); + lua_pushnumber(L, point.y); + + return 2; +} + +int Wrap_Transform::inverseTransformPoint(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + + Vector2 point {}; + point.x = luaL_checknumber(L, 2); + point.y = luaL_checknumber(L, 3); + + point = self->inverseTransformPoint(point); + + lua_pushnumber(L, point.x); + lua_pushnumber(L, point.y); + + return 2; +} + +int Wrap_Transform::__mul(lua_State* L) +{ + auto* self = luax_checktransform(L, 1); + auto* other = luax_checktransform(L, 2); + + auto* result = new Transform(self->getMatrix() * other->getMatrix()); + + luax_pushtype(L, result); + result->release(); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "clone", Wrap_Transform::clone }, + { "inverse", Wrap_Transform::inverse }, + { "apply", Wrap_Transform::apply }, + { "isAffine2DTransform", Wrap_Transform::isAffine2DTransform }, + { "translate", Wrap_Transform::translate }, + { "rotate", Wrap_Transform::rotate }, + { "scale", Wrap_Transform::scale }, + { "shear", Wrap_Transform::shear }, + { "reset", Wrap_Transform::reset }, + { "setTransformation", Wrap_Transform::setTransformation }, + { "setMatrix", Wrap_Transform::setMatrix }, + { "getMatrix", Wrap_Transform::getMatrix }, + { "transformPoint", Wrap_Transform::transformPoint }, + { "inverseTransformPoint", Wrap_Transform::inverseTransformPoint }, + { "__mul", Wrap_Transform::__mul } +}; +// clang-format on + +namespace love +{ + Transform* luax_checktransform(lua_State* L, int index) + { + return luax_checktype(L, index, Transform::type); + } + + void luax_checkmatrix(lua_State* L, int index, Transform::MatrixLayout layout, float elements[16]) + { + const auto columnMajor = layout == Transform::MATRIX_COLUMN_MAJOR; + + if (lua_istable(L, index)) + { + lua_rawgeti(L, index, 1); + const auto tableOfTables = lua_istable(L, -1); + lua_pop(L, 1); + + if (tableOfTables) + { + if (columnMajor) + { + for (int column = 0; column < 4; column++) + { + lua_rawgeti(L, index, column + 1); + + for (int row = 0; row < 4; row++) + { + lua_rawgeti(L, -(row + 1), row + 1); + elements[column * 4 + row] = luaL_checknumber(L, -1); + } + + lua_pop(L, 4 + 1); + } + } + else + { + for (int row = 0; row < 4; row++) + { + lua_rawgeti(L, index, row + 1); + + for (int column = 0; column < 4; column++) + { + // The table has the matrix elements laid out in row-major + // order, but we need to store them column-major in memory. + lua_rawgeti(L, -(column + 1), column + 1); + elements[column * 4 + row] = luaL_checknumber(L, -1); + } + + lua_pop(L, 4 + 1); + } + } + } + else + { + if (columnMajor) + { + for (int column = 0; column < 4; column++) + { + for (int row = 0; row < 4; row++) + { + lua_rawgeti(L, index, column * 4 + row + 1); + elements[column * 4 + row] = luaL_checknumber(L, -1); + } + } + } + else + { + for (int column = 0; column < 4; column++) + { + for (int row = 0; row < 4; row++) + { + lua_rawgeti(L, index, row * 4 + column + 1); + elements[column * 4 + row] = luaL_checknumber(L, -1); + } + } + } + + lua_pop(L, 16); + } + } + else + { + if (columnMajor) + { + for (int i = 0; i < 16; i++) + elements[index] = luaL_checknumber(L, index + i); + } + else + { + for (int column = 0; column < 4; column++) + { + for (int row = 0; row < 4; row++) + elements[column * 4 + row] = luaL_checknumber(L, row * 4 + column + index); + } + } + } + } + + int open_transform(lua_State* L) + { + return luax_register_type(L, &Transform::type, functions); + } +} // namespace love