Make love.math a singleton. Remove code duplication. Add love.math.triangulate.

love::math::Math is now a singleton, so other modules can use it via
love::math::Math::instance.


Removed duplicate implementations of various rng helper functions.
ParticleSystem now uses love::math::Math's RNG.


New function: love.math.triangulate(vertices)

Accepts a table or list of x/y coordinate pairs and returns a table of tables.
The inner tables are the triangles the polygon is composed of.

Works on all *simple* polygons, i.e. a closed chain of vertices that does not
intersect itself. Attempting to triangulate non-simple polygons is undefined
behavior - in the best case it throws an error, in the worst case it returns
an invalid triangulation.

Polygons must be ordered in *clockwise order* with respect to the love
coordinate system. Attempting to triangulate a ccw polygon will throw an
error.

Examples:

  triangles = love.math.triangulate(0,0, 1,1, 2,0, 2,2, 0,2)
  triangles == {
      {1,1, 2,0, 2,2},
      {1,1, 2,2, 0,2},
      {1,1, 0,2, 0,0},
  }

  triangles = love.math.triangulate({0,0, 1,1, 2,0, 2,2, 0,2})
  -- same as above

  triangles = love.math.triangulate(0,2, 2,2, 2,0, 1,1, 0,0)
  -- error - polygons is in counterclockwise order

  triangles = love.math.triangulate(0,0, 1,3, 2,0, 2,2, 0,2)
  -- undefined behavior - polygon intersects itself (because of edge 1,3)
This commit is contained in:
vrld
2013-03-13 20:48:39 +01:00
parent d383434edf
commit ece4ff6472
8 changed files with 347 additions and 185 deletions
-33
View File
@@ -1,33 +0,0 @@
#include "math.h"
#include <limits>
#include <cmath>
namespace
{
// The BoxMuller transform generates two random numbers, one of which we
// cache here. A value of +infinity is used to signal the cache is invalid
// and that new numbers have to be generated.
float last_randnormal = std::numeric_limits<float>::infinity();
}
namespace love
{
float random_normal(float o)
{
// number in cache?
if (last_randnormal != std::numeric_limits<float>::infinity())
{
float r = last_randnormal;
last_randnormal = std::numeric_limits<float>::infinity();
return r * o;
}
// else: generate numbers using the Box-Muller transform
float a = sqrt(-2.0f * log(1. - random()));
float b = float(LOVE_M_PI) * 2.0f * (1. - random());
last_randnormal = a * cos(b);
return a * sin(b) * o;
}
} // namespace love
+8 -36
View File
@@ -68,6 +68,14 @@ struct vertex
float s, t;
};
struct Triangle
{
Triangle(const vertex &x, const vertex &y, const vertex &z)
: a(x), b(y), c(z)
{}
vertex a, b, c;
};
inline int next_p2(int x)
{
x += (x == 0);
@@ -81,42 +89,6 @@ inline float next_p2(float x)
return static_cast<float>(next_p2(static_cast<int>(x)));
}
/**
* Draws a random number from a uniform distribution.
* @returns Uniformly distributed random number in [0:1).
*/
inline float random()
{
// to satisfy picky compilers...
return float(double(rand() % RAND_MAX) / double(RAND_MAX));
}
/**
* Draws a random number from a uniform distribution.
* @return Uniformly distributed random number in [0:max).
*/
inline float random(float max)
{
return random() * max;
}
/**
* Draws a random number from a uniform distribution.
* @return Uniformly distributed random number in [min:max).
*/
inline float random(float min, float max)
{
return random(max - min) + min;
}
/**
* Draws a random number from a normal/gaussian distribution.
* @param o Standard deviation of the distribution.
* @returns Normal distributed random number with mean 0 and variance o^2.
*/
float random_normal(float o = 1.);
#define random_gaussian random_normal
} // love
#endif // LOVE_MATH_H
+17 -14
View File
@@ -21,11 +21,14 @@
#include "ParticleSystem.h"
#include "common/math.h"
#include "modules/math/Math.h"
#include "OpenGL.h"
#include <cmath>
#include <cstdlib>
using love::math::Math;
namespace love
{
namespace graphics
@@ -45,7 +48,7 @@ float calculate_variation(float inner, float outer, float var)
{
float low = inner - (outer/2.0f)*var;
float high = inner + (outer/2.0f)*var;
float r = random();
float r = Math::instance.random();
return low*(1-r)+high*r;
}
@@ -124,7 +127,7 @@ void ParticleSystem::add()
if (min == max)
pLast->life = min;
else
pLast->life = random(min, max);
pLast->life = Math::instance.random(min, max);
pLast->lifetime = pLast->life;
pLast->position[0] = position.getX();
@@ -133,12 +136,12 @@ void ParticleSystem::add()
switch (areaSpreadDistribution)
{
case DISTRIBUTION_UNIFORM:
pLast->position[0] += random(-areaSpread.getX(), areaSpread.getX());
pLast->position[1] += random(-areaSpread.getY(), areaSpread.getY());
pLast->position[0] += Math::instance.random(-areaSpread.getX(), areaSpread.getX());
pLast->position[1] += Math::instance.random(-areaSpread.getY(), areaSpread.getY());
break;
case DISTRIBUTION_NORMAL:
pLast->position[0] += random_normal(areaSpread.getX());
pLast->position[1] += random_normal(areaSpread.getY());
pLast->position[0] += Math::instance.randnormal(areaSpread.getX());
pLast->position[1] += Math::instance.randnormal(areaSpread.getY());
break;
case DISTRIBUTION_NONE:
default:
@@ -147,37 +150,37 @@ void ParticleSystem::add()
min = direction - spread/2.0f;
max = direction + spread/2.0f;
pLast->direction = random(min, max);
pLast->direction = Math::instance.random(min, max);
pLast->origin = position;
min = speedMin;
max = speedMax;
float speed = random(min, max);
float speed = Math::instance.random(min, max);
pLast->speed = love::Vector(cos(pLast->direction), sin(pLast->direction));
pLast->speed *= speed;
min = gravityMin;
max = gravityMax;
pLast->gravity = random(min, max);
pLast->gravity = Math::instance.random(min, max);
min = radialAccelerationMin;
max = radialAccelerationMax;
pLast->radialAcceleration = random(min, max);
pLast->radialAcceleration = Math::instance.random(min, max);
min = tangentialAccelerationMin;
max = tangentialAccelerationMax;
pLast->tangentialAcceleration = random(min, max);
pLast->tangentialAcceleration = Math::instance.random(min, max);
pLast->sizeOffset = random(sizeVariation); // time offset for size change
pLast->sizeIntervalSize = (1.0f - random(sizeVariation)) - pLast->sizeOffset;
pLast->sizeOffset = Math::instance.random(sizeVariation); // time offset for size change
pLast->sizeIntervalSize = (1.0f - Math::instance.random(sizeVariation)) - pLast->sizeOffset;
pLast->size = sizes[(size_t)(pLast->sizeOffset - .5f) * (sizes.size() - 1)];
min = rotationMin;
max = rotationMax;
pLast->spinStart = calculate_variation(spinStart, spinEnd, spinVariation);
pLast->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation);
pLast->rotation = random(min, max);
pLast->rotation = Math::instance.random(min, max);
pLast->color = colors[0];
+198
View File
@@ -0,0 +1,198 @@
/**
* Copyright (c) 2006-2013 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.
**/
#include "modules/math/Math.h"
#include "common/math.h"
#include <cmath>
#include <list>
#include <iostream>
using namespace std;
using love::vertex;
namespace
{
// check if an angle is oriented counter clockwise
inline bool is_oriented_ccw(const vertex &a, const vertex &b, const vertex &c)
{
// return det(b-a, c-a) >= 0
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) >= 0;
}
// check if a and b are on the same side of the line c->d
bool on_same_side(const vertex &a, const vertex &b, const vertex &c, const vertex &d)
{
float px = d.x - c.x, py = d.y - c.y;
// return det(p, a-c) * det(p, b-c) >= 0
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;
}
// checks is p is contained in the triangle abc
inline bool point_in_triangle(const vertex &p, const vertex &a, const vertex &b, const vertex &c)
{
return on_same_side(p,a, b,c) && on_same_side(p,b, a,c) && on_same_side(p,c, a,b);
}
// checks if any vertex in `vertices' is in the triangle abc.
bool any_point_in_triangle(const list<const vertex *> &vertices, const vertex &a, const vertex &b, const vertex &c)
{
list<const vertex *>::const_iterator it, end = vertices.end();
for (it = vertices.begin(); it != end; ++it)
{
const vertex *p = *it;
if ((p != &a) && (p != &b) && (p != &c) && point_in_triangle(*p, a,b,c)) // oh god...
return true;
}
return false;
}
inline bool is_ear(const vertex &a, const vertex &b, const vertex &c, const list<const vertex *> &vertices)
{
return is_oriented_ccw(a,b,c) && !any_point_in_triangle(vertices, a,b,c);
}
}
namespace love
{
namespace math
{
Math Math::instance;
// 64 bit Xorshift implementation taken from the end of Sec. 3 (page 4) in
// George Marsaglia, "Xorshift RNGs", Journal of Statistical Software, Vol.8 (Issue 14), 2003
Math::Math()
: last_randnormal(numeric_limits<double>::infinity())
{
// because it is too big for some compilers to handle ... if you know what
// i mean
union
{
uint64_t b64;
struct
{
uint32_t a;
uint32_t b;
} b32;
} converter;
#ifdef LOVE_BIG_ENDIAN
converter.b32.a = 0x0139408D;
converter.b32.b = 0xCBBF7A44;
#else
converter.b32.b = 0x0139408D;
converter.b32.a = 0xCBBF7A44;
#endif
rng_state = converter.b64;
// prevent the runtime from free()-ing this
retain();
}
uint32_t Math::rand()
{
rng_state ^= (rng_state << 13);
rng_state ^= (rng_state >> 7);
rng_state ^= (rng_state << 17);
return rng_state;
}
// BoxMuller transform
double Math::randnormal(double stddev)
{
// use cached number if possible
if (last_randnormal != numeric_limits<double>::infinity())
{
double r = last_randnormal;
last_randnormal = numeric_limits<double>::infinity();
return r * stddev;
}
double r = sqrt(-2.0 * log(1. - random()));
double phi = 2.0 * LOVE_M_PI * (1. - random());
last_randnormal = r * cos(phi);
return r * sin(phi) * stddev;
}
vector<Triangle> Math::triangulate(const vector<vertex> &polygon)
{
if (polygon.size() < 3)
throw love::Exception("Not a ploygon");
else if (polygon.size() == 3)
return vector<Triangle>(1, Triangle(polygon[0], polygon[1], polygon[2]));
vector<size_t> next_vertex(polygon.size()), prev_vertex(polygon.size());
// collect list of connections
for (size_t i = 0; i < polygon.size(); ++i)
{
next_vertex[i] = i+1;
prev_vertex[i] = i-1;
}
next_vertex[next_vertex.size()-1] = 0;
prev_vertex[0] = prev_vertex.size()-1;
// collect list of concave polygons
list<const vertex *> concave_vertices;
for (size_t i = 0; i < polygon.size(); ++i)
{
if (!is_oriented_ccw(polygon[prev_vertex[i]], polygon[i], polygon[next_vertex[i]]))
concave_vertices.push_back(&polygon[i]);
}
// triangulation according to kong
vector<Triangle> triangles;
size_t n_vertices = polygon.size();
size_t current = 1, skipped = 0, next, prev;
while (n_vertices > 3)
{
next = next_vertex[current];
prev = prev_vertex[current];
const vertex &a = polygon[prev], &b = polygon[current], &c = polygon[next];
if (is_ear(a,b,c, concave_vertices))
{
triangles.push_back(Triangle(a,b,c));
next_vertex[prev] = next;
prev_vertex[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_vertex[current];
prev = prev_vertex[current];
triangles.push_back(Triangle(polygon[prev], polygon[current], polygon[next]));
return triangles;
}
} // math
} // love
@@ -23,21 +23,23 @@
// LOVE
#include "common/Module.h"
#include "common/math.h"
// STL
#include <limits>
#include <stdint.h>
#include <vector>
namespace love
{
namespace math
{
class ModMath : public Module
class Math : public Module
{
public:
ModMath();
virtual ~ModMath() {}
virtual ~Math()
{}
/** Set pseudo random seed.
*
@@ -47,7 +49,7 @@ public:
*/
inline void randomseed(uint64_t seed)
{
RNGState.seed = seed;
rng_state = seed;
}
/** Return uniformly distributed pseudo random integer.
@@ -65,6 +67,24 @@ public:
return double(rand()) / (double(std::numeric_limits<uint32_t>::max()) + 1.0);
}
/** Get uniformly distributed pseudo random number in [0,max).
*
* @returns Pseudo random number in [0,max).
*/
inline double random(double max)
{
return random() * max;
}
/** Get uniformly distributed pseudo random number in [min, max).
*
* @returns Pseudo random number in [min, max).
*/
inline double random(double min, double max)
{
return random() * (max - min) + min;
}
/** Get normally distributed pseudo random number.
*
* @param stddev Standard deviation of the distribution.
@@ -77,12 +97,19 @@ public:
return "love.math";
}
/** Triangulate a simple polygon.
* @param polygon Polygon to triangulate. Must not intersect itself.
* @returns List of triangles the polygon is composed of.
*/
std::vector<Triangle> triangulate(const std::vector<vertex> &polygon);
static Math instance;
private:
struct
{
uint64_t seed;
double last_randnormal;
} RNGState;
Math();
uint64_t rng_state;
double last_randnormal;
};
} // math
-66
View File
@@ -1,66 +0,0 @@
/**
* Copyright (c) 2006-2013 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.
**/
#include "ModMath.h"
#include "common/math.h"
#include <cmath>
namespace love
{
namespace math
{
// 64 bit Xorshift implementation taken from the end of Sec. 3 (page 4) in
// George Marsaglia, "Xorshift RNGs", Journal of Statistical Software, Vol.8 (Issue 14), 2003
ModMath::ModMath()
{
RNGState.seed = 0x0139408DCBBF7A44;
RNGState.last_randnormal = std::numeric_limits<double>::infinity();
}
uint32_t ModMath::rand()
{
uint64_t &x = RNGState.seed;
x ^= (x << 13);
x ^= (x >> 7);
x ^= (x << 17);
return x;
}
// BoxMuller transform
double ModMath::randnormal(double stddev)
{
if (RNGState.last_randnormal != std::numeric_limits<double>::infinity())
{
double r = RNGState.last_randnormal;
RNGState.last_randnormal = std::numeric_limits<double>::infinity();
return r * stddev;
}
double r = sqrt(-2.0 * log(1. - random()));
double phi = 2.0 * LOVE_M_PI * (1. - random());
RNGState.last_randnormal = r * cos(phi);
return r * sin(phi) * stddev;
}
} // math
} // love
+87 -27
View File
@@ -19,40 +19,32 @@
**/
#include "wrap_Math.h"
#include "ModMath.h"
#include "modules/math/Math.h"
#include <cmath>
#include <iostream>
namespace
{
union SeedConverter
{
double seed_double;
uint64_t seed_uint;
};
} // anonymous namespace
namespace love
{
namespace math
{
static ModMath *instance = 0;
int w_randomseed(lua_State *L)
{
SeedConverter s;
union
{
double seed_double;
uint64_t seed_uint;
} s;
s.seed_double = luaL_checknumber(L, 1);
instance->randomseed(s.seed_uint);
Math::instance.randomseed(s.seed_uint);
return 0;
}
int w_random(lua_State *L)
{
double r = instance->random();
double r = Math::instance.random();
int l, u;
// verbatim from lua 5.1.4 source code: lmathlib.c:185 ff.
switch (lua_gettop(L))
@@ -90,17 +82,92 @@ int w_randnormal(lua_State *L)
stddev = luaL_optnumber(L, 1, 1.);
}
double r = instance->randnormal(stddev);
double r = Math::instance.randnormal(stddev);
lua_pushnumber(L, r + mean);
return 1;
}
int w_triangulate(lua_State *L)
{
std::vector<vertex> vertices;
if (lua_istable(L, 1))
{
size_t top = lua_objlen(L, 1);
vertices.reserve(top / 2);
for (size_t i = 1; i <= top; i += 2)
{
lua_rawgeti(L, 1, i);
lua_rawgeti(L, 1, i+1);
vertex v;
v.x = luaL_checknumber(L, -2);
v.y = luaL_checknumber(L, -1);
vertices.push_back(v);
lua_pop(L, 2);
}
}
else
{
size_t top = lua_gettop(L);
vertices.reserve(top / 2);
for (size_t i = 1; i <= top; i += 2)
{
vertex v;
v.x = luaL_checknumber(L, i);
v.y = luaL_checknumber(L, i+1);
vertices.push_back(v);
}
}
if (vertices.size() < 3)
return luaL_error(L, "Need at least 3 vertices to triangulate");
std::vector<Triangle> triangles;
try
{
if (vertices.size() == 3)
triangles.push_back(Triangle(vertices[0], vertices[1], vertices[2]));
else
triangles = Math::instance.triangulate(vertices);
}
catch (love::Exception &e)
{
return luaL_error(L, e.what());
}
lua_createtable(L, triangles.size(), 0);
for (size_t i = 0; i < triangles.size(); ++i)
{
Triangle &tri = triangles[i];
lua_createtable(L, 6, 0);
lua_pushnumber(L, tri.a.x);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, tri.a.y);
lua_rawseti(L, -2, 2);
lua_pushnumber(L, tri.b.x);
lua_rawseti(L, -2, 3);
lua_pushnumber(L, tri.b.y);
lua_rawseti(L, -2, 4);
lua_pushnumber(L, tri.c.x);
lua_rawseti(L, -2, 5);
lua_pushnumber(L, tri.c.y);
lua_rawseti(L, -2, 6);
lua_rawseti(L, -2, i+1);
}
return 1;
}
// List of functions to wrap.
static const luaL_Reg functions[] =
{
{ "randomseed", w_randomseed },
{ "random", w_random },
{ "randnormal", w_randnormal },
{ "triangulate", w_triangulate },
{ 0, 0 }
};
@@ -111,16 +178,9 @@ static const lua_CFunction types[] =
extern "C" int luaopen_love_math(lua_State *L)
{
if (instance == 0)
instance = new love::math::ModMath();
else
instance->retain();
if (instance == 0)
return luaL_error(L, "Could not open module math.");
Math::instance.retain();
WrappedModule w;
w.module = instance;
w.module = &Math::instance;
w.name = "math";
w.flags = MODULE_T;
w.functions = functions;
+1
View File
@@ -33,6 +33,7 @@ namespace math
int w_randomseed(lua_State *L);
int w_random(lua_State *L);
int w_randnormal(lua_State *L);
int w_triangulate(lua_State *L);
extern "C" LOVE_EXPORT int luaopen_love_math(lua_State *L);
} // random