some set up for camera, work started on own vector/matrix structures

This commit is contained in:
matthew 2025-07-27 17:50:35 +10:00
parent e429cee207
commit e2c6bf1b24
30 changed files with 3037 additions and 301 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -9,8 +9,8 @@
"targetPath": "build",
"sourceFiles-linux": ["build/libvma.a", "build/libstb_image.a", "build/libm3d.a"],
"sourceFiles-windows": [],
"importPaths": ["src/gears", "src/shared", "src/generated", "external/xxhash", "external/dplug/math", "external/inteli"],
"sourcePaths": ["src/gears", "src/shared", "src/generated", "external/xxhash", "external/dplug/math", "external/inteli"],
"importPaths": ["src/gears", "src/shared", "src/generated", "external/xxhash", "external/gfm", "external/inteli"],
"sourcePaths": ["src/gears", "src/shared", "src/generated", "external/xxhash", "external/gfm", "external/inteli"],
"libs-linux": ["xcb", "X11", "X11-xcb", "vulkan", "stdc++"],
"libs-windows": [],
"preGenerateCommands-linux": ["./build.sh", "build/Packer"],
@ -22,8 +22,8 @@
"targetType": "executable",
"targetPath": "build",
"targetName": "Packer",
"importPaths": ["src/packer", "src/shared", "src/generated", "external/xxhash", "external/dplug/math", "external/inteli"],
"sourcePaths": ["src/packer", "src/shared", "src/generated", "external/xxhash", "external/dplug/math", "external/inteli"],
"importPaths": ["src/packer", "src/shared", "src/generated", "external/xxhash", "external/gfm", "external/inteli"],
"sourcePaths": ["src/packer", "src/shared", "src/generated", "external/xxhash", "external/gfm", "external/inteli"],
"sourceFiles-linux": ["build/libstb_image.a", "build/libm3d.a"],
"preGenerateCommands-linux": ["./build.sh"],
"postGenerateCommands-linux": [],
@ -35,8 +35,8 @@
"targetType": "executable",
"targetPath": "build",
"targetName": "Codegen",
"importPaths": ["src/codegen", "src/shared", "external/xxhash", "external/dplug/math", "external/inteli"],
"sourcePaths": ["src/codegen", "src/shared", "external/xxhash", "external/dplug/math", "external/inteli"],
"importPaths": ["src/codegen", "src/shared", "external/xxhash", "external/gfm", "external/inteli"],
"sourcePaths": ["src/codegen", "src/shared", "external/xxhash", "external/gfm", "external/inteli"],
"sourceFiles-linux": ["build/libstb_image.a"],
"preGenerateCommands-linux": ["./build.sh"],
"preGenerateCommands-windows": [],

View File

@ -1,12 +0,0 @@
/**
* Math package: rectangles, vectors, matrices.
*
* Copyright: Copyright Guillaume Piolat 2021.
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Note: this is part of the former gfm:math package, hence containing copyright from many GFM contributors.
*/
module dplug.math;
public import dplug.math.vector,
dplug.math.box,
dplug.math.matrix;

View File

@ -1,20 +1,15 @@
/**
* N-dimensional half-open interval [a, b[.
*
* Copyright: Copyright Guillaume Piolat 2015-2021.
* Copyright Ahmet Sait 2021.
* Copyright Ryan Roden-Corrent 2016.
* Copyright Nathan Sashihara 2018.
* Copyright Colden Cullen 2014.
*
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
This module implements a generic axis-aligned bounding box (AABB).
*/
module dplug.math.box;
module gfm.math.box;
import std.math,
std.traits;
std.traits,
std.conv,
std.string;
import dplug.math.vector;
import gfm.math.vector,
gfm.math.funcs;
/// N-dimensional half-open interval [a, b[.
struct Box(T, int N)
@ -317,52 +312,6 @@ struct Box(T, int N)
return Box(min + offset, max + offset);
}
/// Scale the box by factor `scale`, and round the result to integer if needed.
@nogc Box scaleByFactor(float scale) const nothrow
{
Box res;
static if (isFloatingPoint!T)
{
res.min.x = min.x * scale;
res.min.y = min.y * scale;
res.max.x = max.x * scale;
res.max.y = max.y * scale;
}
else
{
res.min.x = cast(T)( round(min.x * scale) );
res.min.y = cast(T)( round(min.y * scale) );
res.max.x = cast(T)( round(max.x * scale) );
res.max.y = cast(T)( round(max.y * scale) );
}
return res;
}
static if (N == 2) // useful for UI that have horizontal and vertical scale
{
/// Scale the box by factor `scaleX` horizontally and `scaleY` vetically.
/// Round the result to integer if needed.
@nogc Box scaleByFactor(float scaleX, float scaleY) const nothrow
{
Box res;
static if (isFloatingPoint!T)
{
res.min.x = min.x * scaleX;
res.min.y = min.y * scaleY;
res.max.x = max.x * scaleX;
res.max.y = max.y * scaleY;
}
else
{
res.min.x = cast(T)( round(min.x * scaleX) );
res.min.y = cast(T)( round(min.y * scaleY) );
res.max.x = cast(T)( round(max.x * scaleX) );
res.max.y = cast(T)( round(max.y * scaleY) );
}
return res;
}
}
static if (N >= 2)
{
/// Translate this Box by `x`, `y`.
@ -404,7 +353,7 @@ struct Box(T, int N)
/// Returns: Expanded box.
@nogc Box expand(bound_t point) pure const nothrow
{
import vector = dplug.math.vector;
import vector = gfm.math.vector;
return Box(vector.minByElem(min, point), vector.maxByElem(max, point));
}
@ -661,9 +610,6 @@ unittest
assert(rectangle(1, 2, 3, 4) == box2i(1, 2, 4, 6));
assert(rectanglef(1, 2, 3, 4) == box2f(1, 2, 4, 6));
assert(rectangled(1, 2, 3, 4) == box2d(1, 2, 4, 6));
assert(rectangle(10, 10, 20, 20).scaleByFactor(1.5f) == rectangle(15, 15, 30, 30));
assert(rectangle(10, 10, 20, 20).scaleByFactor(1.5f, 2.0f) == rectangle(15, 20, 30, 40));
}
/// True if `T` is a kind of Box
@ -687,3 +633,27 @@ unittest
static assert(is(DimensionType!box3d == double));
}
private
{
static string generateLoopCode(string formatString, int N)() pure nothrow
{
string result;
for (int i = 0; i < N; ++i)
{
string index = ctIntToString(i);
// replace all @ by indices
result ~= formatString.replace("@", index);
}
return result;
}
// Speed-up CTFE conversions
static string ctIntToString(int n) pure nothrow
{
static immutable string[16] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
if (n < 10)
return table[n];
else
return to!string(n);
}
}

486
external/gfm/math/funcs.d vendored Normal file
View File

@ -0,0 +1,486 @@
/**
Useful math functions and range-based statistic computations.
If you need real statistics, consider using the $(WEB github.com/dsimcha/dstats,Dstats) library.
*/
module gfm.math.funcs;
import std.math,
std.traits,
std.range,
std.math;
import inteli.xmmintrin;
import gfm.math.vector : Vector;
/// Convert from radians to degrees.
@nogc T degrees(T)(in T x) pure nothrow
if (isFloatingPoint!T || (is(T : Vector!(U, n), U, int n) && isFloatingPoint!U))
{
static if (is(T : Vector!(U, n), U, int n))
return x * U(180 / PI);
else
return x * T(180 / PI);
}
/// Convert from degrees to radians.
@nogc T radians(T)(in T x) pure nothrow
if (isFloatingPoint!T || (is(T : Vector!(U, n), U, int n) && isFloatingPoint!U))
{
static if (is(T : Vector!(U, n), U, int n))
return x * U(PI / 180);
else
return x * T(PI / 180);
}
/// Linear interpolation, akin to GLSL's mix.
@nogc S lerp(S, T)(S a, S b, T t) pure nothrow
if (is(typeof(t * b + (1 - t) * a) : S))
{
return t * b + (1 - t) * a;
}
/// Clamp x in [min, max], akin to GLSL's clamp.
@nogc T clamp(T)(T x, T min, T max) pure nothrow
{
if (x < min)
return min;
else if (x > max)
return max;
else
return x;
}
/// Integer truncation.
@nogc long ltrunc(real x) nothrow // may be pure but trunc isn't pure
{
return cast(long)(trunc(x));
}
/// Integer flooring.
@nogc long lfloor(real x) nothrow // may be pure but floor isn't pure
{
return cast(long)(floor(x));
}
/// Returns: Fractional part of x.
@nogc T fract(T)(real x) nothrow
{
return x - lfloor(x);
}
/// Safe asin: input clamped to [-1, 1]
@nogc T safeAsin(T)(T x) pure nothrow
{
return asin(clamp!T(x, -1, 1));
}
/// Safe acos: input clamped to [-1, 1]
@nogc T safeAcos(T)(T x) pure nothrow
{
return acos(clamp!T(x, -1, 1));
}
/// Same as GLSL step function.
/// 0.0 is returned if x < edge, and 1.0 is returned otherwise.
@nogc T step(T)(T edge, T x) pure nothrow
{
return (x < edge) ? 0 : 1;
}
/// Same as GLSL smoothstep function.
/// See: http://en.wikipedia.org/wiki/Smoothstep
@nogc T smoothStep(T)(T a, T b, T t) pure nothrow
{
if (t <= a)
return 0;
else if (t >= b)
return 1;
else
{
T x = (t - a) / (b - a);
return x * x * (3 - 2 * x);
}
}
/// Returns: true of i is a power of 2.
@nogc bool isPowerOf2(T)(T i) pure nothrow if (isIntegral!T)
{
assert(i >= 0);
return (i != 0) && ((i & (i - 1)) == 0);
}
/// Integer log2
@nogc int ilog2(T)(T i) nothrow if (isIntegral!T)
{
assert(i > 0);
assert(isPowerOf2(i));
import core.bitop : bsr;
return bsr(i);
}
/// Computes next power of 2.
@nogc int nextPowerOf2(int i) pure nothrow
{
int v = i - 1;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
assert(isPowerOf2(v));
return v;
}
/// Computes next power of 2.
@nogc long nextPowerOf2(long i) pure nothrow
{
long v = i - 1;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v |= v >> 32;
v++;
assert(isPowerOf2(v));
return v;
}
/// Computes sin(x)/x accurately.
/// See_also: $(WEB www.plunk.org/~hatch/rightway.php)
@nogc T sinOverX(T)(T x) pure nothrow
{
if (1 + x * x == 1)
return 1;
else
return sin(x) / x;
}
/// Signed integer modulo a/b where the remainder is guaranteed to be in [0..b[,
/// even if a is negative. Only support positive dividers.
@nogc T moduloWrap(T)(T a, T b) pure nothrow if (isSigned!T)
{
assert(b > 0);
if (a >= 0)
a = a % b;
else
{
auto rem = a % b;
x = (rem == 0) ? 0 : (-rem + b);
}
assert(x >= 0 && x < b);
return x;
}
unittest
{
assert(nextPowerOf2(13) == 16);
}
/**
* Find the root of a linear polynomial a + b x = 0
* Returns: Number of roots.
*/
@nogc int solveLinear(T)(T a, T b, out T root) pure nothrow if (isFloatingPoint!T)
{
if (b == 0)
{
return 0;
}
else
{
root = -a / b;
return 1;
}
}
/**
* Finds the root roots of a quadratic polynomial a + b x + c x^2 = 0
* Params:
* a = Coefficient.
* b = Coefficient.
* c = Coefficient.
* outRoots = array of root results, should have room for at least 2 elements.
* Returns: Number of roots in outRoots.
*/
@nogc int solveQuadratic(T)(T a, T b, T c, T[] outRoots) pure nothrow if (isFloatingPoint!T)
{
assert(outRoots.length >= 2);
if (c == 0)
return solveLinear(a, b, outRoots[0]);
T delta = b * b - 4 * a * c;
if (delta < 0.0 )
return 0;
delta = sqrt(delta);
T oneOver2a = 0.5 / a;
outRoots[0] = oneOver2a * (-b - delta);
outRoots[1] = oneOver2a * (-b + delta);
return 2;
}
/**
* Finds the roots of a cubic polynomial a + b x + c x^2 + d x^3 = 0
* Params:
* a = Coefficient.
* b = Coefficient.
* c = Coefficient.
* d = Coefficient.
* outRoots = array of root results, should have room for at least 2 elements.
* Returns: Number of roots in outRoots.
* See_also: $(WEB www.codeguru.com/forum/archive/index.php/t-265551.html)
*/
@nogc int solveCubic(T)(T a, T b, T c, T d, T[] outRoots) pure nothrow if (isFloatingPoint!T)
{
assert(outRoots.length >= 3);
if (d == 0)
return solveQuadratic(a, b, c, outRoots);
// adjust coefficients
T a1 = c / d,
a2 = b / d,
a3 = a / d;
T Q = (a1 * a1 - 3 * a2) / 9,
R = (2 * a1 * a1 * a1 - 9 * a1 * a2 + 27 * a3) / 54;
T Qcubed = Q * Q * Q;
T d2 = Qcubed - R * R;
if (d2 >= 0)
{
// 3 real roots
if (Q < 0.0)
return 0;
T P = R / sqrt(Qcubed);
assert(-1 <= P && P <= 1);
T theta = acos(P);
T sqrtQ = sqrt(Q);
outRoots[0] = -2 * sqrtQ * cos(theta / 3) - a1 / 3;
outRoots[1] = -2 * sqrtQ * cos((theta + 2 * T(PI)) / 3) - a1 / 3;
outRoots[2] = -2 * sqrtQ * cos((theta + 4 * T(PI)) / 3) - a1 / 3;
return 3;
}
else
{
// 1 real root
T e = (sqrt(-d) + abs(R)) ^^ cast(T)(1.0 / 3.0);
if (R > 0)
e = -e;
outRoots[0] = e + Q / e - a1 / 3.0;
return 1;
}
}
/**
* Returns the roots of a quartic polynomial a + b x + c x^2 + d x^3 + e x^4 = 0
*
* Returns number of roots. roots slice should have room for up to 4 elements.
* Bugs: doesn't pass unit-test!
* See_also: $(WEB mathworld.wolfram.com/QuarticEquation.html)
*/
@nogc int solveQuartic(T)(T a, T b, T c, T d, T e, T[] roots) pure nothrow if (isFloatingPoint!T)
{
assert(roots.length >= 4);
if (e == 0)
return solveCubic(a, b, c, d, roots);
// Adjust coefficients
T a0 = a / e,
a1 = b / e,
a2 = c / e,
a3 = d / e;
// Find a root for the following cubic equation:
// y^3 - a2 y^2 + (a1 a3 - 4 a0) y + (4 a2 a0 - a1 ^2 - a3^2 a0) = 0
// aka Resolvent cubic
T b0 = 4 * a2 * a0 - a1 * a1 - a3 * a3 * a0;
T b1 = a1 * a3 - 4 * a0;
T b2 = -a2;
T[3] resolventCubicRoots;
int numRoots = solveCubic!T(b0, b1, b2, 1, resolventCubicRoots[]);
assert(numRoots == 3);
T y = resolventCubicRoots[0];
if (y < resolventCubicRoots[1]) y = resolventCubicRoots[1];
if (y < resolventCubicRoots[2]) y = resolventCubicRoots[2];
// Compute R, D & E
T R = 0.25f * a3 * a3 - a2 + y;
if (R < 0.0)
return 0;
R = sqrt(R);
T D = void,
E = void;
if (R == 0)
{
T d1 = 0.75f * a3 * a3 - 2 * a2;
T d2 = 2 * sqrt(y * y - 4 * a0);
D = sqrt(d1 + d2) * 0.5f;
E = sqrt(d1 - d2) * 0.5f;
}
else
{
T Rsquare = R * R;
T Rrec = 1 / R;
T d1 = 0.75f * a3 * a3 - Rsquare - 2 * a2;
T d2 = 0.25f * Rrec * (4 * a3 * a2 - 8 * a1 - a3 * a3 * a3);
D = sqrt(d1 + d2) * 0.5f;
E = sqrt(d1 - d2) * 0.5f;
}
// Compute the 4 roots
a3 *= -0.25f;
R *= 0.5f;
roots[0] = a3 + R + D;
roots[1] = a3 + R - D;
roots[2] = a3 - R + E;
roots[3] = a3 - R - E;
return 4;
}
unittest
{
bool arrayContainsRoot(double[] arr, double root)
{
foreach(e; arr)
if (abs(e - root) < 1e-7)
return true;
return false;
}
// test quadratic
{
double[3] roots;
int numRoots = solveCubic!double(-2, -3 / 2.0, 3 / 4.0, 1 / 4.0, roots[]);
assert(numRoots == 3);
assert(arrayContainsRoot(roots[], -4));
assert(arrayContainsRoot(roots[], -1));
assert(arrayContainsRoot(roots[], 2));
}
// test quartic
{
double[4] roots;
int numRoots = solveQuartic!double(0, -2, -1, 2, 1, roots[]);
assert(numRoots == 4);
assert(arrayContainsRoot(roots[], -2));
assert(arrayContainsRoot(roots[], -1));
assert(arrayContainsRoot(roots[], 0));
assert(arrayContainsRoot(roots[], 1));
}
}
/// Arithmetic mean.
double average(R)(R r) if (isInputRange!R)
{
if (r.empty)
return double.nan;
typeof(r.front()) sum = 0;
long count = 0;
foreach(e; r)
{
sum += e;
++count;
}
return sum / count;
}
/// Minimum of a range.
double minElement(R)(R r) if (isInputRange!R)
{
// do like Javascript for an empty range
if (r.empty)
return double.infinity;
return minmax!("<", R)(r);
}
/// Maximum of a range.
double maxElement(R)(R r) if (isInputRange!R)
{
// do like Javascript for an empty range
if (r.empty)
return -double.infinity;
return minmax!(">", R)(r);
}
/// Variance of a range.
double variance(R)(R r) if (isForwardRange!R)
{
if (r.empty)
return double.nan;
auto avg = average(r.save); // getting the average
typeof(avg) sum = 0;
long count = 0;
foreach(e; r)
{
sum += (e - avg) ^^ 2;
++count;
}
if (count <= 1)
return 0.0;
else
return (sum / (count - 1.0)); // using sample std deviation as estimator
}
/// Standard deviation of a range.
double standardDeviation(R)(R r) if (isForwardRange!R)
{
return sqrt(variance(r));
}
private
{
typeof(R.front()) minmax(string op, R)(R r) if (isInputRange!R)
{
assert(!r.empty);
auto best = r.front();
r.popFront();
foreach(e; r)
{
mixin("if (e " ~ op ~ " best) best = e;");
}
return best;
}
}
/// SSE approximation of reciprocal square root.
@nogc T inverseSqrt(T)(T x) pure nothrow if (isFloatingPoint!T)
{
static if (is(T == float))
{
__m128 V = _mm_set_ss(x);
V = _mm_rsqrt_ss(V);
return _mm_cvtss_f32(V);
}
else
{
return 1 / sqrt(x);
}
}
unittest
{
assert(abs( inverseSqrt!float(1) - 1) < 1e-3 );
assert(abs( inverseSqrt!double(1) - 1) < 1e-3 );
}

View File

@ -1,21 +1,16 @@
/**
* Custom sized 2D Matrices.
*
* Copyright: Copyright Guillaume Piolat 2015-2021.
* Copyright Aleksandr Druzhinin 2016-2020.
* Copyright Nathan Sashihara 2018.
* Copyright Thibaut Charles 2018.
*
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
*/
module dplug.math.matrix;
/// Custom sized 2-dimension Matrices
module gfm.math.matrix;
import std.math,
std.typetuple,
std.traits,
std.typecons;
std.string,
std.typecons,
std.conv;
import dplug.math.vector;
import gfm.math.vector,
gfm.math.shapes,
gfm.math.quaternion;
/// Generic non-resizeable matrix with R rows and C columns.
/// Intended for 3D use (size 3x3 and 4x4).
@ -187,6 +182,15 @@ struct Matrix(T, int R, int C)
return rows[i];
}
/// Covnerts to pretty string.
string toString() const nothrow
{
try
return format("%s", v);
catch (Exception e)
assert(false); // should not happen since format is right
}
/// Matrix * scalar multiplication.
@nogc Matrix opBinary(string op)(T factor) pure const nothrow if (op == "*")
{
@ -318,6 +322,85 @@ struct Matrix(T, int R, int C)
return res;
}
/// Convert 3x3 rotation matrix to quaternion.
/// See_also: 3D Math Primer for Graphics and Game Development.
@nogc U opCast(U)() pure const nothrow if (isQuaternionInstantiation!U
&& is(U._T : _T)
&& (_R == 3) && (_C == 3))
{
T fourXSquaredMinus1 = c[0][0] - c[1][1] - c[2][2];
T fourYSquaredMinus1 = c[1][1] - c[0][0] - c[2][2];
T fourZSquaredMinus1 = c[2][2] - c[0][0] - c[1][1];
T fourWSquaredMinus1 = c[0][0] + c[1][1] + c[2][2];
int biggestIndex = 0;
T fourBiggestSquaredMinus1 = fourWSquaredMinus1;
if(fourXSquaredMinus1 > fourBiggestSquaredMinus1)
{
fourBiggestSquaredMinus1 = fourXSquaredMinus1;
biggestIndex = 1;
}
if(fourYSquaredMinus1 > fourBiggestSquaredMinus1)
{
fourBiggestSquaredMinus1 = fourYSquaredMinus1;
biggestIndex = 2;
}
if(fourZSquaredMinus1 > fourBiggestSquaredMinus1)
{
fourBiggestSquaredMinus1 = fourZSquaredMinus1;
biggestIndex = 3;
}
T biggestVal = sqrt(fourBiggestSquaredMinus1 + 1) / 2;
T mult = 1 / (biggestVal * 4);
U quat;
switch(biggestIndex)
{
case 1:
quat.w = (c[1][2] - c[2][1]) * mult;
quat.x = biggestVal;
quat.y = (c[0][1] + c[1][0]) * mult;
quat.z = (c[2][0] + c[0][2]) * mult;
break;
case 2:
quat.w = (c[2][0] - c[0][2]) * mult;
quat.x = (c[0][1] + c[1][0]) * mult;
quat.y = biggestVal;
quat.z = (c[1][2] + c[2][1]) * mult;
break;
case 3:
quat.w = (c[0][1] - c[1][0]) * mult;
quat.x = (c[2][0] + c[0][2]) * mult;
quat.y = (c[1][2] + c[2][1]) * mult;
quat.z = biggestVal;
break;
default: // biggestIndex == 0
quat.w = biggestVal;
quat.x = (c[1][2] - c[2][1]) * mult;
quat.y = (c[2][0] - c[0][2]) * mult;
quat.z = (c[0][1] - c[1][0]) * mult;
break;
}
return quat;
}
/// Converts a 4x4 rotation matrix to quaternion.
@nogc U opCast(U)() pure const nothrow if (isQuaternionInstantiation!U
&& is(U._T : _T)
&& (_R == 4) && (_C == 4))
{
auto m3 = cast(mat3!T)(this);
return cast(U)(m3);
}
static if (isSquare && isFloatingPoint!T && R == 1)
{
/// Returns an inverted copy of this matrix
@ -614,6 +697,19 @@ struct Matrix(T, int R, int C)
Z.x, Z.y, Z.z, -dot(Z, eye),
0, 0, 0, 1);
}
/// Extract frustum from a 4x4 matrice.
@nogc Frustum!T frustum() pure const nothrow
{
auto left = Plane!T(row(3) + row(0));
auto right = Plane!T(row(3) - row(0));
auto top = Plane!T(row(3) - row(1));
auto bottom = Plane!T(row(3) + row(1));
auto near = Plane!T(row(3) + row(2));
auto far = Plane!T(row(3) - row(2));
return Frustum!T(left, right, top, bottom, near, far);
}
}
}

9
external/gfm/math/package.d vendored Normal file
View File

@ -0,0 +1,9 @@
module gfm.math;
public import gfm.math.funcs,
gfm.math.vector,
gfm.math.box,
gfm.math.matrix,
gfm.math.quaternion,
gfm.math.shapes,
gfm.math.simplerng;

339
external/gfm/math/quaternion.d vendored Normal file
View File

@ -0,0 +1,339 @@
///
module gfm.math.quaternion;
import std.math,
std.string;
import gfm.math.vector,
gfm.math.matrix,
funcs = gfm.math.funcs;
/// Quaternion implementation.
/// Holds a rotation + angle in a proper but wild space.
struct Quaternion(T)
{
public
{
union
{
Vector!(T, 4u) v;
struct
{
T x, y, z, w;
}
}
/// Construct a Quaternion from a value.
@nogc this(U)(U x) pure nothrow if (isAssignable!U)
{
opAssign!U(x);
}
/// Constructs a Quaternion from coordinates.
/// Warning: order of coordinates is different from storage.
@nogc this(T qw, T qx, T qy, T qz) pure nothrow
{
x = qx;
y = qy;
z = qz;
w = qw;
}
/// Constructs a Quaternion from axis + angle.
@nogc static Quaternion fromAxis(Vector!(T, 3) axis, T angle) pure nothrow
{
Quaternion q = void;
axis.normalize();
T cos_a = cos(angle / 2);
T sin_a = sin(angle / 2);
q.x = sin_a * axis.x;
q.y = sin_a * axis.y;
q.z = sin_a * axis.z;
q.w = cos_a;
return q; // should be normalized
}
/// Constructs a Quaternion from Euler angles.
/// All paramers given in radians.
/// Roll->X axis, Pitch->Y axis, Yaw->Z axis
/// See_also: $(LINK https://www.cs.princeton.edu/~gewang/projects/darth/stuff/quat_faq.html)
@nogc static Quaternion fromEulerAngles(T roll, T pitch, T yaw) pure nothrow
{
Quaternion q = void;
T sinPitch = sin(pitch / 2);
T cosPitch = cos(pitch / 2);
T sinYaw = sin(yaw / 2);
T cosYaw = cos(yaw / 2);
T sinRoll = sin(roll / 2);
T cosRoll = cos(roll / 2);
T cosPitchCosYaw = cosPitch * cosYaw;
T sinPitchSinYaw = sinPitch * sinYaw;
q.x = sinRoll * cosPitchCosYaw - cosRoll * sinPitchSinYaw;
q.y = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw;
q.z = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw;
q.w = cosRoll * cosPitchCosYaw + sinRoll * sinPitchSinYaw;
return q;
}
/// Converts a quaternion to Euler angles.
/// TODO: adds a EulerAngles type.
/// Returns: A vector which contains roll-pitch-yaw as x-y-z.
@nogc vec3!T toEulerAngles() pure const nothrow
{
mat3!T m = cast(mat3!T)(this);
T pitch, yaw, roll;
T s = sqrt(m.c[0][0] * m.c[0][0] + m.c[1][0] * m.c[1][0]);
if (s > T.epsilon)
{
pitch = - atan2(m.c[2][0], s);
yaw = atan2(m.c[1][0], m.c[0][0]);
roll = atan2(m.c[2][1], m.c[2][2]);
}
else
{
pitch = m.c[2][0] < 0.0f ? T(PI) /2 : -T(PI) / 2;
yaw = -atan2(m.c[0][1], m.c[1][1]);
roll = 0.0f;
}
return vec3!T(roll, pitch, yaw);
}
/// Assign from another Quaternion.
@nogc ref Quaternion opAssign(U)(U u) pure nothrow if (isQuaternionInstantiation!U && is(U._T : T))
{
v = u.v;
return this;
}
/// Assign from a vector of 4 elements.
@nogc ref Quaternion opAssign(U)(U u) pure nothrow if (is(U : Vector!(T, 4u)))
{
v = u;
return this;
}
/// Converts to a pretty string.
string toString() const nothrow
{
try
return format("%s", v);
catch (Exception e)
assert(false); // should not happen since format is right
}
/// Normalizes a quaternion.
@nogc void normalize() pure nothrow
{
v.normalize();
}
/// Returns: Normalized quaternion.
@nogc Quaternion normalized() pure const nothrow
{
Quaternion res = void;
res.v = v.normalized();
return res;
}
/// Inverses a quaternion in-place.
@nogc void inverse() pure nothrow
{
x = -x;
y = -y;
z = -z;
}
/// Returns: Inverse of quaternion.
@nogc Quaternion inversed() pure const nothrow
{
Quaternion res = void;
res.v = v;
res.inverse();
return res;
}
@nogc ref Quaternion opOpAssign(string op, U)(U q) pure nothrow
if (is(U : Quaternion) && (op == "*"))
{
T nx = w * q.x + x * q.w + y * q.z - z * q.y,
ny = w * q.y + y * q.w + z * q.x - x * q.z,
nz = w * q.z + z * q.w + x * q.y - y * q.x,
nw = w * q.w - x * q.x - y * q.y - z * q.z;
x = nx;
y = ny;
z = nz;
w = nw;
return this;
}
@nogc ref Quaternion opOpAssign(string op, U)(U operand) pure nothrow if (isConvertible!U)
{
Quaternion conv = operand;
return opOpAssign!op(conv);
}
@nogc Quaternion opBinary(string op, U)(U operand) pure const nothrow
if (is(U: Quaternion) || (isConvertible!U))
{
Quaternion temp = this;
return temp.opOpAssign!op(operand);
}
/// Compare two Quaternions.
bool opEquals(U)(U other) pure const if (is(U : Quaternion))
{
return v == other.v;
}
/// Compare Quaternion and other types.
bool opEquals(U)(U other) pure const nothrow if (isConvertible!U)
{
Quaternion conv = other;
return opEquals(conv);
}
/// Convert to a 3x3 rotation matrix.
/// TODO: check out why we can't do is(Unqual!U == mat3!T)
@nogc U opCast(U)() pure const nothrow if (isMatrixInstantiation!U
&& is(U._T : _T)
&& (U._R == 3) && (U._C == 3))
{
// do not assume that quaternion is normalized
T norm = x*x + y*y + z*z + w*w;
T s = (norm > 0) ? 2 / norm : 0;
T xx = x * x * s, xy = x * y * s, xz = x * z * s, xw = x * w * s,
yy = y * y * s, yz = y * z * s, yw = y * w * s,
zz = z * z * s, zw = z * w * s;
return mat3!(U._T)
(
1 - (yy + zz) , (xy - zw) , (xz + yw) ,
(xy + zw) , 1 - (xx + zz) , (yz - xw) ,
(xz - yw) , (yz + xw) , 1 - (xx + yy)
);
}
/// Converts a to a 4x4 rotation matrix.
/// Bugs: check why we can't do is(Unqual!U == mat4!T)
@nogc U opCast(U)() pure const nothrow if (isMatrixInstantiation!U
&& is(U._T : _T)
&& (U._R == 4) && (U._C == 4))
{
auto m3 = cast(mat3!(U._T))(this);
return cast(U)(m3);
}
/// Workaround Vector not being constructable through CTFE
@nogc static Quaternion identity() pure nothrow @property
{
Quaternion q;
q.x = q.y = q.z = 0;
q.w = 1;
return q;
}
}
private
{
alias T _T;
template isAssignable(T)
{
enum bool isAssignable =
is(typeof(
{
T x;
Quaternion v = x;
}()));
}
// define types that can be converted to Quaternion, but are not Quaternion
template isConvertible(T)
{
enum bool isConvertible = (!is(T : Quaternion)) && isAssignable!T;
}
}
}
template isQuaternionInstantiation(U)
{
private static void isQuaternion(T)(Quaternion!T x)
{
}
enum bool isQuaternionInstantiation = is(typeof(isQuaternion(U.init)));
}
alias Quaternion!float quatf;///
alias Quaternion!double quatd;///
/// Linear interpolation, for quaternions.
@nogc Quaternion!T lerp(T)(Quaternion!T a, Quaternion!T b, float t) pure nothrow
{
Quaternion!T res = void;
res.v = funcs.lerp(a.v, b.v, t);
return res;
}
/// Nlerp of quaternions
/// Returns: Nlerp of quaternions.
/// See_also: $(WEB keithmaggio.wordpress.com/2011/02/15/math-magician-lerp-slerp-and-nlerp/, Math Magician Lerp, Slerp, and Nlerp)
@nogc Quaternion!T Nlerp(T)(Quaternion!T a, Quaternion!T b, float t) pure nothrow
{
assert(t >= 0 && t <= 1); // else probably doesn't make sense
Quaternion!T res = void;
res.v = funcs.lerp(a.v, b.v, t);
res.v.normalize();
return res;
}
/// Slerp of quaternions
/// Returns: Slerp of quaternions. Slerp is more expensive than Nlerp.
/// See_also: "Understanding Slerp, Then Not Using It"
@nogc Quaternion!T slerp(T)(Quaternion!T a, Quaternion!T b, T t) pure nothrow
{
assert(t >= 0 && t <= 1); // else probably doesn't make sense
Quaternion!T res = void;
T dotProduct = dot(a.v, b.v);
// spherical lerp always has 2 potential paths
// here we always take the shortest
if (dotProduct < 0)
{
b.v *= -1;
dotProduct = dot(a.v, b.v);
}
immutable T threshold = 10 * T.epsilon; // idMath uses 1e-6f for 32-bits float precision
if ((1 - dotProduct) > threshold) // if small difference, use lerp
return lerp(a, b, t);
T theta_0 = funcs.safeAcos(dotProduct); // angle between this and other
T theta = theta_0 * t; // angle between this and result
vec3!T v2 = dot(b.v, a.v * dotProduct);
v2.normalize();
res.v = dot(b.v, a.v * dotProduct);
res.v.normalize();
res.v = a.v * cos(theta) + res.v * sin(theta);
return res;
}
unittest
{
quatf a = quatf.fromAxis(vec3f(1, 0, 0), 1);
quatf b = quatf.fromAxis(vec3f(0, 1, 0), 0);
a = a * b;
quatf c = lerp(a, b, 0.5f);
quatf d = Nlerp(a, b, 0.1f);
quatf e = slerp(a, b, 0.0f);
quatd f = quatd(1.0, 4, 5.0, 6.0);
quatf g = quatf.fromEulerAngles(-0.1f, 1.2f, -0.3f);
vec3f ga = g.toEulerAngles();
}

645
external/gfm/math/shapes.d vendored Normal file
View File

@ -0,0 +1,645 @@
/**
This module implements some abstract geometric shapes:
$(UL
$(LI Line segments.)
$(LI Triangle.)
$(LI Circles/spheres.)
$(LI Rays)
$(LI Planes)
$(LI Frustum)
)
*/
module gfm.math.shapes;
import std.math,
std.traits;
import gfm.math.vector,
gfm.math.box;
/// A Segment is 2 points.
/// When considered like a vector, it represents the arrow from a to b.
struct Segment(T, int N)
{
public
{
alias Vector!(T, N) point_t;
point_t a, b;
static if (N == 3 && isFloatingPoint!T)
{
/// Segment vs plane intersection.
/// See_also: "Geometry for Computer Graphics: Formulae, Examples
/// and Proofs", Vince (2005), p. 62
/// Returns: $(D true) if the segment crosses the plane exactly
/// once, or $(D false) if the segment is parallel to or does
/// not reach the plane
/// Params:
/// plane = The plane to intersect.
/// intersection = Point of intersection.
/// progress = Set to the point's progress between endpoints
/// $(D a) at 0.0 and $(D b) at 1.0, or T.infinity
/// if parallel to the plane
@nogc bool intersect(Plane!T plane, out point_t intersection, out T progress) pure const nothrow
{
// direction vector from a to b
point_t dir = b-a;
// dot product will be 0 if angle to plane is 0
T dp = dot(plane.n, dir);
if (abs(dp) < T.epsilon)
{
progress = T.infinity;
return false; // parallel to plane
}
// relative distance along segment where intersection happens
progress = -(dot(plane.n, a) - plane.d) / dp;
intersection = progress*dir + a;
return progress >= 0 && progress <= 1;
}
}
}
}
alias Segment!(float, 2) seg2f; /// 2D float segment.
alias Segment!(float, 3) seg3f; /// 3D float segment.
alias Segment!(double, 2) seg2d; /// 2D double segment.
alias Segment!(double, 3) seg3d; /// 3D double segment.
alias Segment!(int, 2) seg2i; /// 2D integer segment.
alias Segment!(int, 3) seg3i; /// 3D integer segment.
/// A Triangle is 3 points.
struct Triangle(T, int N)
{
public
{
alias Vector!(T, N) point_t;
point_t a, b, c;
static if (N == 2)
{
/// Returns: Area of a 2D triangle.
@nogc T area() pure const nothrow
{
return abs(signedArea());
}
/// Returns: Signed area of a 2D triangle.
@nogc T signedArea() pure const nothrow
{
return ((b.x * a.y - a.x * b.y)
+ (c.x * b.y - b.x * c.y)
+ (a.x * c.y - c.x * a.y)) / 2;
}
}
static if (N == 3 && isFloatingPoint!T)
{
/// Returns: Triangle normal.
@nogc Vector!(T, 3) computeNormal() pure const nothrow
{
return cross(b - a, c - a).normalized();
}
}
}
}
alias Triangle!(float, 2) triangle2f; /// 2D float triangle.
alias Triangle!(float, 3) triangle3f; /// 3D float triangle.
alias Triangle!(double, 2) triangle2d; /// 2D double triangle.
alias Triangle!(double, 3) triangle3d; /// 3D double triangle.
/// A Sphere is a point + a radius.
struct Sphere(T, int N)
{
public nothrow
{
alias Vector!(T, N) point_t;
point_t center;
T radius;
/// Creates a sphere from a point and a radius.
@nogc this(in point_t center_, T radius_) pure nothrow
{
center = center_;
radius = radius_;
}
/// Sphere contains point test.
/// Returns: true if the point is inside the sphere.
@nogc bool contains(in Sphere s) pure const nothrow
{
if (s.radius > radius)
return false;
T innerRadius = radius - s.radius;
return squaredDistanceTo(s.center) < innerRadius * innerRadius;
}
/// Sphere vs point Euclidean distance squared.
@nogc T squaredDistanceTo(point_t p) pure const nothrow
{
return center.squaredDistanceTo(p);
}
/// Sphere vs sphere intersection.
/// Returns: true if the spheres intersect.
@nogc bool intersects(Sphere s) pure const nothrow
{
T outerRadius = radius + s.radius;
return squaredDistanceTo(s.center) < outerRadius * outerRadius;
}
static if (isFloatingPoint!T)
{
/// Sphere vs point Euclidean distance.
@nogc T distanceTo(point_t p) pure const nothrow
{
return center.distanceTo(p);
}
static if(N == 2)
{
/// Returns: Circle area.
@nogc T area() pure const nothrow
{
return T(PI) * (radius * radius);
}
}
}
}
}
alias Sphere!(float, 2) sphere2f; /// 2D float sphere (ie. a circle).
alias Sphere!(float, 3) sphere3f; /// 3D float sphere.
alias Sphere!(double, 2) sphere2d; /// 2D double sphere (ie. a circle).
alias Sphere!(double, 3) sphere3d; /// 3D double sphere (ie. a circle).
/// A Ray ir a point + a direction.
struct Ray(T, int N)
{
nothrow:
public
{
alias Vector!(T, N) point_t;
point_t orig;
point_t dir;
/// Returns: A point further along the ray direction.
@nogc point_t progress(T t) pure const nothrow
{
return orig + dir * t;
}
static if (N == 3 && isFloatingPoint!T)
{
/// Ray vs triangle intersection.
/// See_also: "Fast, Minimum Storage Ray/Triangle intersection", Mommer & Trumbore (1997)
/// Returns: Barycentric coordinates, the intersection point is at $(D (1 - u - v) * A + u * B + v * C).
@nogc bool intersect(Triangle!(T, 3) triangle, out T t, out T u, out T v) pure const nothrow
{
point_t edge1 = triangle.b - triangle.a;
point_t edge2 = triangle.c - triangle.a;
point_t pvec = cross(dir, edge2);
T det = dot(edge1, pvec);
if (abs(det) < T.epsilon)
return false; // no intersection
T invDet = 1 / det;
// calculate distance from triangle.a to ray origin
point_t tvec = orig - triangle.a;
// calculate U parameter and test bounds
u = dot(tvec, pvec) * invDet;
if (u < 0 || u > 1)
return false;
// prepare to test V parameter
point_t qvec = cross(tvec, edge1);
// calculate V parameter and test bounds
v = dot(dir, qvec) * invDet;
if (v < 0.0 || u + v > 1.0)
return false;
// calculate t, ray intersects triangle
t = dot(edge2, qvec) * invDet;
return true;
}
/// Ray vs plane intersection.
/// See_also: "Geometry for Computer Graphics: Formulae, Examples
/// and Proofs", Vince (2005), p. 62
/// Returns: $(D true) if the ray crosses the plane exactly once,
/// or $(D false) if the ray is parallel to or points away from
/// the plane
/// Params:
/// plane = Plane to intersect.
/// intersection = Point of intersection.
/// distance = set to the point's distance along the ray, or
/// T.infinity if parallel to the plane
@nogc bool intersect(Plane!T plane, out point_t intersection, out T distance) pure const nothrow
{
// dot product will be 0 if angle to plane is 0
T dp = dot(plane.n, dir);
if (abs(dp) < T.epsilon)
{
distance = T.infinity;
return false; // parallel to plane
}
// distance along ray where intersection happens
distance = -(dot(plane.n, orig) - plane.d) / dp;
intersection = distance*dir + orig;
return distance >= 0;
}
}
}
}
alias Ray!(float, 2) ray2f; /// 2D float ray.
alias Ray!(float, 3) ray3f; /// 3D float ray.
alias Ray!(double, 2) ray2d; /// 2D double ray.
alias Ray!(double, 3) ray3d; /// 3D double ray.
/// 3D plane.
/// See_also: Flipcode article by Nate Miller $(WEB www.flipcode.com/archives/Plane_Class.shtml).
struct Plane(T) if (isFloatingPoint!T)
{
public
{
vec3!T n; /// Normal (always stored normalized).
T d;
/// Create from four coordinates.
@nogc this(vec4!T abcd) pure nothrow
{
n = vec3!T(abcd.x, abcd.y, abcd.z).normalized();
d = abcd.w;
}
/// Create from a point and a normal.
@nogc this(vec3!T origin, vec3!T normal) pure nothrow
{
n = normal.normalized();
d = -dot(origin, n);
}
/// Create from 3 non-aligned points.
@nogc this(vec3!T A, vec3!T B, vec3!T C) pure nothrow
{
this(C, cross(B - A, C - A));
}
/// Assign a plane with another plane.
@nogc ref Plane opAssign(Plane other) pure nothrow
{
n = other.n;
d = other.d;
return this;
}
/// Returns: signed distance between a point and the plane.
@nogc T signedDistanceTo(vec3!T point) pure const nothrow
{
return dot(n, point) + d;
}
/// Returns: absolute distance between a point and the plane.
@nogc T distanceTo(vec3!T point) pure const nothrow
{
return abs(signedDistanceTo(point));
}
/// Returns: true if the point is in front of the plane.
@nogc bool isFront(vec3!T point) pure const nothrow
{
return signedDistanceTo(point) >= 0;
}
/// Returns: true if the point is in the back of the plane.
@nogc bool isBack(vec3!T point) pure const nothrow
{
return signedDistanceTo(point) < 0;
}
/// Returns: true if the point is on the plane, with a given epsilon.
@nogc bool isOn(vec3!T point, T epsilon) pure const nothrow
{
T sd = signedDistanceTo(point);
return (-epsilon < sd) && (sd < epsilon);
}
}
}
alias Plane!float planef; /// 3D float plane.
alias Plane!double planed; /// 3D double plane.
unittest
{
auto p = planed(vec4d(1.0, 2.0, 3.0, 4.0));
auto p2 = planed(vec3d(1.0, 0.0, 0.0),
vec3d(0.0, 1.0, 0.0),
vec3d(0.0, 0.0, 1.0));
assert(p2.isOn(vec3d(1.0, 0.0, 0.0), 1e-7));
assert(p2.isFront(vec3d(1.0, 1.0, 1.0)));
assert(p2.isBack(vec3d(0.0, 0.0, 0.0)));
}
/// Plane intersection tests
@nogc pure nothrow unittest
{
void testR(planed p, ray3d r, bool shouldIntersect, double expectedDistance, vec3d expectedPoint = vec3d.init) pure nothrow @nogc
{
vec3d point;
double distance;
assert(r.intersect(p, point, distance) == shouldIntersect);
assert(approxEqual(distance, expectedDistance));
if (shouldIntersect)
assert(approxEqual(point.v[], expectedPoint.v[]));
}
// ray facing plane
testR(planed(vec4d(1.0, 0.0, 0.0, 1.0)), ray3d(vec3d(2.0, 3.0, 4.0), vec3d(-1.0, 0.0, 0.0)),
true, 1.0, vec3d(1.0, 3.0, 4.0));
testR(planed(vec4d(1.0, 1.0, 1.0, -sqrt(3.0))), ray3d(vec3d(1.0, 1.0, 1.0), vec3d(-1.0, -1.0, -1.0).normalized()),
true, 2.0*sqrt(3.0), vec3d(-1.0, -1.0, -1.0));
// ray facing away
testR(planed(vec4d(1.0, 0.0, 0.0, 1.0)), ray3d(vec3d(2.0, 3.0, 4.0), vec3d(1.0, 0.0, 0.0)),
false, -1.0);
testR(planed(vec4d(1.0, 0.0, 0.0, 5.0)), ray3d(vec3d(2.0, 3.0, 4.0), vec3d(-1.0, 0.0, 0.0)),
false, -3.0);
// ray parallel
testR(planed(vec4d(0.0, 0.0, 1.0, 1.0)), ray3d(vec3d(1.0, 2.0, 3.0), vec3d(1.0, 0.0, 0.0)),
false, double.infinity);
void testS(planed p, seg3d s, bool shouldIntersect, double expectedProgress, vec3d expectedPoint = vec3d.init) pure nothrow @nogc
{
vec3d point;
double progress;
assert(s.intersect(p, point, progress) == shouldIntersect);
assert(approxEqual(progress, expectedProgress));
if (shouldIntersect)
assert(approxEqual(point.v[], expectedPoint.v[]));
}
// segment crossing plane
testS(planed(vec4d(1.0, 0.0, 0.0, 2.0)), seg3d(vec3d(1.0, 2.0, 3.0), vec3d(3.0, 4.0, 5.0)),
true, 0.5, vec3d(2.0, 3.0, 4.0));
// segment too short
testS(planed(vec4d(1.0, 0.0, 0.0, 0.0)), seg3d(vec3d(1.0, 2.0, 3.0), vec3d(3.0, 4.0, 5.0)),
false, -0.5);
}
/// 3D frustum.
/// See_also: Flipcode article by Dion Picco $(WEB www.flipcode.com/archives/Frustum_Culling.shtml).
/// Bugs: verify proper signedness of half-spaces
struct Frustum(T) if (isFloatingPoint!T)
{
public
{
enum int LEFT = 0,
RIGHT = 1,
TOP = 2,
BOTTOM = 3,
NEAR = 4,
FAR = 5;
Plane!T[6] planes;
/// Create a frustum from 6 planes.
@nogc this(Plane!T left, Plane!T right, Plane!T top, Plane!T bottom, Plane!T near, Plane!T far) pure nothrow
{
planes[LEFT] = left;
planes[RIGHT] = right;
planes[TOP] = top;
planes[BOTTOM] = bottom;
planes[NEAR] = near;
planes[FAR] = far;
}
enum : int
{
OUTSIDE, /// object is outside the frustum
INTERSECT, /// object intersects with the frustum
INSIDE /// object is inside the frustum
}
/// Point vs frustum intersection.
@nogc bool contains(vec3!T point) pure const nothrow
{
for(int i = 0; i < 6; ++i)
{
T distance = planes[i].signedDistanceTo(point);
if(distance < 0)
return false;
}
return true;
}
/// Sphere vs frustum intersection.
/// Returns: Frustum.OUTSIDE, Frustum.INTERSECT or Frustum.INSIDE.
@nogc int contains(Sphere!(T, 3) sphere) pure const nothrow
{
// calculate our distances to each of the planes
for(int i = 0; i < 6; ++i)
{
// find the distance to this plane
T distance = planes[i].signedDistanceTo(sphere.center);
if(distance < -sphere.radius)
return OUTSIDE;
else if (distance < sphere.radius)
return INTERSECT;
}
// otherwise we are fully in view
return INSIDE;
}
/// AABB vs frustum intersection.
/// Returns: Frustum.OUTSIDE, Frustum.INTERSECT or Frustum.INSIDE.
@nogc int contains(box3!T box) pure const nothrow
{
vec3!T[8] corners;
int totalIn = 0;
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
for (int k = 0; k < 2; ++k)
{
auto x = i == 0 ? box.min.x : box.max.x;
auto y = j == 0 ? box.min.y : box.max.y;
auto z = k == 0 ? box.min.z : box.max.z;
corners[i*4 + j*2 + k] = vec3!T(x, y, z);
}
// test all 8 corners against the 6 sides
// if all points are behind 1 specific plane, we are out
// if we are in with all points, then we are fully in
for(int p = 0; p < 6; ++p)
{
int inCount = 8;
int ptIn = 1;
for(int i = 0; i < 8; ++i)
{
// test this point against the planes
if (planes[p].isBack(corners[i]))
{
ptIn = 0;
--inCount;
}
}
// were all the points outside of plane p?
if (inCount == 0)
return OUTSIDE;
// check if they were all on the right side of the plane
totalIn += ptIn;
}
// so if totalIn is 6, then all are inside the view
if(totalIn == 6)
return INSIDE;
// we must be partly in then otherwise
return INTERSECT;
}
}
}
unittest
{
seg2f se;
triangle3f tr;
Frustum!double frust;
planed pl;
}
/// True if `T` is a kind of Segment
enum isSegment(T) = is(T : Segment!U, U...);
/// True if `T` is a kind of Triangle
enum isTriangle(T) = is(T : Triangle!U, U...);
/// True if `T` is a kind of Sphere
enum isSphere(T) = is(T : Sphere!U, U...);
/// True if `T` is a kind of Ray
enum isRay(T) = is(T : Ray!U, U...);
/// True if `T` is a kind of Plane
enum isPlane(T) = is(T : Plane!U, U);
/// True if `T` is a kind of Frustum
enum isFrustum(T) = is(T : Frustum!U, U);
/// True if `T` is a kind of 2 dimensional Segment
enum isSegment2D(T) = is(T : Segment!(U, 2), U);
/// True if `T` is a kind of 2 dimensional Triangle
enum isTriangle2D(T) = is(T : Triangle!(U, 2), U);
/// True if `T` is a kind of 2 dimensional Sphere
enum isSphere2D(T) = is(T : Sphere!(U, 2), U);
/// True if `T` is a kind of 2 dimensional Ray
enum isRay2D(T) = is(T : Ray!(U, 2), U);
/// True if `T` is a kind of 3 dimensional Segment
enum isSegment3D(T) = is(T : Segment!(U, 3), U);
/// True if `T` is a kind of 3 dimensional Triangle
enum isTriangle3D(T) = is(T : Triangle!(U, 3), U);
/// True if `T` is a kind of 3 dimensional Sphere
enum isSphere3D(T) = is(T : Sphere!(U, 3), U);
/// True if `T` is a kind of 3 dimensional Ray
enum isRay3D(T) = is(T : Ray!(U, 3), U);
unittest
{
enum ShapeType
{
segment,
triangle,
sphere,
ray,
plane,
frustum
}
void test(T, ShapeType type, int dim)()
{
with (ShapeType)
{
static assert(isSegment!T == (type == segment ));
static assert(isTriangle!T == (type == triangle));
static assert(isSphere!T == (type == sphere ));
static assert(isRay!T == (type == ray ));
static assert(isPlane!T == (type == plane ));
static assert(isFrustum!T == (type == frustum ));
static assert(isSegment2D!T == (type == segment && dim == 2));
static assert(isTriangle2D!T == (type == triangle && dim == 2));
static assert(isSphere2D!T == (type == sphere && dim == 2));
static assert(isRay2D!T == (type == ray && dim == 2));
static assert(isSegment3D!T == (type == segment && dim == 3));
static assert(isTriangle3D!T == (type == triangle && dim == 3));
static assert(isSphere3D!T == (type == sphere && dim == 3));
static assert(isRay3D!T == (type == ray && dim == 3));
}
}
with (ShapeType)
{
// test case type #dimensions
test!(seg2f , segment , 2);
test!(seg3d , segment , 3);
test!(triangle2d , triangle, 2);
test!(triangle3f , triangle, 3);
test!(sphere2d , sphere , 2);
test!(Sphere!(uint, 3), sphere , 3);
test!(ray2f , ray , 2);
test!(Ray!(real, 3) , ray , 3);
test!(planed , plane , 0); // ignore dimension (always 3D)
test!(Plane!float , plane , 0);
test!(Frustum!double , frustum , 0);
}
}
/// Get the numeric type used to measure a shape's dimensions.
alias DimensionType(T : Segment!U, U...) = U[0];
/// ditto
alias DimensionType(T : Triangle!U, U...) = U[0];
/// ditto
alias DimensionType(T : Sphere!U, U...) = U[0];
/// ditto
alias DimensionType(T : Ray!U, U...) = U[0];
/// ditto
alias DimensionType(T : Plane!U, U) = U;
/// ditto
alias DimensionType(T : Frustum!U, U) = U;
///
unittest
{
static assert(is(DimensionType!seg2i == int));
static assert(is(DimensionType!triangle3d == double));
static assert(is(DimensionType!sphere2d == double));
static assert(is(DimensionType!ray3f == float));
static assert(is(DimensionType!planed == double));
static assert(is(DimensionType!(Frustum!real) == real));
}

485
external/gfm/math/simplerng.d vendored Normal file
View File

@ -0,0 +1,485 @@
/**
Translation of SimpleRNG.
Removed the builtin RNG to use std.random, but kept the distribution functions.
John D. Cook confirmed this code as public domain.
Authors: John D. Cook.
See_also: $(WEB www.johndcook.com/cpp_random_number_generation.html)
*/
module gfm.math.simplerng;
public import std.random;
import std.math;
/// Returns: Normal (Gaussian) random sample.
/// See_also: Box-Muller algorithm.
double randNormal(RNG)(ref RNG rng, double mean = 0.0, double standardDeviation = 1.0)
{
assert(standardDeviation > 0);
double u1;
do
{
u1 = uniform01(rng);
} while (u1 == 0); // u1 must not be zero
double u2 = uniform01(rng);
double r = sqrt(-2.0 * log(u1));
double theta = 2.0 * double(PI) * u2;
return mean + standardDeviation * r * sin(theta);
}
/// Returns: Exponential random sample with specified mean.
double randExponential(RNG)(ref RNG rng, double mean = 1.0)
{
assert(mean > 0);
return -mean*log(uniform01(rng));
}
/// Returns: Gamma random sample.
/// See_also: "A Simple Method for Generating Gamma Variables"
/// by George Marsaglia and Wai Wan Tsang. ACM Transactions on Mathematical Software
/// Vol 26, No 3, September 2000, pages 363-372.
double randGamma(RNG)(ref RNG rng, double shape, double scale)
{
double d, c, x, xsquared, v, u;
if (shape >= 1.0)
{
d = shape - 1.0/3.0;
c = 1.0/sqrt(9.0*d);
for (;;)
{
do
{
x = randNormal(rng);
v = 1.0 + c*x;
}
while (v <= 0.0);
v = v*v*v;
u = uniform01(rng);
xsquared = x*x;
if (u < 1.0 -.0331*xsquared*xsquared || log(u) < 0.5*xsquared + d*(1.0 - v + log(v)))
return scale*d*v;
}
}
else
{
assert(shape > 0);
double g = randGamma(rng, shape+1.0, 1.0);
double w = uniform01(rng);
return scale*g*pow(w, 1.0/shape);
}
}
/// Returns: Chi-square sample.
double randChiSquare(RNG)(ref RNG rng, double degreesOfFreedom)
{
// A chi squared distribution with n degrees of freedom
// is a gamma distribution with shape n/2 and scale 2.
return randGamma(rng, 0.5 * degreesOfFreedom, 2.0);
}
/// Returns: Inverse-gamma sample.
double randInverseGamma(RNG)(ref RNG rng, double shape, double scale)
{
// If X is gamma(shape, scale) then
// 1/Y is inverse gamma(shape, 1/scale)
return 1.0 / randGamma(rng, shape, 1.0 / scale);
}
/// Returns: Weibull sample.
double randWeibull(RNG)(ref RNG rng, double shape, double scale)
{
assert(shape > 0 && scale > 0);
return scale * pow(-log(uniform01(rng)), 1.0 / shape);
}
/// Returns: Cauchy sample.
double randCauchy(RNG)(ref RNG rng, double median, double scale)
{
assert(scale > 0);
double p = uniform01(rng);
// Apply inverse of the Cauchy distribution function to a uniform
return median + scale*tan(double(PI)*(p - 0.5));
}
/// Returns: Student-t sample.
/// See_also: Seminumerical Algorithms by Knuth.
double randStudentT(RNG)(ref RNG rng, double degreesOfFreedom)
{
assert(degreesOfFreedom > 0);
double y1 = getNormal(rng);
double y2 = getChiSquare(rng, degreesOfFreedom);
return y1 / sqrt(y2 / degreesOfFreedom);
}
/// Returns: Laplace distribution random sample (also known as the double exponential distribution).
double randLaplace(RNG)(ref RNG rng, double mean, double scale)
{
double u = uniform01(rng);
return (u < 0.5) ? (mean + scale*log(2.0*u))
: (mean - scale*log(2*(1-u)));
}
/// Returns: Log-normal sample.
double randLogNormal(RNG)(ref RNG rng, double mu, double sigma)
{
return exp(getNormal(rng, mu, sigma));
}
/// Returns: Beta sample.
double randBeta(RNG)(ref RNG rng, double a, double b)
{
assert(a > 0 && b > 0);
// There are more efficient methods for generating beta samples.
// However such methods are a little more efficient and much more complicated.
// For an explanation of why the following method works, see
// http://www.johndcook.com/distribution_chart.html#gamma_beta
double u = getGamma(rng, a, 1.0);
double v = getGamma(rng, b, 1.0);
return u / (u + v);
}
/// Returns: Poisson sample.
int randPoisson(RNG)(ref RNG rng, double lambda)
{
return (lambda < 30.0) ? poissonSmall(rng, lambda) : poissonLarge(rng, lambda);
}
private
{
int poissonSmall(RNG)(ref RNG rng, double lambda)
{
// Algorithm due to Donald Knuth, 1969.
double p = 1.0, L = exp(-lambda);
int k = 0;
do
{
k++;
p *= uniform01(rng);
}
while (p > L);
return k - 1;
}
int poissonLarge(RNG)(ref RNG rng, double lambda)
{
// "Rejection method PA" from "The Computer Generation of Poisson Random Variables" by A. C. Atkinson
// Journal of the Royal Statistical Society Series C (Applied Statistics) Vol. 28, No. 1. (1979)
// The article is on pages 29-35. The algorithm given here is on page 32.
double c = 0.767 - 3.36/lambda;
double beta = double(PI)/sqrt(3.0*lambda);
double alpha = beta*lambda;
double k = log(c) - lambda - log(beta);
for(;;)
{
double u = uniform01(rng);
double x = (alpha - log((1.0 - u)/u))/beta;
int n = cast(int)(floor(x + 0.5));
if (n < 0)
continue;
double v = uniform01(rng);
double y = alpha - beta*x;
double temp = 1.0 + exp(y);
double lhs = y + log(v/(temp*temp));
double rhs = k + n*log(lambda) - logFactorial(n);
if (lhs <= rhs)
return n;
}
}
double logFactorial(int n) nothrow
{
assert(n >= 0);
if (n > 254)
{
double x = n + 1;
return (x - 0.5)*log(x) - x + 0.5*log(2*double(PI)) + 1.0/(12.0*x);
}
else
{
return LOG_FACTORIAL[n];
}
}
}
private static immutable double[255] LOG_FACTORIAL =
[
0.000000000000000,
0.000000000000000,
0.693147180559945,
1.791759469228055,
3.178053830347946,
4.787491742782046,
6.579251212010101,
8.525161361065415,
10.604602902745251,
12.801827480081469,
15.104412573075516,
17.502307845873887,
19.987214495661885,
22.552163853123421,
25.191221182738683,
27.899271383840894,
30.671860106080675,
33.505073450136891,
36.395445208033053,
39.339884187199495,
42.335616460753485,
45.380138898476908,
48.471181351835227,
51.606675567764377,
54.784729398112319,
58.003605222980518,
61.261701761002001,
64.557538627006323,
67.889743137181526,
71.257038967168000,
74.658236348830158,
78.092223553315307,
81.557959456115029,
85.054467017581516,
88.580827542197682,
92.136175603687079,
95.719694542143202,
99.330612454787428,
102.968198614513810,
106.631760260643450,
110.320639714757390,
114.034211781461690,
117.771881399745060,
121.533081515438640,
125.317271149356880,
129.123933639127240,
132.952575035616290,
136.802722637326350,
140.673923648234250,
144.565743946344900,
148.477766951773020,
152.409592584497350,
156.360836303078800,
160.331128216630930,
164.320112263195170,
168.327445448427650,
172.352797139162820,
176.395848406997370,
180.456291417543780,
184.533828861449510,
188.628173423671600,
192.739047287844900,
196.866181672889980,
201.009316399281570,
205.168199482641200,
209.342586752536820,
213.532241494563270,
217.736934113954250,
221.956441819130360,
226.190548323727570,
230.439043565776930,
234.701723442818260,
238.978389561834350,
243.268849002982730,
247.572914096186910,
251.890402209723190,
256.221135550009480,
260.564940971863220,
264.921649798552780,
269.291097651019810,
273.673124285693690,
278.067573440366120,
282.474292687630400,
286.893133295426990,
291.323950094270290,
295.766601350760600,
300.220948647014100,
304.686856765668720,
309.164193580146900,
313.652829949878990,
318.152639620209300,
322.663499126726210,
327.185287703775200,
331.717887196928470,
336.261181979198450,
340.815058870798960,
345.379407062266860,
349.954118040770250,
354.539085519440790,
359.134205369575340,
363.739375555563470,
368.354496072404690,
372.979468885689020,
377.614197873918670,
382.258588773060010,
386.912549123217560,
391.575988217329610,
396.248817051791490,
400.930948278915760,
405.622296161144900,
410.322776526937280,
415.032306728249580,
419.750805599544780,
424.478193418257090,
429.214391866651570,
433.959323995014870,
438.712914186121170,
443.475088120918940,
448.245772745384610,
453.024896238496130,
457.812387981278110,
462.608178526874890,
467.412199571608080,
472.224383926980520,
477.044665492585580,
481.872979229887900,
486.709261136839360,
491.553448223298010,
496.405478487217580,
501.265290891579240,
506.132825342034830,
511.008022665236070,
515.890824587822520,
520.781173716044240,
525.679013515995050,
530.584288294433580,
535.496943180169520,
540.416924105997740,
545.344177791154950,
550.278651724285620,
555.220294146894960,
560.169054037273100,
565.124881094874350,
570.087725725134190,
575.057539024710200,
580.034272767130800,
585.017879388839220,
590.008311975617860,
595.005524249382010,
600.009470555327430,
605.020105849423770,
610.037385686238740,
615.061266207084940,
620.091704128477430,
625.128656730891070,
630.172081847810200,
635.221937855059760,
640.278183660408100,
645.340778693435030,
650.409682895655240,
655.484856710889060,
660.566261075873510,
665.653857411105950,
670.747607611912710,
675.847474039736880,
680.953419513637530,
686.065407301994010,
691.183401114410800,
696.307365093814040,
701.437263808737160,
706.573062245787470,
711.714725802289990,
716.862220279103440,
722.015511873601330,
727.174567172815840,
732.339353146739310,
737.509837141777440,
742.685986874351220,
747.867770424643370,
753.055156230484160,
758.248113081374300,
763.446610112640200,
768.650616799717000,
773.860102952558460,
779.075038710167410,
784.295394535245690,
789.521141208958970,
794.752249825813460,
799.988691788643450,
805.230438803703120,
810.477462875863580,
815.729736303910160,
820.987231675937890,
826.249921864842800,
831.517780023906310,
836.790779582469900,
842.068894241700490,
847.352097970438420,
852.640365001133090,
857.933669825857460,
863.231987192405430,
868.535292100464630,
873.843559797865740,
879.156765776907600,
884.474885770751830,
889.797895749890240,
895.125771918679900,
900.458490711945270,
905.796028791646340,
911.138363043611210,
916.485470574328820,
921.837328707804890,
927.193914982476710,
932.555207148186240,
937.921183163208070,
943.291821191335660,
948.667099599019820,
954.046996952560450,
959.431492015349480,
964.820563745165940,
970.214191291518320,
975.612353993036210,
981.015031374908400,
986.422203146368590,
991.833849198223450,
997.249949600427840,
1002.670484599700300,
1008.095434617181700,
1013.524780246136200,
1018.958502249690200,
1024.396581558613400,
1029.838999269135500,
1035.285736640801600,
1040.736775094367400,
1046.192096209724900,
1051.651681723869200,
1057.115513528895000,
1062.583573670030100,
1068.055844343701400,
1073.532307895632800,
1079.012946818975000,
1084.497743752465600,
1089.986681478622400,
1095.479742921962700,
1100.976911147256000,
1106.478169357800900,
1111.983500893733000,
1117.492889230361000,
1123.006317976526100,
1128.523770872990800,
1134.045231790853000,
1139.570684729984800,
1145.100113817496100,
1150.633503306223700,
1156.170837573242400,
];
unittest
{
Xorshift32 rng;
rng.seed(unpredictableSeed());
double x = randNormal!Xorshift32(rng, 0.0, 1.0);
x = randExponential!Xorshift32(rng);
x = randGamma!Xorshift32(rng, 1.2, 1.0);
x = randGamma!Xorshift32(rng, 0.8, 2.0);
x = randChiSquare!Xorshift32(rng, 2.0);
x = randInverseGamma!Xorshift32(rng, 1.1, 0.7);
x = randWeibull!Xorshift32(rng, 3.0, 0.7);
x = randCauchy!Xorshift32(rng, 5.0, 1.4);
}

View File

@ -1,25 +1,13 @@
/**
* N-dimensional small vector math.
*
* Copyright: Copyright Guillaume Piolat 2021.
* Copyright Chance Snow 2021.
* Copyright Aleksandr Druzhinin 2018.
* Copyright Nathan Sashihara 2018.
* Copyright Ryan Roden-Corrent 2016.
* Copyright Steven Dwy 2015.
* Copyright Martin Nowak 2015.
* Copyright Tanel Tagaväli 2015.
*
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
*/
module dplug.math.vector;
/// N-dimension vector mathematical object
module gfm.math.vector;
import std.traits,
std.math,
std.array;
std.conv,
std.array,
std.string;
import inteli.emmintrin;
import gfm.math.funcs;
/**
* Generic 1D small vector.
@ -160,6 +148,15 @@ nothrow:
return v.ptr;
}
/// Converts to a pretty string.
string toString() const nothrow
{
try
return format("%s", v);
catch (Exception e)
assert(false); // should not happen since format is right
}
@nogc bool opEquals(U)(U other) pure const nothrow
if (is(U : Vector))
{
@ -555,6 +552,31 @@ alias vec4!int vec4i; ///
alias vec4!float vec4f; ///
alias vec4!double vec4d; ///
private
{
static string generateLoopCode(string formatString, int N)() pure nothrow
{
string result;
for (int i = 0; i < N; ++i)
{
string index = ctIntToString(i);
// replace all @ by indices
result ~= formatString.replace("@", index);
}
return result;
}
// Speed-up CTFE conversions
static string ctIntToString(int n) pure nothrow
{
static immutable string[16] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
if (n < 10)
return table[n];
else
return to!string(n);
}
}
/// Element-wise minimum.
@nogc Vector!(T, N) minByElem(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow
@ -730,6 +752,8 @@ unittest
assert(!__traits(compiles, h.xx = h.yy));
vec4ub j;
assert(lerp(vec2f(-10, -1), vec2f(10, 1), 0.5) == vec2f(0, 0));
// larger vectors
alias Vector!(float, 5) vec5f;
vec5f l = vec5f(1, 2.0f, 3.0, 4u, 5.0L);
@ -744,80 +768,3 @@ unittest
assert(absByElem(vec3i(-1, 0, 2)) == vec3i(1, 0, 2));
}
private:
/// SSE approximation of reciprocal square root.
@nogc T inverseSqrt(T)(T x) pure nothrow if (isFloatingPoint!T)
{
static if (is(T == float))
{
__m128 V = _mm_set_ss(x);
V = _mm_rsqrt_ss(V);
return _mm_cvtss_f32(V);
}
else
{
return 1 / sqrt(x);
}
}
package
{
// This generates small loops for Vector, Matrix, and Box.
// Time has shown such sort of manually unrolled code works best on both DMD and LDC.
static string generateLoopCode(string formatString, int N)() pure nothrow
{
string result;
for (int i = 0; i < N; ++i)
{
string index = ctIntToString(i);
// replace all @ by indices
int after = 0;
int cur = 0;
for (; cur < formatString.length; ++cur)
{
char ch = formatString[cur];
if (ch == '@')
{
if (cur > after)
result ~= formatString[after..cur];
result ~= index;
after = cur+1;
}
}
if (cur > after)
result ~= formatString[after..cur];
}
return result;
}
// Speed-up CTFE conversions, replacement for std.conv
// Doesn't do the negatives.
static string ctIntToString(int n) pure nothrow
{
static immutable string[16] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
if (n < 10)
return table[n];
else
{
char[10] r;
for (int k = 0; k < 10; ++k)
{
r[9-k] = cast(char)('0' + n % 10);
n /= 10;
if (n == 0)
return r[9-k..$].idup;
}
return r.idup;
}
}
}
unittest
{
assert(ctIntToString(132) == "132");
assert(ctIntToString(2147483647) == "2147483647");
}

View File

@ -4,14 +4,14 @@ import renderer : Destroy;
import renderer;
import util;
import platform;
import dplug.math;
import math;
struct Camera
{
f32 speed = 3.0;
Vec3 pos = Vec3(0.0, 0.0, 3.0);
Vec3 front = Vec3(0.0, 0.0, -1.0);
Vec3 up = Vec3(0.0, 1.0, 0.0);
Vec3 velocity = Vec3(0.0);
Vec3 pos = Vec3(0.0, 0.0, 0.0);
f32 yaw = 0.0;
f32 pitch = 0.0;
}
struct Game
@ -32,6 +32,9 @@ struct Game
Camera camera;
u16 prev_x;
u16 prev_y;
Model model;
}
@ -89,44 +92,80 @@ InitGame(PlatformWindow* window)
}
void
Update(Game* g, Camera* cam)
ProcessInputs(Game* g, Camera* cam)
{
f32 speed = cam.speed * g.delta;
foreach(i; 0 .. g.window.input_count)
{
bool pressed = g.window.inputs[i].pressed;
switch(g.window.inputs[i].key)
{
case KBI.W:
case Input.W: cam.velocity.z = pressed ? -1.0 : 0.0; break;
case Input.S: cam.velocity.z = pressed ? +1.0 : 0.0; break;
case Input.A: cam.velocity.x = pressed ? -1.0 : 0.0; break;
case Input.D: cam.velocity.x = pressed ? +1.0 : 0.0; break;
case Input.MouseMotion:
{
cam.pos += speed * cam.front;
} break;
case KBI.A:
{
cam.pos -= speed * cam.front;
} break;
case KBI.S:
{
cam.pos -= cross(cam.front, cam.up).normalized() * speed;
} break;
case KBI.D:
{
cam.pos += cross(cam.front, cam.up).normalized() * speed;
f32 x = cast(f32)(g.window.inputs[i].x) - cast(f32)(g.prev_x);
f32 y = cast(f32)(g.window.inputs[i].y) - cast(f32)(g.prev_y);
cam.yaw += x / 200.0;
cam.pitch -= y / 200.0;
g.prev_x = g.window.inputs[i].x;
g.prev_y = g.window.inputs[i].y;
} break;
default: break;
}
}
}
void
Update(Game* g, Camera* cam)
{
Mat4 rotation = RotationMatrix(cam);
//cam.pos += (rotation * Vec4(cam.velocity * 0.5, 0.0)).xyz * g.delta;
Extent ext = GetExtent(&g.rd);
g.globals.view_matrix = ViewMatrix(cam);
g.globals.projection_matrix = Mat4Identity();
//g.globals.projection_matrix.perspective(Radians(70.0), cast(f32)(ext.x) / cast(f32)(ext.y), 10000.0, 0.1);
//g.globals.projection_matrix.rows[1][1] *= -1.0;
//g.globals.view_projection = g.globals.projection_matrix * g.globals.view_matrix;
g.globals.res.x = cast(f32)ext.x;
g.globals.res.y = cast(f32)ext.y;
//g.globals.projection_matrix = g.globals.projection_matrix.transposed();
//g.globals.view_matrix = g.globals.view_matrix.transposed();
//g.globals.view_projection = g.globals.view_projection.transposed();
}
pragma(inline): Mat4
RotationMatrix(Camera* cam)
{
//Quat pitch_rotation = Quat.fromAxis(Vec3(1.0, 0.0, 0.0), cam.pitch);
//Quat yaw_rotation = Quat.fromAxis(Vec3(0.0, -1.0, 0.0), cam.yaw);
return Mat4Identity(); //cast(Mat4)(yaw_rotation) * cast(Mat4)(pitch_rotation);
}
pragma(inline): Mat4
ViewMatrix(Camera* cam)
{
//Mat4 translation = Mat4.translation(cam.pos);
//Mat4 rotation = RotationMatrix(cam);
return Mat4Identity(); //(translation * rotation).inverse();
}
void
Cycle(Game* g)
{
g.delta = DeltaTime(&g.timer);
Update(g, &g.camera);
ProcessInputs(g, &g.camera);
g.globals.projection_matrix = Mat4.identity;
g.globals.view_matrix = Mat4.lookAt(g.camera.pos, g.camera.pos + g.camera.front, g.camera.up);
Update(g, &g.camera);
BeginFrame(&g.rd);
@ -134,11 +173,11 @@ Cycle(Game* g)
SetUniform(&g.rd, &g.globals);
DrawRect(&g.rd, 150.0, 300.0, 500.0, 700.0, Vec4(0.0, 0.0, 1.0, 1.0));
//DrawRect(&g.rd, 150.0, 300.0, 500.0, 700.0, Vec4(0.0, 0.0, 1.0, 1.0));
PrepComputeDrawImage(&g.rd);
//PrepComputeDrawImage(&g.rd);
Dispatch(&g.rd);
//Dispatch(&g.rd);
BeginRender(&g.rd);
@ -146,11 +185,11 @@ Cycle(Game* g)
BindUIBuffers(&g.rd);
DrawUI(&g.rd);
//DrawUI(&g.rd);
Bind(&g.rd, &g.triangle_pipeline);
Draw(&g.rd, 3, 1);
//Draw(&g.rd, 3, 1);
Bind(&g.rd, &g.pbr_pipeline);

View File

@ -6,9 +6,34 @@ import platform;
import game;
import util;
import core.simd;
import math;
// TODO:
// 1. Remove bindless (informed to be perf death)
// 2. Set up VK_AMD_shader_info
void main()
{
Mat4 mat1 = Mat4Identity();
Mat4 mat2 = Mat4Identity();
mat1.v[0] = 2.0;
mat1.v[4] = 3.0;
mat1.v[7] = 25.0;
mat1.v[11] = 55.0;
mat2.v[2] = 13.0;
mat2.v[5] = 2.0;
mat2.v[9] = 23.0;
mat2.v[10] = 15.0;
mat2.v[11] = 34.0;
mat2 = mat1 * mat2;
Logf("%s", mat2.vec[0].v);
Logf("%s", mat2.vec[1].v);
Logf("%s", mat2.vec[2].v);
Logf("%s", mat2.vec[3].v);
PlatformWindow window = CreateWindow("Video Game", 1920, 1080);
Game g = InitGame(&window);

View File

@ -3,9 +3,11 @@ import includes;
import std.stdio;
import core.memory;
enum KeyboardInput
enum Input
{
None,
// Keyboard
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine,
@ -22,9 +24,14 @@ enum KeyboardInput
Enter, Space,
Tilde, Esc,
Semicolon, Quote, LeftBrace, RightBrace,
// Mouse
MouseMotion,
LeftClick, MiddleClick, RightClick,
};
alias KBI = KeyboardInput;
alias KBI = Input;
version(linux)
{
@ -32,8 +39,19 @@ import core.sys.posix.dlfcn;
struct InputEvent
{
KeyboardInput key;
Input key;
union
{
struct
{
bool pressed;
};
struct
{
u16 x;
u16 y;
};
};
}
struct PlatformWindow
@ -171,6 +189,8 @@ PlatformWindow CreateWindow(string name, u16 width, u16 height)
xcb_map_window(window.conn, window.window);
xcb_grab_pointer(window.conn, 1, window.window, 0, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, window.window, XCB_NONE, XCB_CURRENT_TIME);
i32 stream_result = xcb_flush(window.conn);
assert(stream_result > 0, "xcb_flush failure");
@ -180,6 +200,8 @@ PlatformWindow CreateWindow(string name, u16 width, u16 height)
void
HandleEvents(PlatformWindow* window)
{
window.input_count = 0;
xcb_generic_event_t* e;
do
@ -226,6 +248,40 @@ HandleEvents(PlatformWindow* window)
window.input_count += 1;
}
} break;
case XCB_BUTTON_PRESS:
case XCB_BUTTON_RELEASE:
{
xcb_button_press_event_t* mouse_event = cast(xcb_button_press_event_t*)e;
bool pressed = e.response_type == XCB_BUTTON_PRESS;
Input input = Input.None;
switch (mouse_event.detail)
{
case XCB_BUTTON_INDEX_1: input = Input.LeftClick; break;
case XCB_BUTTON_INDEX_2: input = Input.MiddleClick; break;
case XCB_BUTTON_INDEX_3: input = Input.RightClick; break;
default: break;
}
if (input != Input.None)
{
window.inputs[window.input_count].key = input;
window.inputs[window.input_count].pressed = pressed;
window.input_count += 1;
}
} break;
case XCB_MOTION_NOTIFY:
{
xcb_motion_notify_event_t* move_event = cast(xcb_motion_notify_event_t*)e;
if (move_event.event_x > 0 || move_event.event_y > 0)
{
window.inputs[window.input_count].key = Input.MouseMotion;
window.inputs[window.input_count].x = move_event.event_x;
window.inputs[window.input_count].y = move_event.event_y;
window.input_count += 1;
}
} break;
default:
break;
}
@ -256,7 +312,7 @@ Function LoadFunction(Library lib, string name)
return fn;
};
KeyboardInput
Input
ConvertInput(u64 x_key)
{
switch (x_key)

View File

@ -7,8 +7,10 @@ import vulkan;
import vulkan : Destroy, Init, Draw, DrawIndexed, Bind, BindUIBuffers, BeginRender, SetUniform, PrepComputeDrawImage, Dispatch, FinishFrame, BeginFrame;
import assets;
import std.math.traits : isNaN;
import u = util : Logf;
import util : Logf;
import util;
import platform;
import math;
alias Shader = VkShaderModule;
alias Pipeline = PipelineHandle;
@ -51,8 +53,9 @@ alias BT = BufferType;
struct GlobalUniforms
{
Mat4 view_matrix = Mat4.identity;
Mat4 projection_matrix = Mat4.identity;
Mat4 view_matrix = Mat4Identity();
Mat4 projection_matrix = Mat4Identity();
Mat4 view_projection = Mat4Identity();
Vec2 res;
}
@ -129,6 +132,12 @@ extern(C) struct Material
f32 shininess = 0.0;
}
struct Extent
{
u32 x;
u32 y;
}
struct UIVertex
{
Vec2 p0;
@ -154,6 +163,7 @@ struct MeshPart
struct Model
{
Vec3 pos = Vec3(0.0, 0.0, 0.0);
Buffer vertex_buffer;
Buffer index_buffer;
MeshPart[] parts;
@ -167,12 +177,12 @@ struct Model
Renderer
InitRenderer(PlatformWindow* window)
{
u.Result!(Vulkan) vk_result = Init(window, u.MB(24), u.MB(32));
Result!(Vulkan) vk_result = Init(window, MB(24), MB(32));
assert(vk_result.ok, "Init failure: Unable to initialize Vulkan");
Renderer rd = {
arena: CreateArena(u.MB(16)),
temp_arena: CreateArena(u.MB(16)),
arena: CreateArena(MB(16)),
temp_arena: CreateArena(MB(16)),
vk: vk_result.value,
window: window,
ui_vertex_buf: GetUIVertexBuffer(&vk_result.value),
@ -187,7 +197,10 @@ DrawModel(Renderer* rd, Model* model)
{
BindBuffers(&rd.vk, &model.index_buffer, &model.vertex_buffer);
rd.push_const.model_matrix = Mat4.identity;
rd.push_const.model_matrix = Mat4Identity();
//rd.push_const.model_matrix.translate(Vec3(1.0, 0.0, 0.0));
//rd.push_const.model_matrix = rd.push_const.model_matrix.transposed();
foreach(i, part; model.parts)
{
rd.push_const.mat_id = part.mat;
@ -196,6 +209,12 @@ DrawModel(Renderer* rd, Model* model)
}
}
pragma(inline): Extent
GetExtent(Renderer* rd)
{
return Extent(rd.vk.swapchain_extent.width, rd.vk.swapchain_extent.height);
}
pragma(inline): void
CopyVertex(Vec4* dst, m3dv_t* src)
{

View File

@ -1,25 +1,3 @@
import aliases;
import u = util;
import util;
void
RunTests()
{
TestHashTable();
}
void
TestHashTable()
{
auto ht = u.CreateHashTable!(u64, u64)(8);
u64 value = 55;
ht[3] = value;
auto res = ht[3];
assert(res.ok && value == res.value, "hash table failure");
res = ~ht[3];
assert(res.ok && res.value == value, "hash table delete failure");
res = ~ht[3];
assert(!res.ok, "value not deleted from hash table");
}

View File

@ -5,12 +5,13 @@ import std.stdio;
import std.algorithm.comparison;
import core.stdc.string : strcmp, memcpy;
import std.format : sformat;
import u = util : HashTable, Result, Logf, Log, MB, Delete;
import util;
import alloc;
import platform;
import ap = assets;
import assets;
import renderer;
import std.math.rounding : Ceil = ceil;
import math;
bool g_VLAYER_SUPPORT = false;
bool g_DEBUG_PRINTF = false;
@ -138,7 +139,7 @@ struct Vulkan
u32 frame_index;
u32 semaphore_index;
u.SLList!(SI) cleanup_list;
SLList!(SI) cleanup_list;
PlatformWindow* window;
@ -223,9 +224,9 @@ struct QueueInfo
i32 gfx_index, tfer_index;
VkQueue gfx_queue, tfer_queue;
bool single_queue;
};
}
u.Result!(Vulkan)
Result!(Vulkan)
Init(PlatformWindow* window, u64 permanent_mem, u64 frame_mem)
{
bool success = true;
@ -261,7 +262,7 @@ Init(PlatformWindow* window, u64 permanent_mem, u64 frame_mem)
if (success) InitBuffers(&vk);
if (success) success = InitConversionPipeline(&vk);
u.Result!(Vulkan) result = {
Result!(Vulkan) result = {
ok: success,
value: vk,
};
@ -518,7 +519,7 @@ BeginRender(Vulkan* vk)
sType: VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
imageView: vk.draw_image.view,
imageLayout: vk.draw_image.layout,
loadOp: VK_ATTACHMENT_LOAD_OP_LOAD, // CLEAR instead of LOAD if wanting to clear colors, also clearColor value (or whatever)
loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR, // CLEAR instead of LOAD if wanting to clear colors, also clearColor value (or whatever)
storeOp: VK_ATTACHMENT_STORE_OP_STORE,
};
@ -1262,7 +1263,7 @@ CreateGraphicsPipeline(Vulkan* vk, GfxPipelineInfo* build_info)
VkPipelineRasterizationStateCreateInfo rasterization_info = {
sType: VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
cullMode: VK_CULL_MODE_BACK_BIT,
//cullMode: VK_CULL_MODE_BACK_BIT,
polygonMode: VK_POLYGON_MODE_FILL,
lineWidth: 1.0,
frontFace: VK_FRONT_FACE_COUNTER_CLOCKWISE,
@ -1342,8 +1343,8 @@ CreateGraphicsPipeline(Vulkan* vk, GfxPipelineInfo* build_info)
Arena* arena = &vk.frame_arenas[0];
u8[] vert_bytes = ap.LoadAssetData(arena, build_info.vertex_shader);
u8[] frag_bytes = ap.LoadAssetData(arena, build_info.frag_shader);
u8[] vert_bytes = LoadAssetData(arena, build_info.vertex_shader);
u8[] frag_bytes = LoadAssetData(arena, build_info.frag_shader);
assert(vert_bytes && frag_bytes, "Unable to load shaders");
@ -1422,7 +1423,7 @@ CreateComputePipeline(Vulkan* vk, CompPipelineInfo* comp_info)
},
};
u8[] comp_bytes = ap.LoadAssetData(&vk.frame_arenas[0], comp_info.shader);
u8[] comp_bytes = LoadAssetData(&vk.frame_arenas[0], comp_info.shader);
assert(comp_bytes != null, "Unable to load compute shader data");
Result!(Shader) comp_module = BuildShader(vk, comp_bytes);
@ -1453,10 +1454,6 @@ CreateComputePipeline(Vulkan* vk, CompPipelineInfo* comp_info)
void
SetUniform(Vulkan* vk, GlobalUniforms* globals)
{
globals.res.x = vk.swapchain_extent.width;
globals.res.y = vk.swapchain_extent.height;
globals.projection_matrix = Mat4.perspective(1.57, cast(f32)(globals.res.y) / cast(f32)(globals.res.y), 0.1, 100.0);
vk.global_buf.data[0] = *globals;
VkDescriptorBufferInfo buffer_info = {
@ -1493,7 +1490,7 @@ Destroy(Vulkan* vk)
{
vkDeviceWaitIdle(vk.device);
alias N = u.Node!(SI);
alias N = Node!(SI);
assert(vk.cleanup_list.first != null, "node null");
for(N* node = vk.cleanup_list.first; node != null; node = node.next)
{
@ -1685,7 +1682,7 @@ InitDescriptors(Vulkan* vk)
{
foreach(i; cast(u64)DT.min .. cast(u64)DT.max)
{
vk.desc_bindings[i].lookup_table = u.CreateHashTable!(string, u32)(8);
vk.desc_bindings[i].lookup_table = CreateHashTable!(string, u32)(8);
u32 DESC_MAX_BINDINGS = 512;
vk.desc_bindings[i].free = AllocArray!(u32)(&vk.arena, DESC_MAX_BINDINGS);
@ -2555,7 +2552,7 @@ CheckQueueProperties(Arena *arena, VkPhysicalDevice device, VkSurfaceKHR surface
VkQueueFamilyProperties[] properties = AllocArray!(VkQueueFamilyProperties)(arena, count);
vkGetPhysicalDeviceQueueFamilyProperties(device, &count, properties.ptr);
if (count == 1 && properties[0].queueCount == 1 && u.BitEq(properties[0].queueFlags, T_BIT | C_BIT | G_BIT))
if (count == 1 && properties[0].queueCount == 1 && BitEq(properties[0].queueFlags, T_BIT | C_BIT | G_BIT))
{
current.gfx_index = current.tfer_index = 0;
current.single_queue = true;
@ -2569,13 +2566,13 @@ CheckQueueProperties(Arena *arena, VkPhysicalDevice device, VkSurfaceKHR surface
b32 surface_support;
vkGetPhysicalDeviceSurfaceSupportKHR(device, cast(u32)i, surface, &surface_support);
if (current.gfx_index < 0 && surface_support && u.BitEq(prop.queueFlags, G_BIT))
if (current.gfx_index < 0 && surface_support && BitEq(prop.queueFlags, G_BIT))
{
current.gfx_index = cast(i32)i;
continue;
}
if (u.BitEq(prop.queueFlags, T_BIT | S_BIT) && !u.BitEq(prop.queueFlags, G_BIT | C_BIT))
if (BitEq(prop.queueFlags, T_BIT | S_BIT) && !BitEq(prop.queueFlags, G_BIT | C_BIT))
{
sparse = true;
tfer_only = true;
@ -2583,21 +2580,21 @@ CheckQueueProperties(Arena *arena, VkPhysicalDevice device, VkSurfaceKHR surface
continue;
}
if (!(sparse && tfer_only) && u.BitEq(prop.queueFlags, T_BIT | S_BIT))
if (!(sparse && tfer_only) && BitEq(prop.queueFlags, T_BIT | S_BIT))
{
sparse = true;
current.tfer_index = cast(i32)i;
continue;
}
if (!sparse && !u.BitEq(prop.queueFlags, T_BIT) && u.BitEq(prop.queueFlags, C_BIT))
if (!sparse && !BitEq(prop.queueFlags, T_BIT) && BitEq(prop.queueFlags, C_BIT))
{
tfer_only = true;
current.tfer_index = cast(i32)i;
continue;
}
if (!sparse && !tfer_only && u.BitEq(prop.queueFlags, C_BIT))
if (!sparse && !tfer_only && BitEq(prop.queueFlags, C_BIT))
{
current.tfer_index = cast(i32)i;
}
@ -2615,9 +2612,9 @@ CheckQueueProperties(Arena *arena, VkPhysicalDevice device, VkSurfaceKHR surface
pragma(inline): void
Push(Vulkan* vk, StepInitialized step)
{
u.Node!(SI)* node = Alloc!(u.Node!(SI));
Node!(SI)* node = Alloc!(Node!(SI));
node.value = step;
u.PushFront(&vk.cleanup_list, node, null);
PushFront(&vk.cleanup_list, node, null);
}
void
@ -2928,3 +2925,18 @@ EnableVLayers(Vulkan* vk)
}
}
void
PrintShaderDisassembly(Vulkan* vk, Pipeline* pipeline, VkShaderStageFlagBits stage)
{
version(AMD_GPU)
{
u64 size;
VkResult result = vkGetShaderInfoAMD(vk.device, pipeline.handle, stage, VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD, &size, null);
if (result == VK_SUCCESS)
{
u8[] buf = AllocArray!(u8)(vk.frame_arenas[vk.frame_index], size);
vkGetShaderInfoAMD(vk.device, pipeline.handle, stage, VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD, &size, buf.ptr);
Logf("DISASSEMBLY:\n%r", buf);
}
}
}

View File

@ -1,5 +1,5 @@
public import includes;
import p = platform;
import platform;
import vulkan : Vulkan, VULKAN_LIBS;
// Global Functions
@ -43,6 +43,11 @@ version(Windows)
PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = null;
};
debug
{
PFN_vkGetShaderInfoAMD vkGetShaderInfoAMD = null;
}
// Device Functions
@ -112,14 +117,14 @@ PFN_vkQueueWaitIdle vkQueueWaitIdle = null;
bool
LoadGlobalFunctions()
{
p.Library lib;
p.Function fn;
Library lib;
Function fn;
foreach(name; VULKAN_LIBS)
{
lib = p.LoadLibrary(name);
lib = LoadLibrary(name);
if (lib.ptr)
{
fn = p.LoadFunction(lib, "vkGetInstanceProcAddr");
fn = LoadFunction(lib, "vkGetInstanceProcAddr");
vkGetInstanceProcAddr = cast(PFN_vkGetInstanceProcAddr)fn.ptr;
}
}
@ -205,6 +210,11 @@ LoadDeviceFunctions(Vulkan* vk)
vkWaitSemaphores = cast(PFN_vkWaitSemaphores)vkGetDeviceProcAddr(vk.device, "vkWaitSemaphores");
vkQueueWaitIdle = cast(PFN_vkQueueWaitIdle)vkGetDeviceProcAddr(vk.device, "vkQueueWaitIdle");
version(AMD_GPU)
{
vkGetShaderInfoAMD = cast(PFN_vkGetShaderInfoAMD)vkGetShaderInfoAMD(vk.device, "vkGetShaderInfoAMD");
}
assert(vkCreateSwapchainKHR != null, "LoadDeviceFunctions failure: function pointer is null");
}

View File

@ -22,7 +22,7 @@ mat4 y_matrix = mat4(
void main()
{
gl_Position = G.projection_matrix * G.view_matrix * PC.model_matrix * in_pos;
gl_Position = G.projection_matrix * G.view_matrix * PC.model_matrix * vec4(in_pos.rgb, 1.0);
vec4 col = Materials[nonuniformEXT(PC.mat_id)].diffuse;

View File

@ -5,6 +5,7 @@
layout (set = 0, binding = 0) uniform GlobalUniforms {
mat4 view_matrix;
mat4 projection_matrix;
mat4 view_projection;
vec2 res;
} G;

View File

@ -1,7 +1,6 @@
import core.memory;
import std.stdint;
import dplug.math;
import std.math.trigonometry;
import math;
debug
{
@ -30,10 +29,10 @@ alias b32 = uint;
alias intptr = intptr_t;
alias uintptr = uintptr_t;
alias Vec2 = vec2f;
alias Vec3 = vec3f;
alias Vec4 = vec4f;
alias Vec2 = Vector!(f32, 2);
alias Vec3 = Vector!(f32, 3);
alias Vec4 = Vector!(f32, 4);
alias Mat2 = mat2f;
alias Mat3 = mat3f;
alias Mat4 = mat4f;
alias Mat2 = Matrix!(f32, 2);
alias Mat3 = Matrix!(f32, 3);
alias Mat4 = Matrix!(f32, 4);

View File

@ -1,5 +1,5 @@
import aliases;
import m = math;
import math;
import std.stdio;
import core.stdc.string : memset;
import core.memory;
@ -66,7 +66,7 @@ AllocAlign(Arena* arena, u64 size, u64 alignment)
uintptr mem_pos = cast(uintptr)arena.mem;
uintptr current = mem_pos + arena.pos;
uintptr offset = m.AlignPow2(current, alignment) - mem_pos;
uintptr offset = AlignPow2(current, alignment) - mem_pos;
if (offset+size <= arena.length)
{

View File

@ -1,6 +1,599 @@
import aliases;
import util;
import std.math;
import std.traits;
import inteli;
import std.meta;
import std.format;
import std.stdio;
T AlignPow2(T)(T v, T a)
{
return (v + a - 1) & ~(a - 1);
};
}
f32 Radians(f32 deg)
{
return deg * (PI / 180.0);
}
enum IsVector(T) = is(T : Vector!U, U...);
struct Vector(T, int N)
{
static assert(N > 0 && N <= 4);
enum _N = N;
alias T _T;
union
{
T[N] v;
struct
{
T x;
alias x r;
static if (N > 1)
{
T y;
alias y g;
}
static if (N > 2)
{
T z;
alias z b;
}
static if (N > 3)
{
T w;
alias w a;
}
}
}
nothrow @nogc pure:
this(Args...)(Args args)
{
static if (args.length == 1)
{
opAssign!(Args[0])(args[0]);
}
else static if (args.length == N)
{
mixin(GenerateLoop!("v[@] = args[@];", N)());
}
else static if (args.length == 2 && N == 4)
{
v[0] = args[0];
v[1] = args[0];
v[2] = args[0];
v[3] = args[1];
}
else
{
static assert(false, "Invalid Vector constructor");
}
}
ref Vector opAssign(U)(U x) if (isAssignable!(T, U))
{
mixin(GenerateLoop!("v[@] = x;", N)());
return this;
}
ref Vector opAssign(U)(U arr) if (isStaticArray!(U) && isAssignable!(T, typeof(arr[0])) && arr.length == N)
{
mixin(GenerateLoop!("v[@] = arr[@];", N)());
return this;
}
ref Vector opAssign(U)(U arr) if (isDynamicArray!(U) && isAssignable!(T, typeof(arr[0])))
{
mixin(GenerateLoop!("v[@] = arr[@];", N)());
return this;
}
ref Vector opAssign(U)(U u) if (is(U : Vector))
{
v[] = u.v[];
return this;
}
ref Vector opAssign(U)(U x) if (IsVector!(U) && isAssignable!(T, U._T) && (!is(U: Vector)) && (U._N == _N))
{
mixin(GenerateLoop!("v[@] = x.v[@];", N)());
return this;
}
inout(T)* ptr() inout @property
{
return v.ptr;
}
bool opEquals(U)(U other) if (IsConvertible!(U))
{
Vector conv = other;
return opEquals(conv);
}
Vector opEquals(U)(U other) if (is(U: Vector))
{
bool result = true;
foreach(i; 0 .. N)
{
if (v[i] != other.v[i])
{
result = false;
break;
}
}
return result;
}
int opDollar()
{
return N;
}
T[] opSlice()
{
return v[];
}
Vector opUnary(string op)() if (op == "+" || op == "-" || op == "~" || op == "!")
{
Vector result;
mixin(GenerateLoop!("res.v[@] = " ~ op ~ " v[@];", N)());
return res;
}
ref Vector opOpAssign(string op, U)(U value) if (is(U: Vector))
{
mixin(GenerateLoop!("v[@] " ~ op ~ "= value.v[@];", N)());
return this;
}
ref Vector opOpAssign(string op, U)(U value) if (IsConvertible!(U))
{
Vector conv = value;
return opOpAssign!(op)(conv);
}
@property auto opDispatch(string op, U = void)() if (ValidSwizzle!(op) && op.length <= 4)
{
Vector!(T, op.length) result;
enum index_tuple = SwizzleTuple!(op);
static foreach(i, index; index_tuple)
{
result.v[i] = v[index];
}
return result;
}
@property void opDispatch(string op, U)(U x) if ((op.length > 1) && ValidUniqueSwizzle!(op) && is(typeof(Vector!(T, op.length)(x))))
{
Vector!(T, op.length) conv = x;
enum index_tuple = SwizzleTuple!(op);
static foreach(i, index; index_tuple)
{
v[index] = conv[i];
}
}
Vector opBinary(string op, U)(U operand) if (is(U: Vector) || (IsConvertible!(U)))
{
Vector result;
static if (is(U: T))
{
mixin(GenerateLoop!("result.v[@] = cast(T)(v[@] " ~ op ~ " operand);", N)());
}
else
{
Vector other = operand;
mixin(GenerateLoop!("result.v[@] = cast(T)(v[@] " ~ op ~ " other.v[@]);", N)());
}
return result;
}
ref T opIndex(size_t i)
{
return v[i];
}
ref const(T) opIndex(size_t i) const
{
return v[i];
}
T opIndexAssign(U : T)(U x, size_t i)
{
return v[i] = x;
}
U opCast(U)() if (IsVector!(U) && (U._N == _N))
{
U result;
mixin(GenerateLoop!("res.v[@] = cast(U._T)v[@];", N)());
return result;
}
template IsConvertible(T)
{
enum bool IsConvertible = (!is(T : Vector)) && is(typeof({ T x; Vector v = x; }()));
}
template SwizzleIndex(char c)
{
static if ((c == 'x' || c == 'r') && N > 0)
enum SwizzleIndex = 0;
else static if ((c == 'y' || c == 'g') && N > 1)
enum SwizzleIndex = 1;
else static if ((c == 'z' || c == 'b') && N > 2)
enum SwizzleIndex = 2;
else static if ((c == 'w' || c == 'a') && N > 3)
enum SwizzleIndex = 3;
else
enum SwizzleIndex = -1;
}
template SwizzleSet(char c)
{
static if (c == 'x' || c == 'y' || c == 'z' || c == 'w')
enum SwizzleSet = 0;
else static if (c == 'r' || c == 'g' || c == 'b' || c == 'a')
enum SwizzleSet = 1;
else
enum SwizzleSet = -1;
}
template SwizzleTuple(string op)
{
enum op_length = op.length;
static if (op.length == 0)
enum SwizzleTuple = [];
else
enum SwizzleTuple = [ SwizzleIndex!(op[0])] ~ SwizzleTuple!(op[1 .. op.length]);
}
template SearchString(char c, string s)
{
static if (s.length == 0)
{
enum bool result = false;
}
else
{
enum string tail = s[1 .. s.length];
enum bool result = (s[0] == c) || SearchString!(c, tail).result;
}
}
template UniqueChars(string s)
{
static if (s.length == 1)
{
enum bool result = true;
}
else
{
enum tail = s[1 .. s.length];
enum bool result = !(SearchString!(s[0], tail).result) && UniqueChars!(tail).result;
}
}
template ValidSwizzle(string op, int last_swizzle = -1)
{
static if (op.length == 0)
{
enum bool ValidSwizzle = true;
}
else
{
enum length = op.length;
enum int swizzle_set = SwizzleSet!(op[0]);
enum bool valid_swizzle_set = (last_swizzle == -1 || (swizzle_set == last_swizzle));
enum bool ValidSwizzle = (SwizzleIndex!(op[0]) != -1) && valid_swizzle_set && ValidSwizzle!(op[1 .. length], swizzle_set);
}
}
template ValidUniqueSwizzle(string op)
{
static if (ValidSwizzle!(op))
{
enum ValidUniqueSwizzle = UniqueChars!(op).result;
}
else
{
enum ValidUniqueSwizzle = false;
}
}
}
struct Matrix(T, int D)
{
static assert(D > 0 && D <= 4);
alias Vector!(T, D) MatrixVec;
alias T _T;
enum N = D*D;
union
{
T[N] v;
MatrixVec[D] vec;
}
// TODO: setup @nogc nothrow
this(U...)(U values)
{
static if ((U.length == N) && allSatisfy!(IsTypeAssignable, U))
{
static foreach(i, x; values)
{
v[i] = x;
}
}
else static if ((U.length == 1) && (isAssignable!(U[0])) && (!is(U[0] : Matrix)))
{
v[] = values[0];
}
else static assert(false, "Cannot construct matrix with provided parameters");
}
this(U)(T x)
{
static foreach(i; 0 .. N)
{
v[i] = x;
}
}
@property inout(T)* ptr() inout
{
return v.ptr;
}
ref Matrix opAssign(U : T)(U x)
{
static foreach(i; 0 .. N)
{
v[i] = x;
}
return this;
}
ref Matrix opAssign(U : Matrix)(U x)
{
static foreach(i; 0 .. N)
{
v[i] = x.v[i];
}
return this;
}
ref Matrix opAssign(U)(U x) if (IsMatrixInstantiation!(U) && is(U._T : _T) && (!is(U: Matrix) && (U.N != N)))
{
static foreach(i; 0 .. N)
{
v[i] = x.v[i];
}
return this;
}
Matrix opBinary(string op)(T scalar) if (op == "*")
{
Matrix result;
static foreach(i; 0 .. N)
{
result.v[i] = v[i] * scalar;
}
return result;
}
MatrixVec opBinary(string op)(T factor) if (op == "*")
{
Matrix result;
return result;
}
static if (D == 4)
{
Matrix opBinary(string op, U)(U x) if (is(U: Matrix!(T, D)) && is(T: f32))
{
Matrix result;
auto res = &result;
auto l = &this;
auto r = &x;
asm pure @trusted @nogc nothrow
{
mov R8, l;
mov R9, r;
mov R10, res;
movups XMM0, vec.offsetof[R8];
movups XMM1, x.vec.offsetof[R9];
movups XMM2, x.vec.offsetof[R9]+16;
movups XMM3, x.vec.offsetof[R9]+32;
movups XMM4, x.vec.offsetof[R9]+48;
movups XMM5, XMM1;
shufps XMM5, XMM5, 0; // XMM5 = vec.xxxx;
mulps XMM5, XMM0;
movups XMM6, XMM5; // XMM6 = col1;
movups XMM5, XMM2;
shufps XMM5, XMM5, 0;
mulps XMM5, XMM0;
movups XMM7, XMM5; // XMM7 = col2;
movups XMM5, XMM3;
shufps XMM5, XMM5, 0;
mulps XMM5, XMM0;
movups XMM8, XMM5; // XMM8 = col3;
movups XMM5, XMM3;
shufps XMM5, XMM5, 0;
mulps XMM5, XMM0;
movups XMM9, XMM5; // XMM9 = col4;
movups XMM0, vec.offsetof[R8]+16;
movups XMM5, XMM1;
shufps XMM5, XMM5, 85; // XMM5 = vec.yyyy;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM6, XMM10;
movups XMM5, XMM2;
shufps XMM5, XMM5, 85;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM7, XMM10;
movups XMM5, XMM3;
shufps XMM5, XMM5, 85;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM8, XMM10;
movups XMM5, XMM4;
shufps XMM5, XMM5, 85;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM9, XMM10;
movups XMM0, vec.offsetof[R8]+32;
movups XMM5, XMM1;
shufps XMM5, XMM5, 170; // XMM5 = vec.zzzz;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM6, XMM10;
movups XMM5, XMM2;
shufps XMM5, XMM5, 170;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM7, XMM10;
movups XMM5, XMM3;
shufps XMM5, XMM5, 170;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM8, XMM10;
movups XMM5, XMM4;
shufps XMM5, XMM5, 170;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM9, XMM10;
movups XMM0, vec.offsetof[R8]+48;
movups XMM5, XMM1;
shufps XMM5, XMM5, 255; // XMM5 = vec.wwww;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM6, XMM10;
movups XMM5, XMM2;
shufps XMM5, XMM5, 255;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM7, XMM10;
movups XMM5, XMM3;
shufps XMM5, XMM5, 255;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM8, XMM10;
movups XMM5, XMM4;
shufps XMM5, XMM5, 255;
movups XMM10, XMM0;
mulps XMM10, XMM5;
addps XMM9, XMM10;
movups result.vec.offsetof[R10]+00, XMM6;
movups result.vec.offsetof[R10]+16, XMM7;
movups result.vec.offsetof[R10]+32, XMM8;
movups result.vec.offsetof[R10]+48, XMM9;
}
return result;
}
}
template IsTypeAssignable(U)
{
enum bool IsTypeAssignable = std.traits.isAssignable!(T, U);
}
template IsMatrixInstantiation(U)
{
static void IsMatrix(T, int D)(Matrix!(T, D) x) {}
enum bool IsMatrixInstantiation = is(typeof(IsMatrix(U.init)));
}
}
nothrow @nogc pragma(inline): Mat4
Mat4Identity()
{
return Mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
nothrow @nogc pragma(inline): Mat3
Mat3Identity()
{
return Mat3(
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0
);
}
nothrow @nogc pragma(inline): Mat2
Mat2Identity()
{
return Mat2(
1.0, 0.0,
0.0, 1.0
);
}
T SquaredMagnitude(T)(T v) if (IsVector!(T))
{
T sum_squares = 0;
mixin(GenerateLoop!("sum_squares += v.v[@] * v.v[@];", v._N)());
return sum_squares;
}
T SquaredDistTo(T)(T l, T r) if (IsVector!(T))
{
return SquaredMagnitude(r - l);
}

View File

@ -3,14 +3,23 @@ import xxhash3;
import includes;
import std.stdio;
import core.stdc.string : memset;
import a = alloc;
import alloc;
import core.simd;
import std.conv;
import std.string;
void
Logf(Args...)(string fmt, Args args)
{
try
{
writefln(fmt, args);
}
catch (Exception e)
{
assert(false, "Incompatible format type");
}
}
void
Log(string str)
@ -47,7 +56,7 @@ ConvertColor(Vec4 *dst, u32 src)
{
if (src == 0)
{
dst.v[] = 0.0;
dst.rgb = 0.0;
dst.a = 1.0;
}
else
@ -232,8 +241,8 @@ struct HashTable(K, V)
HashTable!(K, V)
CreateHashTable(K, V)(u64 size)
{
auto nil = a.Alloc!(Node!(KVPair!(K, V)));
auto lists = a.AllocArray!(SLList!(KVPair!(K, V)))(size);
auto nil = Alloc!(Node!(KVPair!(K, V)));
auto lists = AllocArray!(SLList!(KVPair!(K, V)))(size);
HashTable!(K, V) table = {
lists: lists,
@ -278,7 +287,7 @@ Push(K, V)(HashTable!(K, V)* ht, K key, V value)
}
else
{
node = a.Alloc!(N);
node = Alloc!(N);
}
node.next = ht.nil;
@ -519,3 +528,33 @@ DeltaTime(Timer* t)
t.prev = time;
return cast(f32)(step) / cast(f32)(t.cpu_freq);
}
static string
IntToStr(int n) nothrow pure @safe
{
string result;
static immutable string[] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
if (n < table.length)
{
result = table[n];
}
else
{
result = to!string(n);
}
return result;
}
static string
GenerateLoop(string format_string, int N)() nothrow pure @safe
{
string result;
for (int i = 0; i < N; i++)
{
result ~= format_string.replace("@", IntToStr(i));
}
return result;
}