Browse Source

Update for 0.11.0;

bjorn 7 years ago
parent
commit
a6f7cb84a7
55 changed files with 3112 additions and 642 deletions
  1. 4 3
      app/cry.lua
  2. 1 1
      app/menu.lua
  3. 2 2
      app/mobile.lua
  4. 3 2
      app/play.lua
  5. 1 1
      app/rattle.lua
  6. 9 8
      app/sleep.lua
  7. BIN
      art/skyboxes/gg_dn.jpg
  8. BIN
      art/skyboxes/gg_ft.jpg
  9. BIN
      art/skyboxes/gg_lf.jpg
  10. 13 0
      art/skyboxes/gg_readme.txt
  11. BIN
      art/skyboxes/gg_rt.jpg
  12. BIN
      art/skyboxes/gg_up.jpg
  13. BIN
      art/skyboxes/posz.jpg
  14. BIN
      art/skyboxes/stormydays_bk.tga
  15. BIN
      art/skyboxes/stormydays_dn.tga
  16. BIN
      art/skyboxes/stormydays_ft.tga
  17. BIN
      art/skyboxes/stormydays_lf.tga
  18. BIN
      art/skyboxes/stormydays_rt.tga
  19. BIN
      art/skyboxes/stormydays_up.tga
  20. 0 15
      lib/cpml/.atom-build.json
  21. 0 0
      lua_modules/cpml/.coveralls.yml
  22. 0 0
      lua_modules/cpml/.editorconfig
  23. 0 0
      lua_modules/cpml/.gitignore
  24. 0 0
      lua_modules/cpml/.travis.yml
  25. 2 0
      lua_modules/cpml/LICENSE.md
  26. 0 0
      lua_modules/cpml/README.md
  27. 31 0
      lua_modules/cpml/cpml-scm-1.rockspec
  28. 9 0
      lua_modules/cpml/doc/config.ld
  29. 1 1
      lua_modules/cpml/init.lua
  30. 166 0
      lua_modules/cpml/modules/bound2.lua
  31. 166 0
      lua_modules/cpml/modules/bound3.lua
  32. 88 22
      lua_modules/cpml/modules/color.lua
  33. 0 0
      lua_modules/cpml/modules/constants.lua
  34. 124 64
      lua_modules/cpml/modules/intersect.lua
  35. 66 43
      lua_modules/cpml/modules/mat4.lua
  36. 6 9
      lua_modules/cpml/modules/mesh.lua
  37. 20 20
      lua_modules/cpml/modules/octree.lua
  38. 178 177
      lua_modules/cpml/modules/quat.lua
  39. 4 8
      lua_modules/cpml/modules/simplex.lua
  40. 64 43
      lua_modules/cpml/modules/utils.lua
  41. 106 97
      lua_modules/cpml/modules/vec2.lua
  42. 120 118
      lua_modules/cpml/modules/vec3.lua
  43. 180 0
      lua_modules/cpml/spec/bound2_spec.lua
  44. 219 0
      lua_modules/cpml/spec/bound3_spec.lua
  45. 24 0
      lua_modules/cpml/spec/color_spec.lua
  46. 277 0
      lua_modules/cpml/spec/intersect_spec.lua
  47. 429 0
      lua_modules/cpml/spec/mat4_spec.lua
  48. 12 0
      lua_modules/cpml/spec/mesh_spec.lua
  49. 34 0
      lua_modules/cpml/spec/octree_spec.lua
  50. 317 0
      lua_modules/cpml/spec/quat_spec.lua
  51. 50 0
      lua_modules/cpml/spec/utils_spec.lua
  52. 185 0
      lua_modules/cpml/spec/vec2_spec.lua
  53. 199 0
      lua_modules/cpml/spec/vec3_spec.lua
  54. 0 0
      lua_modules/lume.lua
  55. 2 8
      main.lua

+ 4 - 3
app/cry.lua

@@ -1,8 +1,8 @@
 local cry = {}
 local controllers = require 'app/controllers'
 local rattle = require 'app/rattle'
-local vec3 = require('lib/cpml').vec3
-local quat = require('lib/cpml').quat
+local vec3 = require('cpml').vec3
+local quat = require('cpml').quat
 local drawBlock = require('app/block')
 
 cry.won = false
@@ -17,7 +17,8 @@ function cry:init()
     'art/skyboxes/sea_lf.jpg'
   )
 
-  self.floor = g.newMesh(lovr.headset.getBoundsGeometry())
+  local w, d = lovr.headset.getBoundsDimensions()
+  self.floor = g.newMesh({{ -w, 0, -d }, { -w, 0, d }, { w, 0, -d }, { w, 0, d }}, 'strip')
 
   self.block = {}
   self.block.position = vec3(0, 1, -4)

+ 1 - 1
app/menu.lua

@@ -2,7 +2,7 @@ local menu = {}
 local controllers = require 'app/controllers'
 local rattle = require 'app/rattle'
 local mobile = require 'app/mobile'
-local vec3 = require('lib/cpml').vec3
+local vec3 = require('cpml').vec3
 
 function menu:init()
   self.room = lovr.graphics.newModel('art/room.obj')

+ 2 - 2
app/mobile.lua

@@ -2,8 +2,8 @@ local controllers = require 'app/controllers'
 local cry = require 'app/cry'
 local sleep = require 'app/sleep'
 local play = require 'app/play'
-local vec3 = require('lib/cpml').vec3
-local mat4 = require('lib/cpml').mat4
+local vec3 = require('cpml').vec3
+local mat4 = require('cpml').mat4
 
 local mobile = {}
 

+ 3 - 2
app/play.lua

@@ -1,7 +1,7 @@
 local sleep = {}
 local controllers = require('app/controllers')
 local rattle = require('app/rattle')
-local vec3 = require('lib/cpml').vec3
+local vec3 = require('cpml').vec3
 local drawBlock = require('app/block')
 
 sleep.won = false
@@ -16,7 +16,8 @@ function sleep:init()
     'art/skyboxes/bluecloud_lf.jpg'
   )
 
-  self.floor = g.newMesh(lovr.headset.getBoundsGeometry())
+  local w, d = lovr.headset.getBoundsDimensions()
+  self.floor = g.newMesh({{ -w, 0, -d }, { -w, 0, d }, { w, 0, -d }, { w, 0, d }}, 'strip')
 
   self.blocks = {}
   for i = 1, 4 do

+ 1 - 1
app/rattle.lua

@@ -1,6 +1,6 @@
 local rattle = {}
 local controllers = require 'app/controllers'
-local vec3 = require('lib/cpml').vec3
+local vec3 = require('cpml').vec3
 
 rattle.model = lovr.graphics.newModel('art/rattle.obj')
 rattle.model:setMaterial(lovr.graphics.newMaterial('art/rattle_DIFF.png'))

+ 9 - 8
app/sleep.lua

@@ -1,22 +1,23 @@
 local sleep = {}
 local controllers = require('app/controllers')
 local rattle = require('app/rattle')
-local vec3 = require('lib/cpml').vec3
+local vec3 = require('cpml').vec3
 local drawBlock = require('app/block')
 
 sleep.won = false
 
 function sleep:init()
   self.skybox = g.newTexture(
-    'art/skyboxes/stormydays_ft.tga',
-    'art/skyboxes/stormydays_bk.tga',
-    'art/skyboxes/stormydays_up.tga',
-    'art/skyboxes/stormydays_dn.tga',
-    'art/skyboxes/stormydays_rt.tga',
-    'art/skyboxes/stormydays_lf.tga'
+    'art/skyboxes/gg_ft.jpg',
+    'art/skyboxes/gg_bk.jpg',
+    'art/skyboxes/gg_up.jpg',
+    'art/skyboxes/gg_dn.jpg',
+    'art/skyboxes/gg_rt.jpg',
+    'art/skyboxes/gg_lf.jpg'
   )
 
-  self.floor = g.newMesh(lovr.headset.getBoundsGeometry())
+  local w, d = lovr.headset.getBoundsDimensions()
+  self.floor = g.newMesh({{ -w, 0, -d }, { -w, 0, d }, { w, 0, -d }, { w, 0, d }}, 'strip')
 
   self.block = {}
   self.block.maxY = 6

BIN
art/skyboxes/gg_dn.jpg


BIN
art/skyboxes/gg_ft.jpg


BIN
art/skyboxes/gg_lf.jpg


+ 13 - 0
art/skyboxes/gg_readme.txt

@@ -0,0 +1,13 @@
+Author
+======
+
+This is the work of Emil Persson, aka Humus.
+http://www.humus.name
+
+
+
+License
+=======
+
+This work is licensed under a Creative Commons Attribution 3.0 Unported License.
+http://creativecommons.org/licenses/by/3.0/

BIN
art/skyboxes/gg_rt.jpg


BIN
art/skyboxes/gg_up.jpg


BIN
art/skyboxes/posz.jpg


BIN
art/skyboxes/stormydays_bk.tga


BIN
art/skyboxes/stormydays_dn.tga


BIN
art/skyboxes/stormydays_ft.tga


BIN
art/skyboxes/stormydays_lf.tga


BIN
art/skyboxes/stormydays_rt.tga


BIN
art/skyboxes/stormydays_up.tga


+ 0 - 15
lib/cpml/.atom-build.json

@@ -1,15 +0,0 @@
-{
-	"cmd": "busted",
-	"args": [ "spec" ],
-	"sh": false,
-	"targets": {
-		"LDoc": {
-			"cmd": "ldoc",
-			"args": ["-c doc/config.ld", "-o index", "."]
-		},
-		"View Docs": {
-			"cmd": "xdg-open",
-			"args": ["doc/doc/index.html"]
-		}
-	}
-}

+ 0 - 0
lib/cpml/.coveralls.yml → lua_modules/cpml/.coveralls.yml


+ 0 - 0
lib/cpml/.editorconfig → lua_modules/cpml/.editorconfig


+ 0 - 0
lib/cpml/.gitignore → lua_modules/cpml/.gitignore


+ 0 - 0
lib/cpml/.travis.yml → lua_modules/cpml/.travis.yml


+ 2 - 0
lib/cpml/LICENSE.md → lua_modules/cpml/LICENSE.md

@@ -10,6 +10,8 @@ Portions of mat4.lua are from LuaMatrix, (c) 2010 Michael Lutz. MIT.
 
 Code in simplex.lua is (c) 2011 Stefan Gustavson. MIT.
 
+Code in bound2.lua and bound3.lua are (c) 2018 Andi McClure. MIT.
+
 Code in quat.lua is from Andrew Stacey and covered under the CC0 license.
 
 Code in octree.lua is derived from UnityOctree. (c) 2014 Nition. BSD-2-Clause.

+ 0 - 0
lib/cpml/README.md → lua_modules/cpml/README.md


+ 31 - 0
lua_modules/cpml/cpml-scm-1.rockspec

@@ -0,0 +1,31 @@
+package = "cpml"
+version = "scm-1"
+source = {
+   url = "git://github.com/excessive/cpml.git"
+}
+description = {
+   summary = "Cirno's Perfect Math Library",
+   detailed = "Various useful bits of game math. 3D line intersections, ray casting, vectors, matrices, quaternions, etc.",
+   homepage = "http://github.com/excessive/cpml.git",
+   license = "MIT"
+}
+dependencies = {
+   "lua ~> 5.1"
+}
+build = {
+   type = "builtin",
+   modules = {
+      ["cpml"] = "init.lua",
+      ["cpml.modules.color"] = "modules/color.lua",
+      ["cpml.modules.constants"] = "modules/constants.lua",
+      ["cpml.modules.intersect"] = "modules/intersect.lua",
+      ["cpml.modules.mat4"] = "modules/mat4.lua",
+      ["cpml.modules.mesh"] = "modules/mesh.lua",
+      ["cpml.modules.octree"] = "modules/octree.lua",
+      ["cpml.modules.quat"] = "modules/quat.lua",
+      ["cpml.modules.simplex"] = "modules/simplex.lua",
+      ["cpml.modules.utils"] = "modules/utils.lua",
+      ["cpml.modules.vec2"] = "modules/vec2.lua",
+      ["cpml.modules.vec3"] = "modules/vec3.lua",
+   }
+}

+ 9 - 0
lua_modules/cpml/doc/config.ld

@@ -0,0 +1,9 @@
+project="CPML"
+title="CPML documentation"
+description="A math library with (hopefully) everything you need for 2D/3D games"
+format="markdown"
+backtick_references=false
+file = {
+   "../init.lua",
+   "../modules"
+}

+ 1 - 1
lib/cpml/init.lua → lua_modules/cpml/init.lua

@@ -37,7 +37,7 @@ local modules = (...) and (...):gsub('%.init$', '') .. ".modules." or ""
 local cpml = {
 	_LICENSE = "CPML is distributed under the terms of the MIT license. See LICENSE.md.",
 	_URL = "https://github.com/excessive/cpml",
-	_VERSION = "0.9.0",
+	_VERSION = "1.2.9",
 	_DESCRIPTION = "Cirno's Perfect Math Library: Just about everything you need for 3D games. Hopefully."
 }
 

+ 166 - 0
lua_modules/cpml/modules/bound2.lua

@@ -0,0 +1,166 @@
+--- A 2 component bounding box.
+-- @module bound2
+
+local modules = (...):gsub('%.[^%.]+$', '') .. "."
+local vec2    = require(modules .. "vec2")
+
+local bound2    = {}
+local bound2_mt = {}
+
+-- Private constructor.
+local function new(min, max)
+	return setmetatable({
+		min=min, -- min: vec2, minimum value for each component 
+		max=max, -- max: vec2, maximum value for each component 
+	}, bound2_mt)
+end
+
+-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
+local status, ffi
+if type(jit) == "table" and jit.status() then
+	status, ffi = pcall(require, "ffi")
+	if status then
+		ffi.cdef "typedef struct { cpml_vec2 min, max; } cpml_bound2;"
+		new = ffi.typeof("cpml_bound2")
+	end
+end
+
+bound2.zero = new(vec2.zero, vec2.zero)
+
+--- The public constructor.
+-- @param min Can be of two types: </br>
+-- vec2 min, minimum value for each component
+-- nil Create bound at single point 0,0
+-- @tparam vec2 max, maximum value for each component
+-- @treturn bound2 out
+function bound2.new(min, max)
+	if min and max then
+		return new(min:clone(), max:clone())
+	elseif min or max then
+		error("Unexpected nil argument to bound2.new")
+	else
+		return new(vec2.zero, vec2.zero)
+	end
+end
+
+--- Clone a bound.
+-- @tparam bound2 a bound to be cloned
+-- @treturn bound2 out
+function bound2.clone(a)
+	return new(a.min, a.max)
+end
+
+--- Construct a bound covering one or two points 
+-- @tparam vec2 a Any vector
+-- @tparam vec2 b Any second vector (optional)
+-- @treturn vec2 Minimum bound containing the given points
+function bound2.at(a, b) -- "bounded by". b may be nil
+	if b then
+		return bound2.new(a,b):check()
+	else
+		return bound2.zero:with_center(a)
+	end
+end
+
+--- Get size of bounding box as a vector 
+-- @tparam bound2 a bound
+-- @treturn vec2 Vector spanning min to max points
+function bound2.size(a)
+	return a.max - a.min
+end
+
+--- Resize bounding box from minimum corner
+-- @tparam bound2 a bound
+-- @tparam vec2 new size
+-- @treturn bound2 resized bound
+function bound2.with_size(a, size)
+	return bound2.new(a.min, a.min + size)
+end
+
+--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
+-- @tparam bound2 a bound
+-- @treturn vec2 Vector spanning center to max point
+function bound2.radius(a)
+	return a:size()/2
+end
+
+--- Get center of bounding box
+-- @tparam bound2 a bound
+-- @treturn bound2 Point in center of bound
+function bound2.center(a)
+	return (a.min + a.max)/2
+end
+
+--- Move bounding box to new center
+-- @tparam bound2 a bound
+-- @tparam vec2 new center
+-- @treturn bound2 Bound with same size as input but different center
+function bound2.with_center(a, center)
+	return bound2.offset(a, center - a:center())
+end
+
+--- Resize bounding box from center
+-- @tparam bound2 a bound
+-- @tparam vec2 new size
+-- @treturn bound2 resized bound
+function bound2.with_size_centered(a, size)
+	local center = a:center()
+	local rad = size/2
+	return bound2.new(center - rad, center + rad)
+end
+
+--- Convert possibly-invalid bounding box to valid one
+-- @tparam bound2 a bound
+-- @treturn bound2 bound with all components corrected for min-max property
+function bound2.check(a)
+	if a.min.x > a.max.x or a.min.y > a.max.y then
+		return bound2.new(vec2.component_min(a.min, a.max), vec2.component_max(a.min, a.max))
+	end
+	return a
+end
+
+--- Shrink bounding box with fixed margin
+-- @tparam bound2 a bound
+-- @tparam vec2 a margin
+-- @treturn bound2 bound with margin subtracted from all edges. May not be valid, consider calling check()
+function bound2.inset(a, v)
+	return bound2.new(a.min + v, a.max - v)
+end
+
+--- Expand bounding box with fixed margin
+-- @tparam bound2 a bound
+-- @tparam vec2 a margin
+-- @treturn bound2 bound with margin added to all edges. May not be valid, consider calling check()
+function bound2.outset(a, v)
+	return bound2.new(a.min - v, a.max + v)
+end
+
+--- Offset bounding box
+-- @tparam bound2 a bound
+-- @tparam vec2 offset
+-- @treturn bound2 bound with same size, but position moved by offset
+function bound2.offset(a, v)
+	return bound2.new(a.min + v, a.max + v)
+end
+
+--- Test if point in bound
+-- @tparam bound2 a bound
+-- @tparam vec2 point to test
+-- @treturn boolean true if point in bounding box
+function bound2.contains(a, v)
+	return a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z
+	   and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z
+end
+
+bound2_mt.__index    = bound2
+bound2_mt.__tostring = bound2.to_string
+
+function bound2_mt.__call(_, a, b)
+	return bound2.new(a, b)
+end
+
+if status then
+	ffi.metatype(new, bound2_mt)
+end
+
+return setmetatable({}, bound2_mt)

+ 166 - 0
lua_modules/cpml/modules/bound3.lua

@@ -0,0 +1,166 @@
+--- A 3-component axis-aligned bounding box.
+-- @module bound3
+
+local modules = (...):gsub('%.[^%.]+$', '') .. "."
+local vec3    = require(modules .. "vec3")
+
+local bound3    = {}
+local bound3_mt = {}
+
+-- Private constructor.
+local function new(min, max)
+	return setmetatable({
+		min=min, -- min: vec3, minimum value for each component 
+		max=max  -- max: vec3, maximum value for each component
+	}, bound3_mt)
+end
+
+-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
+local status, ffi
+if type(jit) == "table" and jit.status() then
+	status, ffi = pcall(require, "ffi")
+	if status then
+		ffi.cdef "typedef struct { cpml_vec3 min, max; } cpml_bound3;"
+		new = ffi.typeof("cpml_bound3")
+	end
+end
+
+bound3.zero = new(vec3.zero, vec3.zero)
+
+--- The public constructor.
+-- @param min Can be of two types: </br>
+-- vec3 min, minimum value for each component
+-- nil Create bound at single point 0,0,0
+-- @tparam vec3 max, maximum value for each component
+-- @treturn bound3 out
+function bound3.new(min, max)
+	if min and max then
+		return new(min:clone(), max:clone())
+	elseif min or max then
+		error("Unexpected nil argument to bound3.new")
+	else
+		return new(vec3.zero, vec3.zero)
+	end
+end
+
+--- Clone a bound.
+-- @tparam bound3 a bound to be cloned
+-- @treturn bound3 out
+function bound3.clone(a)
+	return new(a.min, a.max)
+end
+
+--- Construct a bound covering one or two points 
+-- @tparam vec3 a Any vector
+-- @tparam vec3 b Any second vector (optional)
+-- @treturn vec3 Minimum bound containing the given points
+function bound3.at(a, b) -- "bounded by". b may be nil
+	if b then
+		return bound3.new(a,b):check()
+	else
+		return bound3.zero:with_center(a)
+	end
+end
+
+--- Get size of bounding box as a vector 
+-- @tparam bound3 a bound
+-- @treturn vec3 Vector spanning min to max points
+function bound3.size(a)
+	return a.max - a.min
+end
+
+--- Resize bounding box from minimum corner
+-- @tparam bound3 a bound
+-- @tparam vec3 new size
+-- @treturn bound3 resized bound
+function bound3.with_size(a, size)
+	return bound3.new(a.min, a.min + size)
+end
+
+--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
+-- @tparam bound3 a bound
+-- @treturn vec3 Vector spanning center to max point
+function bound3.radius(a)
+	return a:size()/2
+end
+
+--- Get center of bounding box
+-- @tparam bound3 a bound
+-- @treturn bound3 Point in center of bound
+function bound3.center(a)
+	return (a.min + a.max)/2
+end
+
+--- Move bounding box to new center
+-- @tparam bound3 a bound
+-- @tparam vec3 new center
+-- @treturn bound3 Bound with same size as input but different center
+function bound3.with_center(a, center)
+	return bound3.offset(a, center - a:center())
+end
+
+--- Resize bounding box from center
+-- @tparam bound3 a bound
+-- @tparam vec3 new size
+-- @treturn bound3 resized bound
+function bound3.with_size_centered(a, size)
+	local center = a:center()
+	local rad = size/2
+	return bound3.new(center - rad, center + rad)
+end
+
+--- Convert possibly-invalid bounding box to valid one
+-- @tparam bound3 a bound
+-- @treturn bound3 bound with all components corrected for min-max property
+function bound3.check(a)
+	if a.min.x > a.max.x or a.min.y > a.max.y or a.min.z > a.max.z then
+		return bound3.new(vec3.component_min(a.min, a.max), vec3.component_max(a.min, a.max))
+	end
+	return a
+end
+
+--- Shrink bounding box with fixed margin
+-- @tparam bound3 a bound
+-- @tparam vec3 a margin
+-- @treturn bound3 bound with margin subtracted from all edges. May not be valid, consider calling check()
+function bound3.inset(a, v)
+	return bound3.new(a.min + v, a.max - v)
+end
+
+--- Expand bounding box with fixed margin
+-- @tparam bound3 a bound
+-- @tparam vec3 a margin
+-- @treturn bound3 bound with margin added to all edges. May not be valid, consider calling check()
+function bound3.outset(a, v)
+	return bound3.new(a.min - v, a.max + v)
+end
+
+--- Offset bounding box
+-- @tparam bound3 a bound
+-- @tparam vec3 offset
+-- @treturn bound3 bound with same size, but position moved by offset
+function bound3.offset(a, v)
+	return bound3.new(a.min + v, a.max + v)
+end
+
+--- Test if point in bound
+-- @tparam bound3 a bound
+-- @tparam vec3 point to test
+-- @treturn boolean true if point in bounding box
+function bound3.contains(a, v)
+	return a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z
+	   and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z
+end
+
+bound3_mt.__index    = bound3
+bound3_mt.__tostring = bound3.to_string
+
+function bound3_mt.__call(_, a, b)
+	return bound3.new(a, b)
+end
+
+if status then
+	ffi.metatype(new, bound3_mt)
+end
+
+return setmetatable({}, bound3_mt)

+ 88 - 22
lib/cpml/modules/color.lua → lua_modules/cpml/modules/color.lua

@@ -1,7 +1,7 @@
 --- Color utilities
 -- @module color
 
-local modules  = (...):gsub('%/[^%/]+$', '') .. "."
+local modules  = (...):gsub('%.[^%.]+$', '') .. "."
 local utils    = require(modules .. "utils")
 local color    = {}
 local color_mt = {}
@@ -19,7 +19,6 @@ end
 local function hsv_to_color(hsv)
 	local i
 	local f, q, p, t
-	local r, g, b
 	local h, s, v
 	local a = hsv[4] or 255
 	s = hsv[2]
@@ -53,10 +52,7 @@ local function color_to_hsv(c)
 	local g = c[2]
 	local b = c[3]
 	local a = c[4] or 255
-
-	local h = 0
-	local s = 0
-	local v = 0
+	local h, s, v
 
 	local min = math.min(r, g, b)
 	local max = math.max(r, g, b)
@@ -95,6 +91,15 @@ local function color_to_hsv(c)
 	return { h, s, v, a }
 end
 
+--- The public constructor.
+-- @param x Can be of three types: </br>
+-- number red component 0-255
+-- table {r, g, b, a}
+-- nil for {0,0,0,0}
+-- @tparam number g Green component 0-255
+-- @tparam number b Blue component 0-255
+-- @tparam number a Alpha component 0-255
+-- @treturn color out
 function color.new(r, g, b, a)
 	-- number, number, number, number
 	if r and g and b and a then
@@ -105,32 +110,60 @@ function color.new(r, g, b, a)
 
 		return new(r, g, b, a)
 
-	-- {x, y, z, w}
-	elseif type(x) == "table" then
-		local r, g, b, a = r[1], r[2], r[3], r[4]
-		assert(type(r) == "number", "new: Wrong argument type for r (<number> expected)")
-		assert(type(g) == "number", "new: Wrong argument type for g (<number> expected)")
-		assert(type(b) == "number", "new: Wrong argument type for b (<number> expected)")
-		assert(type(a) == "number", "new: Wrong argument type for a (<number> expected)")
+	-- {r, g, b, a}
+	elseif type(r) == "table" then
+		local rr, gg, bb, aa = r[1], r[2], r[3], r[4]
+		assert(type(rr) == "number", "new: Wrong argument type for r (<number> expected)")
+		assert(type(gg) == "number", "new: Wrong argument type for g (<number> expected)")
+		assert(type(bb) == "number", "new: Wrong argument type for b (<number> expected)")
+		assert(type(aa) == "number", "new: Wrong argument type for a (<number> expected)")
 
-		return new(r, g, b, a)
+		return new(rr, gg, bb, aa)
 	end
 
-	new(0, 0, 0, 0)
+	return new(0, 0, 0, 0)
 end
 
+--- Convert hue,saturation,value table to color object.
+-- @tparam table hsva {hue 0-359, saturation 0-1, value 0-1, alpha 0-255}
+-- @treturn color out
+color.hsv_to_color_table = hsv_to_color
+
+--- Convert color to hue,saturation,value table
+-- @tparam color in
+-- @treturn table hsva {hue 0-359, saturation 0-1, value 0-1, alpha 0-255}
+color.color_to_hsv_table = color_to_hsv
+
+--- Convert hue,saturation,value to color object.
+-- @tparam number h hue 0-359
+-- @tparam number s saturation 0-1
+-- @tparam number v value 0-1
+-- @treturn color out
 function color.from_hsv(h, s, v)
-	return hsv_to_color { h, s, v, 255 }
+	return hsv_to_color { h, s, v }
 end
 
+--- Convert hue,saturation,value to color object.
+-- @tparam number h hue 0-359
+-- @tparam number s saturation 0-1
+-- @tparam number v value 0-1
+-- @tparam number a alpha 0-255
+-- @treturn color out
 function color.from_hsva(h, s, v, a)
 	return hsv_to_color { h, s, v, a }
 end
 
+--- Invert a color.
+-- @tparam color to invert
+-- @treturn color out
 function color.invert(c)
 	return new(255 - c[1], 255 - c[2], 255 - c[3], c[4])
 end
 
+--- Lighten a color by a component-wise fixed amount (alpha unchanged)
+-- @tparam color to lighten
+-- @tparam number amount to increase each component by, 0-255 scale
+-- @treturn color out
 function color.lighten(c, v)
 	return new(
 		utils.clamp(c[1] + v * 255, 0, 255),
@@ -144,6 +177,10 @@ function color.lerp(a, b, s)
 	return a + s * (b - a)
 end
 
+--- Darken a color by a component-wise fixed amount (alpha unchanged)
+-- @tparam color to darken
+-- @tparam number amount to decrease each component by, 0-255 scale
+-- @treturn color out
 function color.darken(c, v)
 	return new(
 		utils.clamp(c[1] - v * 255, 0, 255),
@@ -153,6 +190,10 @@ function color.darken(c, v)
 	)
 end
 
+--- Multiply a color's components by a value (alpha unchanged)
+-- @tparam color to multiply
+-- @tparam number to multiply each component by
+-- @treturn color out
 function color.multiply(c, v)
 	local t = color.new()
 	for i = 1, 3 do
@@ -164,6 +205,9 @@ function color.multiply(c, v)
 end
 
 -- directly set alpha channel
+-- @tparam color to alter
+-- @tparam number new alpha 0-255
+-- @treturn color out
 function color.alpha(c, v)
 	local t = color.new()
 	for i = 1, 3 do
@@ -174,6 +218,10 @@ function color.alpha(c, v)
 	return t
 end
 
+--- Multiply a color's alpha by a value
+-- @tparam color to multiply
+-- @tparam number to multiply alpha by
+-- @treturn color out
 function color.opacity(c, v)
 	local t = color.new()
 	for i = 1, 3 do
@@ -184,20 +232,32 @@ function color.opacity(c, v)
 	return t
 end
 
-function color.hue(color, hue)
-	local c = color_to_hsv(color)
+--- Set a color's hue (saturation, value, alpha unchanged)
+-- @tparam color to alter
+-- @tparam hue to set 0-359
+-- @treturn color out
+function color.hue(col, hue)
+	local c = color_to_hsv(col)
 	c[1] = (hue + 360) % 360
 	return hsv_to_color(c)
 end
 
-function color.saturation(color, percent)
-	local c = color_to_hsv(color)
+--- Set a color's saturation (hue, value, alpha unchanged)
+-- @tparam color to alter
+-- @tparam hue to set 0-359
+-- @treturn color out
+function color.saturation(col, percent)
+	local c = color_to_hsv(col)
 	c[2] = utils.clamp(percent, 0, 1)
 	return hsv_to_color(c)
 end
 
-function color.value(color, percent)
-	local c = color_to_hsv(color)
+--- Set a color's value (saturation, hue, alpha unchanged)
+-- @tparam color to alter
+-- @tparam hue to set 0-359
+-- @treturn color out
+function color.value(col, percent)
+	local c = color_to_hsv(col)
 	c[3] = utils.clamp(percent, 0, 1)
 	return hsv_to_color(c)
 end
@@ -256,6 +316,9 @@ function color.linear_to_gamma(r, g, b, a)
 	end
 end
 
+--- Check if color is valid
+-- @tparam color to test
+-- @treturn boolean is color
 function color.is_color(a)
 	if type(a) ~= "table" then
 		return false
@@ -270,6 +333,9 @@ function color.is_color(a)
 	return true
 end
 
+--- Return a formatted string.
+-- @tparam color a color to be turned into a string
+-- @treturn string formatted
 function color.to_string(a)
 	return string.format("[ %3.0f, %3.0f, %3.0f, %3.0f ]", a[1], a[2], a[3], a[4])
 end

+ 0 - 0
lib/cpml/modules/constants.lua → lua_modules/cpml/modules/constants.lua


+ 124 - 64
lib/cpml/modules/intersect.lua → lua_modules/cpml/modules/intersect.lua

@@ -1,10 +1,11 @@
 --- Various geometric intersections
 -- @module intersect
 
-local modules     = (...):gsub('%/[^%/]+$', '') .. "/"
+local modules     = (...):gsub('%.[^%.]+$', '') .. "."
 local constants   = require(modules .. "constants")
-local vec3        = require(modules .. "vec3")
 local mat4        = require(modules .. "mat4")
+local vec3        = require(modules .. "vec3")
+local utils       = require(modules .. "utils")
 local DBL_EPSILON = constants.DBL_EPSILON
 local sqrt        = math.sqrt
 local abs         = math.abs
@@ -12,12 +13,6 @@ local min         = math.min
 local max         = math.max
 local intersect   = {}
 
--- Some temp variables
-local d, h, s, q, e1, e2 = vec3(), vec3(), vec3(), vec3(), vec3(), vec3()
-local dir, dirfrac       = vec3(), vec3()
-local p13, p43, p21      = vec3(), vec3(), vec3()
-local axes               = { "x", "y", "z" }
-
 -- https://blogs.msdn.microsoft.com/rezanour/2011/08/07/barycentric-coordinates-and-point-in-triangle-tests/
 -- point       is a vec3
 -- triangle[1] is a vec3
@@ -28,15 +23,15 @@ function intersect.point_triangle(point, triangle)
 	local v = triangle[3] - triangle[1]
 	local w = point       - triangle[1]
 
-	local vw = vec3():cross(v, w)
-	local vu = vec3():cross(v, u)
+	local vw = v:cross(w)
+	local vu = v:cross(u)
 
 	if vw:dot(vu) < 0 then
 		return false
 	end
 
-	local uw = vec3():cross(u, w)
-	local uv = vec3():cross(u, v)
+	local uw = u:cross(w)
+	local uv = u:cross(v)
 
 	if uw:dot(uv) < 0 then
 		return false
@@ -103,11 +98,10 @@ end
 -- triangle[2]   is a vec3
 -- triangle[3]   is a vec3
 function intersect.ray_triangle(ray, triangle)
-	e1:sub(triangle[2], triangle[1])
-	e2:sub(triangle[3], triangle[1])
-	h:cross(ray.direction, e2)
-
-	local a = h:dot(e1)
+	local e1 = triangle[2] - triangle[1]
+	local e2 = triangle[3] - triangle[1]
+	local h  = ray.direction:cross(e2)
+	local a  = h:dot(e1)
 
 	-- if a is too close to 0, ray does not intersect triangle
 	if abs(a) <= DBL_EPSILON then
@@ -115,7 +109,7 @@ function intersect.ray_triangle(ray, triangle)
 	end
 
 	local f = 1 / a
-	s:sub(ray.position, triangle[1])
+	local s = ray.position - triangle[1]
 	local u = s:dot(h) * f
 
 	-- ray does not intersect triangle
@@ -123,7 +117,7 @@ function intersect.ray_triangle(ray, triangle)
 		return false
 	end
 
-	q:cross(s, e1)
+	local q = s:cross(e1)
 	local v = ray.direction:dot(q) * f
 
 	-- ray does not intersect triangle
@@ -135,13 +129,9 @@ function intersect.ray_triangle(ray, triangle)
 	-- the intersection point is on the line
 	local t = q:dot(e2) * f
 
-	-- return position of intersection
+	-- return position of intersection and distance from ray origin
 	if t >= DBL_EPSILON then
-		local out = vec3()
-		out:scale(ray.direction, t)
-		out:add(ray.position, out)
-
-		return out
+		return ray.position + ray.direction * t, t
 	end
 
 	-- ray does not intersect triangle
@@ -171,17 +161,12 @@ function intersect.ray_sphere(ray, sphere)
 		return false
 	end
 
-	local t = -b - sqrt(discr)
-
 	-- Clamp t to 0
+	local t = -b - sqrt(discr)
 	t = t < 0 and 0 or t
 
-	local out = vec3()
-	out:scale(ray.direction, t)
-	out:add(out, ray.position)
-
 	-- Return collision point and distance from ray origin
-	return out, t
+	return ray.position + ray.direction * t, t
 end
 
 -- http://gamedev.stackexchange.com/a/18459
@@ -190,10 +175,12 @@ end
 -- aabb.min      is a vec3
 -- aabb.max      is a vec3
 function intersect.ray_aabb(ray, aabb)
-	dir:normalize(ray.direction)
-	dirfrac.x = 1 / dir.x
-	dirfrac.y = 1 / dir.y
-	dirfrac.z = 1 / dir.z
+	local dir     = ray.direction:normalize()
+	local dirfrac = vec3(
+		1 / dir.x,
+		1 / dir.y,
+		1 / dir.z
+	)
 
 	local t1 = (aabb.min.x - ray.position.x) * dirfrac.x
 	local t2 = (aabb.max.x - ray.position.x) * dirfrac.x
@@ -215,12 +202,8 @@ function intersect.ray_aabb(ray, aabb)
 		return false
 	end
 
-	local out = vec3()
-	out:scale(ray.direction, tmin)
-	out:add(out, ray.position)
-
 	-- Return collision point and distance from ray origin
-	return out, tmin
+	return ray.position + ray.direction * tmin, tmin
 end
 
 -- http://stackoverflow.com/a/23976134/1190664
@@ -237,19 +220,29 @@ function intersect.ray_plane(ray, plane)
 	end
 
 	-- distance of direction
-	d:sub(plane.position, ray.position)
+	local d = plane.position - ray.position
 	local t = d:dot(plane.normal) / denom
 
 	if t < DBL_EPSILON then
 		return false
 	end
 
-	local out = vec3()
-	out:scale(ray.direction, t)
-	out:add(out, ray.position)
-
 	-- Return collision point and distance from ray origin
-	return out, t
+	return ray.position + ray.direction * t, t
+end
+
+function intersect.ray_capsule(ray, capsule)
+	local dist2, p1, p2 = intersect.closest_point_segment_segment(
+		ray.position,
+		ray.position + ray.direction * 1e10,
+		capsule.a,
+		capsule.b
+	)
+	if dist2 <= capsule.radius^2 then
+		return p1
+	end
+
+	return false
 end
 
 -- https://web.archive.org/web/20120414063459/http://local.wasp.uwa.edu.au/~pbourke//geometry/lineline3d/
@@ -260,9 +253,9 @@ end
 -- e    is a number
 function intersect.line_line(a, b, e)
 	-- new points
-	p13:sub(a[1], b[1])
-	p43:sub(b[2], b[1])
-	p21:sub(a[2], a[1])
+	local p13 = a[1] - b[1]
+	local p43 = b[2] - b[1]
+	local p21 = a[2] - a[1]
 
 	-- if lengths are negative or too close to 0, lines do not intersect
 	if p43:len2() < DBL_EPSILON or p21:len2() < DBL_EPSILON then
@@ -287,14 +280,8 @@ function intersect.line_line(a, b, e)
 	local mub   = (d1343 + d4321 * mua) / d4343
 
 	-- return positions of intersection on each line
-	local out1 = vec3()
-	out1:scale(p21, mua)
-	out1:add(out1, a[1])
-
-	local out2 = vec3()
-	out2:scale(p43, mub)
-	out2:add(out2, b[1])
-
+	local out1 = a[1] + p21 * mua
+	local out2 = b[1] + p43 * mub
 	local dist = out1:dist(out2)
 
 	-- if distance of the shortest segment between lines is less than threshold
@@ -422,15 +409,15 @@ function intersect.aabb_obb(aabb, obb)
 
 	if wy.x > hx.x and wy.y > hx.y and wy.z > hx.z then
 		if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
-			return vec3(mat4.mul_vec4({}, obb.rotation, {  0, -1, 0, 1 }))
+			return vec3(obb.rotation * {  0, -1, 0, 1 })
 		else
-			return vec3(mat4.mul_vec4({}, obb.rotation, { -1,  0, 0, 1 }))
+			return vec3(obb.rotation * { -1,  0, 0, 1 })
 		end
 	else
 		if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
-			return vec3(mat4.mul_vec4({}, obb.rotation, { 1, 0, 0, 1 }))
+			return vec3(obb.rotation * { 1, 0, 0, 1 })
 		else
-			return vec3(mat4.mul_vec4({}, obb.rotation, { 0, 1, 0, 1 }))
+			return vec3(obb.rotation * { 0, 1, 0, 1 })
 		end
 	end
 end
@@ -440,6 +427,7 @@ end
 -- aabb.max        is a vec3
 -- sphere.position is a vec3
 -- sphere.radius   is a number
+local axes = { "x", "y", "z" }
 function intersect.aabb_sphere(aabb, sphere)
 	local dist2 = sphere.radius ^ 2
 
@@ -555,7 +543,7 @@ function intersect.sphere_triangle(sphere, triangle)
 	local C  = triangle[3] - sphere.position
 
 	-- Compute normal of triangle plane
-	local V  = vec3():cross(B - A, C - A)
+	local V  = (B - A):cross(C - A)
 
 	-- Test if sphere lies outside triangle plane
 	local rr = sphere.radius * sphere.radius
@@ -637,7 +625,79 @@ function intersect.sphere_frustum(sphere, frustum)
 
 	-- dot + radius is the distance of the object from the near plane.
 	-- make sure that the near plane is the last test!
-	return dot + radius
+	return dot + sphere.radius
+end
+
+function intersect.capsule_capsule(c1, c2)
+	local dist2, p1, p2 = intersect.closest_point_segment_segment(c1.a, c1.b, c2.a, c2.b)
+	local radius = c1.radius + c2.radius
+
+	if dist2 <= radius * radius then
+		return p1, p2
+	end
+
+	return false
+end
+
+function intersect.closest_point_segment_segment(p1, p2, p3, p4)
+	local s  -- Distance of intersection along segment 1
+	local t  -- Distance of intersection along segment 2
+	local c1 -- Collision point on segment 1
+	local c2 -- Collision point on segment 2
+
+	local d1 = p2 - p1 -- Direction of segment 1
+	local d2 = p4 - p3 -- Direction of segment 2
+	local r  = p1 - p3
+	local a  = d1:dot(d1)
+	local e  = d2:dot(d2)
+	local f  = d2:dot(r)
+
+	-- Check if both segments degenerate into points
+	if a <= DBL_EPSILON and e <= DBL_EPSILON then
+		s  = 0
+		t  = 0
+		c1 = p1
+		c2 = p3
+		return (c1 - c2):dot(c1 - c2), s, t, c1, c2
+	end
+
+	-- Check if segment 1 degenerates into a point
+	if a <= DBL_EPSILON then
+		s = 0
+		t = utils.clamp(f / e, 0, 1)
+	else
+		local c = d1:dot(r)
+
+		-- Check is segment 2 degenerates into a point
+		if e <= DBL_EPSILON then
+			t = 0
+			s = utils.clamp(-c / a, 0, 1)
+		else
+			local b     = d1:dot(d2)
+			local denom = a * e - b * b
+
+			if abs(denom) > 0 then
+				s = utils.clamp((b * f - c * e) / denom, 0, 1)
+			else
+				s = 0
+			end
+
+			t = (b * s + f) / e
+
+			if t < 0 then
+				t = 0
+				s = utils.clamp(-c / a, 0, 1)
+			elseif t > 1 then
+				t = 1
+				s = utils.clamp((b - c) / a, 0, 1)
+			end
+		end
+	end
+
+	c1 = p1 + d1 * s
+	c2 = p3 + d2 * t
+
+	return (c1 - c2):dot(c1 - c2), c1, c2, s, t
 end
 
 return intersect

+ 66 - 43
lib/cpml/modules/mat4.lua → lua_modules/cpml/modules/mat4.lua

@@ -1,6 +1,6 @@
 --- double 4x4, 1-based, column major matrices
 -- @module mat4
-local modules   = (...):gsub('%/[^%/]+$', '') .. "/"
+local modules   = (...):gsub('%.[^%.]+$', '') .. "."
 local constants = require(modules .. "constants")
 local vec2      = require(modules .. "vec2")
 local vec3      = require(modules .. "vec3")
@@ -37,8 +37,7 @@ end
 -- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
 local status, ffi, the_type
 if type(jit) == "table" and jit.status() then
-    status, ffi = pcall(require, "ffi")
-    status = false
+   --  status, ffi = pcall(require, "ffi")
     if status then
         ffi.cdef "typedef struct { double _m[16]; } cpml_mat4;"
         new = ffi.typeof("cpml_mat4")
@@ -136,15 +135,9 @@ end
 -- @tparam vec3 up Up direction
 -- @treturn mat4 out
 function mat4.from_direction(direction, up)
-	local forward = vec3():normalize(direction)
-
-	local side = vec3()
-		:cross(forward, up)
-		:normalize(side)
-
-	local new_up = vec3()
-		:cross(side, forward)
-		:normalize(new_up)
+	local forward = vec3.normalize(direction)
+	local side = vec3.cross(forward, up):normalize()
+	local new_up = vec3.cross(side, forward):normalize()
 
 	local out = new()
 	out[1]    = side.x
@@ -328,8 +321,8 @@ end
 
 --- Multiply two matrices.
 -- @tparam mat4 out Matrix to store the result
--- @tparam mat4 a Left hand operant
--- @tparam mat4 b Right hand operant
+-- @tparam mat4 a Left hand operand
+-- @tparam mat4 b Right hand operand
 -- @treturn mat4 out
 function mat4.mul(out, a, b)
 	tm4[1]  = a[1]  * b[1] + a[2]  * b[5] + a[3]  * b[9]  + a[4]  * b[13]
@@ -358,8 +351,8 @@ end
 
 --- Multiply a matrix and a vec4.
 -- @tparam mat4 out Matrix to store the result
--- @tparam mat4 a Left hand operant
--- @tparam table b Right hand operant
+-- @tparam mat4 a Left hand operand
+-- @tparam table b Right hand operand
 -- @treturn mat4 out
 function mat4.mul_vec4(out, a, b)
 	tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9]  + b[4] * a[13]
@@ -498,6 +491,34 @@ function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
 	return out:mul(tmp, a)
 end
 
+--- Reflect a matrix across a plane.
+-- @tparam mat4 Matrix to store the result
+-- @tparam a Matrix to reflect
+-- @tparam vec3 position A point on the plane
+-- @tparam vec3 normal The (normalized!) normal vector of the plane
+function mat4.reflect(out, a, position, normal)
+	local nx, ny, nz = normal:unpack()
+	local d = -position:dot(normal)
+	tmp[1] = 1 - 2 * nx ^ 2
+	tmp[2] = 2 * nx * ny
+	tmp[3] = -2 * nx * nz
+	tmp[4] = 0
+	tmp[5] = -2 * nx * ny
+	tmp[6] = 1 - 2 * ny ^ 2
+	tmp[7] = -2 * ny * nz
+	tmp[8] = 0
+	tmp[9] = -2 * nx * nz
+	tmp[10] = -2 * ny * nz
+	tmp[11] = 1 - 2 * nz ^ 2
+	tmp[12] = 0
+	tmp[13] = -2 * nx * d
+	tmp[14] = -2 * ny * d
+	tmp[15] = -2 * nz * d
+	tmp[16] = 1
+
+	return out:mul(tmp, a)
+end
+
 --- Transform matrix to look at a point.
 -- @tparam mat4 out Matrix to store result
 -- @tparam mat4 a Matrix to transform
@@ -505,31 +526,28 @@ end
 -- @tparam vec3 center Location of object to view
 -- @tparam vec3 up Up direction
 -- @treturn mat4 out
-function mat4.look_at(out, a, eye, center, up)
-	forward:normalize(center - eye)
-	side:cross(forward, up):normalize(side)
-	new_up:cross(side, forward)
-
-	identity(tmp)
-	tmp[1]  =  side.x
-	tmp[5]  =  side.y
-	tmp[9]  =  side.z
-	tmp[2]  =  new_up.x
-	tmp[6]  =  new_up.y
-	tmp[10] =  new_up.z
-	tmp[3]  = -forward.x
-	tmp[7]  = -forward.y
-	tmp[11] = -forward.z
-	tmp[13] = -side:dot(eye)
-	tmp[14] = -new_up:dot(eye)
-	tmp[15] =  forward:dot(eye)
-	tmp[16] = 1
-
-	for i = 1, 16 do
-		out[i] = tmp[i]
-	end
-
-	return out
+function mat4.look_at(out, a, eye, look_at, up)
+	local z_axis = (eye - look_at):normalize()
+	local x_axis = up:cross(z_axis):normalize()
+	local y_axis = z_axis:cross(x_axis)
+	out[1] = x_axis.x
+	out[2] = y_axis.x
+	out[3] = z_axis.x
+	out[4] = 0
+	out[5] = x_axis.y
+	out[6] = y_axis.y
+	out[7] = z_axis.y
+	out[8] = 0
+	out[9] = x_axis.z
+	out[10] = y_axis.z
+	out[11] = z_axis.z
+	out[12] = 0
+	out[13] = 0
+	out[14] = 0
+	out[15] = 0
+	out[16] = 1
+
+  return out
 end
 
 --- Transpose a matrix.
@@ -819,8 +837,13 @@ function mat4_mt.__eq(a, b)
 end
 
 function mat4_mt.__mul(a, b)
-	assert(mat4.is_mat4(a), "__mul: Wrong argument type for left hand operant. (<cpml.mat4> expected)")
-	assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operant. (<cpml.mat4> or table #4 expected)")
+	assert(mat4.is_mat4(a), "__mul: Wrong argument type for left hand operand. (<cpml.mat4> expected)")
+
+	if vec3.is_vec3(b) then
+		return vec3(mat4.mul_vec4({}, a, { b.x, b.y, b.z, 1 }))
+	end
+
+	assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")
 
 	if mat4.is_mat4(b) then
 		return new():mul(a, b)

+ 6 - 9
lib/cpml/modules/mesh.lua → lua_modules/cpml/modules/mesh.lua

@@ -1,7 +1,7 @@
 --- Mesh utilities
 -- @module mesh
 
-local modules = (...):gsub('%/[^%/]+$', '') .. "/"
+local modules = (...):gsub('%.[^%.]+$', '') .. "."
 local vec3    = require(modules .. "vec3")
 local mesh    = {}
 
@@ -9,21 +9,18 @@ local mesh    = {}
 function mesh.average(vertices)
 	local out = vec3()
 	for _, v in ipairs(vertices) do
-		out:add(out, v)
+		out = out + v
 	end
-	return out:div(out, #vertices)
+	return out / #vertices
 end
 
 -- triangle[1] is a vec3
 -- triangle[2] is a vec3
 -- triangle[3] is a vec3
 function mesh.normal(triangle)
-	local ba  = vec3():sub(triangle[2], triangle[1])
-	local ca  = vec3():sub(triangle[3], triangle[1])
-	local out = vec3()
-	return out
-		:cross(ba, ca)
-		:normalize(out)
+	local ba = triangle[2] - triangle[1]
+	local ca = triangle[3] - triangle[1]
+	return ba:cross(ca):normalize()
 end
 
 -- triangle[1] is a vec3

+ 20 - 20
lib/cpml/modules/octree.lua → lua_modules/cpml/modules/octree.lua

@@ -5,7 +5,8 @@
 
 --- Octree
 -- @module octree
-local modules = (...):gsub('%/[^%/]+$', '') .. "/"
+
+local modules = (...):gsub('%.[^%.]+$', '') .. "."
 local intersect  = require(modules .. "intersect")
 local mat4       = require(modules .. "mat4")
 local utils      = require(modules .. "utils")
@@ -51,6 +52,17 @@ local function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)
 	return tree
 end
 
+--- Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
+-- @param xDir X direction of growth. 1 or -1
+-- @param yDir Y direction of growth. 1 or -1
+-- @param zDir Z direction of growth. 1 or -1
+-- @return Octant where the root node should be
+local function get_root_pos_index(xDir, yDir, zDir)
+	local result = xDir > 0 and 1 or 0
+	if yDir < 0 then return result + 4 end
+	if zDir > 0 then return result + 2 end
+end
+
 --- Add an object.
 -- @param obj Object to add
 -- @param objBounds 3D bounding box around the object
@@ -136,7 +148,7 @@ function Octree:grow(direction)
 	self.rootNode = Node(newLength, self.minSize, self.looseness, newCenter)
 
 	-- Create 7 new octree children to go with the old root as children of the new root
-	local rootPos  = self:get_root_pos_index(xDirection, yDirection, zDirection)
+	local rootPos  = get_root_pos_index(xDirection, yDirection, zDirection)
 	local children = {}
 
 	for i = 0, 7 do
@@ -156,19 +168,7 @@ end
 
 --- Shrink the octree if possible, else leave it the same.
 function Octree:shrink()
-	self.rootNode = self.rootNode:shrink_if_possible(initialSize)
-end
-
---- Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
--- @param xDir X direction of growth. 1 or -1
--- @param yDir Y direction of growth. 1 or -1
--- @param zDir Z direction of growth. 1 or -1
--- @return Octant where the root node should be
-function Octree:get_root_pos_index(xDir, yDir, zDir)
-	local result = xDir > 0 and 1 or 0
-
-	if yDir < 0 then return result + 4 end
-	if zDir > 0 then return result + 2 end
+	self.rootNode = self.rootNode:shrink_if_possible(self.initialSize)
 end
 
 --== Octree Node ==--
@@ -328,7 +328,7 @@ end
 -- @param results List results
 -- @return table Objects that intersect with the specified bounds
 function OctreeNode:get_colliding(checkBounds, results)
-	local results = results or {}
+	results = results or {}
 
 	-- Are the input bounds at least partially in this node?
 	if not intersect.aabb_aabb(self.bounds, checkBounds) then
@@ -581,8 +581,8 @@ function OctreeNode:draw_bounds(cube, depth)
 
 	love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
 	local m = mat4()
-		:translate(m, self.center)
-		:scale(m, vec3(self.adjLength, self.adjLength, self.adjLength))
+		:translate(self.center)
+		:scale(vec3(self.adjLength, self.adjLength, self.adjLength))
 
 	love.graphics.updateMatrix("transform", m)
 	love.graphics.setWireframe(true)
@@ -606,8 +606,8 @@ function OctreeNode:draw_objects(cube, filter)
 	for _, object in ipairs(self.objects) do
 		if filter and filter(object.data) or not filter then
 			local m = mat4()
-				:translate(m, object.bounds.center)
-				:scale(m, object.bounds.size)
+				:translate(object.bounds.center)
+				:scale(object.bounds.size)
 
 			love.graphics.updateMatrix("transform", m)
 			love.graphics.draw(cube)

+ 178 - 177
lib/cpml/modules/quat.lua → lua_modules/cpml/modules/quat.lua

@@ -1,15 +1,12 @@
 --- A quaternion and associated utilities.
 -- @module quat
 
-local modules       = (...):gsub('%/[^%/]+$', '') .. "/"
+local modules       = (...):gsub('%.[^%.]+$', '') .. "."
 local constants     = require(modules .. "constants")
 local vec3          = require(modules .. "vec3")
 local DOT_THRESHOLD = constants.DOT_THRESHOLD
 local DBL_EPSILON   = constants.DBL_EPSILON
-local abs           = math.abs
 local acos          = math.acos
-local asin          = math.asin
-local atan2         = math.atan2
 local cos           = math.cos
 local sin           = math.sin
 local min           = math.min
@@ -20,26 +17,28 @@ local quat_mt       = {}
 
 -- Private constructor.
 local function new(x, y, z, w)
-	local q = {}
-	q.x, q.y, q.z, q.w = x, y, z, w
-	return setmetatable(q, quat_mt)
+	return setmetatable({
+		x = x or 0,
+		y = y or 0,
+		z = z or 0,
+		w = w or 1
+	}, quat_mt)
 end
 
--- Statically allocate a temporary variable used in some of our functions.
-local tmp = new(0, 0, 0, 0)
-local uv, uuv = vec3(), vec3()
-
 -- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
 local status, ffi
 if type(jit) == "table" and jit.status() then
 	status, ffi = pcall(require, "ffi")
-  status = false
 	if status then
 		ffi.cdef "typedef struct { double x, y, z, w;} cpml_quat;"
 		new = ffi.typeof("cpml_quat")
 	end
 end
 
+-- Statically allocate a temporary variable used in some of our functions.
+local tmp = new()
+local qv, uv, uuv = vec3(), vec3(), vec3()
+
 --- Constants
 -- @table quat
 -- @field unit Unit quaternion
@@ -67,13 +66,13 @@ function quat.new(x, y, z, w)
 
 	-- {x, y, z, w} or {x=x, y=y, z=z, w=w}
 	elseif type(x) == "table" then
-		local x, y, z, w = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]
-		assert(type(x) == "number", "new: Wrong argument type for x (<number> expected)")
-		assert(type(y) == "number", "new: Wrong argument type for y (<number> expected)")
-		assert(type(z) == "number", "new: Wrong argument type for z (<number> expected)")
-		assert(type(w) == "number", "new: Wrong argument type for w (<number> expected)")
+		local xx, yy, zz, ww = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]
+		assert(type(xx) == "number", "new: Wrong argument type for x (<number> expected)")
+		assert(type(yy) == "number", "new: Wrong argument type for y (<number> expected)")
+		assert(type(zz) == "number", "new: Wrong argument type for z (<number> expected)")
+		assert(type(ww) == "number", "new: Wrong argument type for w (<number> expected)")
 
-		return new(x, y, z, w)
+		return new(xx, yy, zz, ww)
 	end
 
 	return new(0, 0, 0, 1)
@@ -81,22 +80,30 @@ end
 
 --- Create a quaternion from an angle/axis pair.
 -- @tparam number angle Angle (in radians)
--- @tparam vec3 axis
+-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis
+-- @param y axis -- y component of axis (optional, only if x component param used)
+-- @param z axis -- z component of axis (optional, only if x component param used)
 -- @treturn quat out
-function quat.from_angle_axis(angle, axis)
-	local len = axis:len()
-	local s   = sin(angle * 0.5)
-	local c   = cos(angle * 0.5)
-	return new(axis.x * s, axis.y * s, axis.z * s, c)
+function quat.from_angle_axis(angle, axis, a3, a4)
+	if axis and a3 and a4 then
+		local x, y, z = axis, a3, a4
+		local s = sin(angle * 0.5)
+		local c = cos(angle * 0.5)
+		return new(x * s, y * s, z * s, c)
+	else
+		return quat.from_angle_axis(angle, axis.x, axis.y, axis.z)
+	end
 end
 
 --- Create a quaternion from a normal/up vector pair.
 -- @tparam vec3 normal
--- @tparam vec3 up
+-- @tparam vec3 up (optional)
 -- @treturn quat out
 function quat.from_direction(normal, up)
-	local a = vec3():cross(up, normal)
-	local d = up:dot(normal)
+	local u = up or vec3.unit_z
+	local n = normal:normalize()
+	local a = u:cross(n)
+	local d = u:dot(n)
 	return new(a.x, a.y, a.z, d + 1)
 end
 
@@ -108,99 +115,93 @@ function quat.clone(a)
 end
 
 --- Add two quaternions.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam quat b Right hand operant
+-- @tparam quat a Left hand operand
+-- @tparam quat b Right hand operand
 -- @treturn quat out
-function quat.add(out, a, b)
-	out.x = a.x + b.x
-	out.y = a.y + b.y
-	out.z = a.z + b.z
-	out.w = a.w + b.w
-	return out
+function quat.add(a, b)
+	return new(
+		a.x + b.x,
+		a.y + b.y,
+		a.z + b.z,
+		a.w + b.w
+	)
 end
 
 --- Subtract a quaternion from another.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam quat b Right hand operant
+-- @tparam quat a Left hand operand
+-- @tparam quat b Right hand operand
 -- @treturn quat out
-function quat.sub(out, a, b)
-	out.x = a.x - b.x
-	out.y = a.y - b.y
-	out.z = a.z - b.z
-	out.w = a.w - b.w
-	return out
+function quat.sub(a, b)
+	return new(
+		a.x - b.x,
+		a.y - b.y,
+		a.z - b.z,
+		a.w - b.w
+	)
 end
 
 --- Multiply two quaternions.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam quat b Right hand operant
--- @treturn quat out
-function quat.mul(out, a, b)
-	out.x = a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y
-	out.y = a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z
-	out.z = a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x
-	out.w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
-	return out
+-- @tparam quat a Left hand operand
+-- @tparam quat b Right hand operand
+-- @treturn quat quaternion equivalent to "apply b, then a"
+function quat.mul(a, b)
+	return new(
+		a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y,
+		a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z,
+		a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x,
+		a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
+	)
 end
 
 --- Multiply a quaternion and a vec3.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam quat a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn quat out
-function quat.mul_vec3(out, a, b)
-	uv:cross(a, b)
-	uuv:cross(a, uv)
-
-	return out
-		:scale(uv, a.w)
-		:add(out, uuv)
-		:scale(out, 2)
-		:add(b, out)
-end
-
---- Multiply a quaternion by an exponent.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam number n Right hand operant
+function quat.mul_vec3(a, b)
+	qv.x = a.x
+	qv.y = a.y
+	qv.z = a.z
+	uv   = qv:cross(b)
+	uuv  = qv:cross(uv)
+	return b + ((uv * a.w) + uuv) * 2
+end
+
+--- Raise a normalized quaternion to a scalar power.
+-- @tparam quat a Left hand operand (should be a unit quaternion)
+-- @tparam number s Right hand operand
 -- @treturn quat out
-function quat.pow(out, a, n)
-	if n == 0 then
-		out.x = 0
-		out.y = 0
-		out.z = 0
-		out.w = 1
-	elseif n > 0 then
-		out.x = a.x^(n-1)
-		out.y = a.y^(n-1)
-		out.z = a.z^(n-1)
-		out.w = a.w^(n-1)
-		out:mul(a, out)
-	elseif n < 0 then
-		out:reciprocal(a)
-		out.x = out.x^(-n)
-		out.y = out.y^(-n)
-		out.z = out.z^(-n)
-		out.w = out.w^(-n)
+function quat.pow(a, s)
+	-- Do it as a slerp between identity and a (code borrowed from slerp)
+	if a.w < 0 then
+		a   = -a
+	end
+	local dot = a.w
+
+	if dot > DOT_THRESHOLD then
+		return a:scale(s)
 	end
 
-	return out
+	dot = min(max(dot, -1), 1)
+
+	local theta = acos(dot) * s
+	local c = new(a.x, a.y, a.z, 0):normalize() * sin(theta)
+	c.w = cos(theta)
+	return c
 end
 
 --- Normalize a quaternion.
--- @tparam quat out Quaternion to store the result
 -- @tparam quat a Quaternion to normalize
 -- @treturn quat out
-function quat.normalize(out, a)
-	return out:scale(a, 1 / a:len())
+function quat.normalize(a)
+	if a:is_zero() then
+		return new(0, 0, 0, 0)
+	end
+	return a:scale(1 / a:len())
 end
 
 --- Get the dot product of two quaternions.
--- @tparam quat a Left hand operant
--- @tparam quat b Right hand operant
+-- @tparam quat a Left hand operand
+-- @tparam quat b Right hand operand
 -- @treturn number dot
 function quat.dot(a, b)
 	return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
@@ -221,101 +222,94 @@ function quat.len2(a)
 end
 
 --- Multiply a quaternion by a scalar.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam number s Right hand operant
+-- @tparam quat a Left hand operand
+-- @tparam number s Right hand operand
 -- @treturn quat out
-function quat.scale(out, a, s)
-	out.x = a.x * s
-	out.y = a.y * s
-	out.z = a.z * s
-	out.w = a.w * s
-	return out
+function quat.scale(a, s)
+	return new(
+		a.x * s,
+		a.y * s,
+		a.z * s,
+		a.w * s
+	)
 end
 
 --- Alias of from_angle_axis.
 -- @tparam number angle Angle (in radians)
--- @tparam vec3 axis
+-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis
+-- @param y axis -- y component of axis (optional, only if x component param used)
+-- @param z axis -- z component of axis (optional, only if x component param used)
 -- @treturn quat out
-function quat.rotate(angle, axis)
-	return quat.from_angle_axis(angle, axis)
+function quat.rotate(angle, axis, a3, a4)
+	return quat.from_angle_axis(angle, axis, a3, a4)
 end
 
 --- Return the conjugate of a quaternion.
--- @tparam quat out Quaternion to store the result
 -- @tparam quat a Quaternion to conjugate
 -- @treturn quat out
-function quat.conjugate(out, a)
-	out.x = -a.x
-	out.y = -a.y
-	out.z = -a.z
-	out.w =  a.w
-	return out
+function quat.conjugate(a)
+	return new(-a.x, -a.y, -a.z, a.w)
 end
 
 --- Return the inverse of a quaternion.
--- @tparam quat out Quaternion to store the result
 -- @tparam quat a Quaternion to invert
 -- @treturn quat out
-function quat.inverse(out, a)
-	return out
-		:conjugate(a)
-		:normalize(out)
+function quat.inverse(a)
+	tmp.x = -a.x
+	tmp.y = -a.y
+	tmp.z = -a.z
+	tmp.w =  a.w
+	return tmp:normalize()
 end
 
 --- Return the reciprocal of a quaternion.
--- @tparam quat out Quaternion to store the result
 -- @tparam quat a Quaternion to reciprocate
 -- @treturn quat out
-function quat.reciprocal(out, a)
-	assert(not a:is_zero(), "Cannot reciprocate a zero quaternion")
-	return out
-		:conjugate(a)
-		:scale(out, 1 / a:len2())
+function quat.reciprocal(a)
+	if a:is_zero() then
+		error("Cannot reciprocate a zero quaternion")
+		return false
+	end
+
+	tmp.x = -a.x
+	tmp.y = -a.y
+	tmp.z = -a.z
+	tmp.w =  a.w
+
+	return tmp:scale(1 / a:len2())
 end
 
 --- Lerp between two quaternions.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam quat b Right hand operant
+-- @tparam quat a Left hand operand
+-- @tparam quat b Right hand operand
 -- @tparam number s Step value
 -- @treturn quat out
-function quat.lerp(out, a, b, s)
-	tmp:sub(b, a)
-	tmp:scale(tmp, s)
-	tmp:add(tmp, a)
-	return out:normalize(tmp)
+function quat.lerp(a, b, s)
+	return (a + (b - a) * s):normalize()
 end
 
 --- Slerp between two quaternions.
--- @tparam quat out Quaternion to store the result
--- @tparam quat a Left hand operant
--- @tparam quat b Right hand operant
+-- @tparam quat a Left hand operand
+-- @tparam quat b Right hand operand
 -- @tparam number s Step value
 -- @treturn quat out
-function quat.slerp(out, a, b, s)
+function quat.slerp(a, b, s)
 	local dot = a:dot(b)
 
 	if dot < 0 then
-		a:scale(a, -1)
+		a   = -a
 		dot = -dot
 	end
 
 	if dot > DOT_THRESHOLD then
-		return out:lerp(a, b, s)
+		return a:lerp(b, s)
 	end
 
 	dot = min(max(dot, -1), 1)
-	local theta = acos(dot) * s
-
-	tmp:scale(a, cos(theta))
 
-	return out
-		:scale(a, dot)
-		:sub(b, out)
-		:normalize(out)
-		:scale(out, sin(theta))
-		:add(tmp, out)
+	local theta = acos(dot) * s
+	local c = (b - a * dot):normalize()
+	return a * cos(theta) + c * sin(theta)
 end
 
 --- Unpack a quaternion into individual components.
@@ -372,29 +366,40 @@ function quat.is_imaginary(a)
 	return a.w == 0
 end
 
---- Convert a quaternion into an angle/axis pair.
+--- Convert a quaternion into an angle plus axis components.
 -- @tparam quat a Quaternion to convert
 -- @treturn number angle
--- @treturn vec3 axis
-function quat.to_angle_axis(a)
+-- @treturn x axis-x
+-- @treturn y axis-y
+-- @treturn z axis-z
+function quat.to_angle_axis_unpack(a)
 	if a.w > 1 or a.w < -1 then
-		a:normalize(a)
+		a = a:normalize()
 	end
 
+	local x, y, z
 	local angle = 2 * acos(a.w)
 	local s     = sqrt(1 - a.w * a.w)
-	local x, y, z
 
-	if s < constants.DBL_EPSILON then
+	if s < DBL_EPSILON then
 		x = a.x
 		y = a.y
 		z = a.z
 	else
-		x = a.x / s -- normalize axis
+		x = a.x / s
 		y = a.y / s
 		z = a.z / s
 	end
 
+	return angle, x, y, z
+end
+
+--- Convert a quaternion into an angle/axis pair.
+-- @tparam quat a Quaternion to convert
+-- @treturn number angle
+-- @treturn vec3 axis
+function quat.to_angle_axis(a)
+	local angle, x, y, z = a:to_angle_axis_unpack()
 	return angle, vec3(x, y, z)
 end
 
@@ -402,11 +407,7 @@ end
 -- @tparam quat a Quaternion to convert
 -- @treturn vec3 out
 function quat.to_vec3(a)
-	local out = vec3()
-	out.x = a.x
-	out.y = a.y
-	out.z = a.z
-	return out
+	return vec3(a.x, a.y, a.z)
 end
 
 --- Return a formatted string.
@@ -424,7 +425,7 @@ function quat_mt.__call(_, x, y, z, w)
 end
 
 function quat_mt.__unm(a)
-	return new():scale(a, -1)
+	return a:scale(-1)
 end
 
 function quat_mt.__eq(a,b)
@@ -435,36 +436,36 @@ function quat_mt.__eq(a,b)
 end
 
 function quat_mt.__add(a, b)
-	assert(quat.is_quat(a), "__add: Wrong argument type for left hand operant. (<cpml.quat> expected)")
-	assert(quat.is_quat(b), "__add: Wrong argument type for right hand operant. (<cpml.quat> expected)")
-	return new():add(a, b)
+	assert(quat.is_quat(a), "__add: Wrong argument type for left hand operand. (<cpml.quat> expected)")
+	assert(quat.is_quat(b), "__add: Wrong argument type for right hand operand. (<cpml.quat> expected)")
+	return a:add(b)
 end
 
 function quat_mt.__sub(a, b)
-	assert(quat.is_quat(a), "__sub: Wrong argument type for left hand operant. (<cpml.quat> expected)")
-	assert(quat.is_quat(b), "__sub: Wrong argument type for right hand operant. (<cpml.quat> expected)")
-	return new():sub(a, b)
+	assert(quat.is_quat(a), "__sub: Wrong argument type for left hand operand. (<cpml.quat> expected)")
+	assert(quat.is_quat(b), "__sub: Wrong argument type for right hand operand. (<cpml.quat> expected)")
+	return a:sub(b)
 end
 
 function quat_mt.__mul(a, b)
-	assert(quat.is_quat(a), "__mul: Wrong argument type for left hand operant. (<cpml.quat> expected)")
-	assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operant. (<cpml.quat> or <cpml.vec3> or <number> expected)")
+	assert(quat.is_quat(a), "__mul: Wrong argument type for left hand operand. (<cpml.quat> expected)")
+	assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.quat> or <cpml.vec3> or <number> expected)")
 
 	if quat.is_quat(b) then
-		return new():mul(a, b)
+		return a:mul(b)
 	end
 
 	if type(b) == "number" then
-		return new():scale(a, b)
+		return a:scale(b)
 	end
 
-	return quat.mul_vec3(vec3(), a, b)
+	return a:mul_vec3(b)
 end
 
 function quat_mt.__pow(a, n)
-	assert(quat.is_quat(a), "__pow: Wrong argument type for left hand operant. (<cpml.quat> expected)")
-	assert(type(n) == "number", "__pow: Wrong argument type for right hand operant. (<number> expected)")
-	return new():pow(a, n)
+	assert(quat.is_quat(a), "__pow: Wrong argument type for left hand operand. (<cpml.quat> expected)")
+	assert(type(n) == "number", "__pow: Wrong argument type for right hand operand. (<number> expected)")
+	return a:pow(n)
 end
 
 if status then

+ 4 - 8
lib/cpml/modules/simplex.lua → lua_modules/cpml/modules/simplex.lua

@@ -29,15 +29,12 @@
 -- [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
 --
 
--- Bail out with dummy module if FFI is missing.
-local has_ffi, ffi = pcall(require, "ffi")
-
-
-if love and love.math then
+if _G.love and _G.love.math then
 	return love.math.noise
 end
 
-local M = {}
+-- Bail out with dummy module if FFI is missing.
+local has_ffi, ffi = pcall(require, "ffi")
 if not has_ffi then
 	return function()
 		return 0
@@ -45,12 +42,11 @@ if not has_ffi then
 end
 
 -- Modules --
--- local bit = require("bit")
+local bit = require("bit")
 
 -- Imports --
 local band   = bit.band
 local bor    = bit.bor
-local bxor   = bit.bxor
 local floor  = math.floor
 local lshift = bit.lshift
 local max    = math.max

+ 64 - 43
lib/cpml/modules/utils.lua → lua_modules/cpml/modules/utils.lua

@@ -1,11 +1,9 @@
 --- Various utility functions
 -- @module utils
 
-local modules = (...): gsub('%/[^%/]+$', '') .. "/"
+local modules = (...): gsub('%.[^%.]+$', '') .. "."
 local vec2    = require(modules .. "vec2")
 local vec3    = require(modules .. "vec3")
-local atan2   = math.atan2
-local acos    = math.acos
 local sqrt    = math.sqrt
 local abs     = math.abs
 local ceil    = math.ceil
@@ -71,12 +69,22 @@ end
 
 --- Linear interpolation.
 -- Performs linear interpolation between 0 and 1 when `low` < `progress` < `high`.
--- @param progress (0-1)
 -- @param low value to return when `progress` is 0
 -- @param high value to return when `progress` is 1
+-- @param progress (0-1)
+-- @return number
+function utils.lerp(low, high, progress)
+	return low * (1 - progress) + high * progress
+end
+
+--- Exponential decay
+-- @param low initial value
+-- @param high target value
+-- @param rate portion of the original value remaining per second
+-- @param dt time delta
 -- @return number
-function utils.lerp(progress, low, high)
-	return progress * (high - low) + low
+function utils.decay(low, high, rate, dt)
+	return utils.lerp(low, high, 1.0 - math.exp(-rate * dt))
 end
 
 --- Hermite interpolation.
@@ -124,67 +132,80 @@ function utils.is_pot(value)
 end
 
 -- Originally from vec3
-function utils.project_on(out, a, b)
-	local isvec3 = vec3.is_vec3(out)
+function utils.project_on(a, b)
 	local s =
-		(a.x * b.x + a.y * b.y + isvec3 and a.z * b.z or 0) /
-		(b.x * b.x + b.y * b.y + isvec3 and b.z * b.z or 0)
-
-	out.x = b.x * s
-	out.y = b.y * s
-	out.z = isvec3 and b.z * s or nil
+		(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /
+		(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0)
+
+	if a.z and b.z then
+		return vec3(
+			b.x * s,
+			b.y * s,
+			b.z * s
+		)
+	end
 
-	return out
+	return vec2(
+		b.x * s,
+		b.y * s
+	)
 end
 
 -- Originally from vec3
-function utils.project_from(out, a, b)
-	local isvec3 = vec3.is_vec3(out)
+function utils.project_from(a, b)
 	local s =
-		(b.x * b.x + b.y * b.y + isvec3 and b.z * b.z or 0) /
-		(a.x * b.x + a.y * b.y + isvec3 and a.z * b.z or 0)
-
-	out.x = b.x * s
-	out.y = b.y * s
-	out.z = isvec3 and b.z * s or nil
+		(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) /
+		(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0)
+
+	if a.z and b.z then
+		return vec3(
+			b.x * s,
+			b.y * s,
+			b.z * s
+		)
+	end
 
-	return out
+	return vec2(
+		b.x * s,
+		b.y * s
+	)
 end
 
 -- Originally from vec3
-function utils.mirror_on(out, a, b)
-	local isvec3 = vec3.is_vec3(out)
+function utils.mirror_on(a, b)
 	local s =
-		(a.x * b.x + a.y * b.y + isvec3 and a.z * b.z or 0) /
-		(b.x * b.x + b.y * b.y + isvec3 and b.z * b.z or 0) * 2
-
-	out.x = b.x * s - a.x
-	out.y = b.y * s - a.y
-	out.z = isvec3 and b.z * s - a.z or nil
+		(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /
+		(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) * 2
+
+	if a.z and b.z then
+		return vec3(
+			b.x * s - a.x,
+			b.y * s - a.y,
+			b.z * s - a.z
+		)
+	end
 
-	return out
+	return vec2(
+		b.x * s - a.x,
+		b.y * s - a.y
+	)
 end
 
 -- Originally from vec3
-function utils.reflect(out, i, n)
-	return out
-		:mul(n, n:dot(i) * 2)
-		:sub(i, out)
+function utils.reflect(i, n)
+	return i - (n * (2 * n:dot(i)))
 end
 
 -- Originally from vec3
-local tmp = vec3()
-function utils.refract(out, i, n, ior)
+function utils.refract(i, n, ior)
 	local d = n:dot(i)
 	local k = 1 - ior * ior * (1 - d * d)
 
 	if k >= 0 then
-		tmp:mul(n, ior * d + sqrt(k))
-		out:mul(i, ior)
-		out:sub(out, tmp)
+		return (i * ior) - (n * (ior * d + sqrt(k)))
 	end
 
-	return out
+	return vec3()
 end
 
 return utils

+ 106 - 97
lib/cpml/modules/vec2.lua → lua_modules/cpml/modules/vec2.lua

@@ -1,6 +1,9 @@
 --- A 2 component vector.
 -- @module vec2
 
+local modules = (...):gsub('%.[^%.]+$', '') .. "."
+local vec3    = require(modules .. "vec3")
+local acos    = math.acos
 local atan2   = math.atan2
 local sqrt    = math.sqrt
 local cos     = math.cos
@@ -10,16 +13,16 @@ local vec2_mt = {}
 
 -- Private constructor.
 local function new(x, y)
-	local v  = {}
-	v.x, v.y = x, y
-	return setmetatable(v, vec2_mt)
+	return setmetatable({
+		x = x or 0,
+		y = y or 0
+	}, vec2_mt)
 end
 
 -- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
 local status, ffi
 if type(jit) == "table" and jit.status() then
 	status, ffi = pcall(require, "ffi")
-  status = false
 	if status then
 		ffi.cdef "typedef struct { double x, y;} cpml_vec2;"
 		new = ffi.typeof("cpml_vec2")
@@ -52,17 +55,17 @@ function vec2.new(x, y)
 
 	-- {x, y} or {x=x, y=y}
 	elseif type(x) == "table" then
-		local x, y = x.x or x[1], x.y or x[2]
-		assert(type(x) == "number", "new: Wrong argument type for x (<number> expected)")
-		assert(type(y) == "number", "new: Wrong argument type for y (<number> expected)")
+		local xx, yy = x.x or x[1], x.y or x[2]
+		assert(type(xx) == "number", "new: Wrong argument type for x (<number> expected)")
+		assert(type(yy) == "number", "new: Wrong argument type for y (<number> expected)")
 
-		return new(x, y)
+		return new(xx, yy)
 
 	-- number
 	elseif type(x) == "number" then
 		return new(x, x)
 	else
-		return new(0, 0)
+		return new()
 	end
 end
 
@@ -82,82 +85,78 @@ function vec2.clone(a)
 end
 
 --- Add two vectors.
--- @tparam vec2 out Vector to store the result
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn vec2 out
-function vec2.add(out, a, b)
-	out.x = a.x + b.x
-	out.y = a.y + b.y
-	return out
+function vec2.add(a, b)
+	return new(
+		a.x + b.x,
+		a.y + b.y
+	)
 end
 
 --- Subtract one vector from another.
--- @tparam vec2 out Vector to store the result
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn vec2 out
-function vec2.sub(out, a, b)
-	out.x = a.x - b.x
-	out.y = a.y - b.y
-	return out
+function vec2.sub(a, b)
+	return new(
+		a.x - b.x,
+		a.y - b.y
+	)
 end
 
 --- Multiply a vector by another vector.
--- @tparam vec2 out Vector to store the result
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn vec2 out
-function vec2.mul(out, a, b)
-	out.x = a.x * b.x
-	out.y = a.y * b.y
-	return out
+function vec2.mul(a, b)
+	return new(
+		a.x * b.x,
+		a.y * b.y
+	)
 end
 
 --- Divide a vector by another vector.
--- @tparam vec2 out Vector to store the result
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn vec2 out
-function vec2.div(out, a, b)
-	out.x = a.x / b.x
-	out.y = a.y / b.y
-	return out
+function vec2.div(a, b)
+	return new(
+		a.x / b.x,
+		a.y / b.y
+	)
 end
 
 --- Get the normal of a vector.
--- @tparam vec2 out Vector to store the result
 -- @tparam vec2 a Vector to normalize
 -- @treturn vec2 out
-function vec2.normalize(out, a)
-	local l = a:len()
-	out.x = a.x / l
-	out.y = a.y / l
-	return out
+function vec2.normalize(a)
+	if a:is_zero() then
+		return new()
+	end
+	return a:scale(1 / a:len())
 end
 
 --- Trim a vector to a given length.
--- @tparam vec2 out Vector to store the result
 -- @tparam vec2 a Vector to be trimmed
 -- @tparam number len Length to trim the vector to
 -- @treturn vec2 out
-function vec2.trim(out, a, len)
-	return out
-		:normalize(a)
-		:scale(out, math.min(a:len(), len))
+function vec2.trim(a, len)
+	return a:normalize():scale(math.min(a:len(), len))
 end
 
 --- Get the cross product of two vectors.
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn number magnitude
 function vec2.cross(a, b)
 	return a.x * b.y - a.y * b.x
 end
 
 --- Get the dot product of two vectors.
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn number dot
 function vec2.dot(a, b)
 	return a.x * b.x + a.y * b.y
@@ -178,8 +177,8 @@ function vec2.len2(a)
 end
 
 --- Get the distance between two vectors.
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn number dist
 function vec2.dist(a, b)
 	local dx = a.x - b.x
@@ -188,8 +187,8 @@ function vec2.dist(a, b)
 end
 
 --- Get the squared distance between two vectors.
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @treturn number dist
 function vec2.dist2(a, b)
 	local dx = a.x - b.x
@@ -198,37 +197,34 @@ function vec2.dist2(a, b)
 end
 
 --- Scale a vector by a scalar.
--- @tparam vec2 out Vector to store the result
--- @tparam vec2 a Left hand operant
--- @tparam number b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam number b Right hand operand
 -- @treturn vec2 out
-function vec2.scale(out, a, b)
-	out.x = a.x * b
-	out.y = a.y * b
-	return out
+function vec2.scale(a, b)
+	return new(
+		a.x * b,
+		a.y * b
+	)
 end
 
 --- Rotate a vector.
--- @tparam vec2 out Vector to store the result
 -- @tparam vec2 a Vector to rotate
 -- @tparam number phi Angle to rotate vector by (in radians)
 -- @treturn vec2 out
-function vec2.rotate(out, a, phi)
+function vec2.rotate(a, phi)
 	local c = cos(phi)
 	local s = sin(phi)
-	out.x   = c * a.x - s * a.y
-	out.y   = s * a.x + c * a.y
-	return out
+	return new(
+		c * a.x - s * a.y,
+		s * a.x + c * a.y
+	)
 end
 
 --- Get the perpendicular vector of a vector.
--- @tparam vec2 out Vector to store the result
 -- @tparam vec2 a Vector to get perpendicular axes from
 -- @treturn vec2 out
-function vec2.perpendicular(out, a)
-	out.x = -a.y
-	out.y =  a.x
-	return out
+function vec2.perpendicular(a)
+	return new(-a.y, a.x)
 end
 
 --- Angle from one vector to another.
@@ -250,7 +246,7 @@ end
 function vec2.angle_between(a, b)
 	if b then
 		if vec2.is_vec2(a) then
-			return acos(vec2.dot(a, b) / (vec2.len(a) * vec2.len(b)))
+			return acos(a:dot(b) / (a:len() * b:len()))
 		end
 
 		return acos(vec3.dot(a, b) / (vec3.len(a) * vec3.len(b)))
@@ -260,16 +256,12 @@ function vec2.angle_between(a, b)
 end
 
 --- Lerp between two vectors.
--- @tparam vec2 out Vector to store the result
--- @tparam vec2 a Left hand operant
--- @tparam vec2 b Right hand operant
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
 -- @tparam number s Step value
 -- @treturn vec2 out
-function vec2.lerp(out, a, b, s)
-	return out
-		:sub(b, a)
-		:scale(out, s)
-		:add(out, a)
+function vec2.lerp(a, b, s)
+	return a + (b - a) * s
 end
 
 --- Unpack a vector into individual components.
@@ -280,6 +272,23 @@ function vec2.unpack(a)
 	return a.x, a.y
 end
 
+--- Return the component-wise minimum of two vectors.
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
+-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.
+function vec2.component_min(a, b)
+	return new(math.min(a.x, b.x), math.min(a.y, b.y))
+end
+
+--- Return the component-wise maximum of two vectors.
+-- @tparam vec2 a Left hand operand
+-- @tparam vec2 b Right hand operand
+-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.
+function vec2.component_max(a, b)
+	return new(math.max(a.x, b.x), math.max(a.y, b.y))
+end
+
+
 --- Return a boolean showing if a table is or is not a vec2.
 -- @tparam vec2 a Vector to be tested
 -- @treturn boolean is_vec2
@@ -338,41 +347,41 @@ function vec2_mt.__eq(a, b)
 end
 
 function vec2_mt.__add(a, b)
-	assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operant. (<cpml.vec2> expected)")
-	assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operant. (<cpml.vec2> expected)")
-	return new():add(a, b)
+	assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
+	assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operand. (<cpml.vec2> expected)")
+	return a:add(b)
 end
 
 function vec2_mt.__sub(a, b)
-	assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operant. (<cpml.vec2> expected)")
-	assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operant. (<cpml.vec2> expected)")
-	return new():sub(a, b)
+	assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
+	assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operand. (<cpml.vec2> expected)")
+	return a:sub(b)
 end
 
 function vec2_mt.__mul(a, b)
-	assert(vec2.is_vec2(a), "__mul: Wrong argument type for left hand operant. (<cpml.vec2> expected)")
-	assert(vec2.is_vec2(b) or type(b) == "number", "__mul: Wrong argument type for right hand operant. (<cpml.vec2> or <number> expected)")
+	assert(vec2.is_vec2(a), "__mul: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
+	assert(vec2.is_vec2(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
 
 	if vec2.is_vec2(b) then
-		return new():mul(a, b)
+		return a:mul(b)
 	end
 
-	return new():scale(a, b)
+	return a:scale(b)
 end
 
 function vec2_mt.__div(a, b)
-	assert(vec2.is_vec2(a), "__div: Wrong argument type for left hand operant. (<cpml.vec2> expected)")
-	assert(vec2.is_vec2(b) or type(b) == "number", "__div: Wrong argument type for right hand operant. (<cpml.vec2> or <number> expected)")
+	assert(vec2.is_vec2(a), "__div: Wrong argument type for left hand operand. (<cpml.vec2> expected)")
+	assert(vec2.is_vec2(b) or type(b) == "number", "__div: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
 
 	if vec2.is_vec2(b) then
-		return new():div(a, b)
+		return a:div(b)
 	end
 
-	return new():scale(a, 1 / b)
+	return a:scale(1 / b)
 end
 
 if status then
-  ffi.metatype(new, vec2_mt)
+	ffi.metatype(new, vec2_mt)
 end
 
 return setmetatable({}, vec2_mt)

+ 120 - 118
lib/cpml/modules/vec3.lua → lua_modules/cpml/modules/vec3.lua

@@ -9,16 +9,17 @@ local vec3_mt = {}
 
 -- Private constructor.
 local function new(x, y, z)
-	local v       = {}
-	v.x, v.y, v.z = x, y, z
-	return setmetatable(v, vec3_mt)
+	return setmetatable({
+		x = x or 0,
+		y = y or 0,
+		z = z or 0
+	}, vec3_mt)
 end
 
 -- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
 local status, ffi
 if type(jit) == "table" and jit.status() then
 	status, ffi = pcall(require, "ffi")
-  status = false
 	if status then
 		ffi.cdef "typedef struct { double x, y, z;} cpml_vec3;"
 		new = ffi.typeof("cpml_vec3")
@@ -36,9 +37,6 @@ vec3.unit_y = new(0, 1, 0)
 vec3.unit_z = new(0, 0, 1)
 vec3.zero   = new(0, 0, 0)
 
--- Statically allocate a temporary variable used in some of our functions.
-local tmp = new(0, 0, 0)
-
 --- The public constructor.
 -- @param x Can be of three types: </br>
 -- number X component
@@ -58,18 +56,18 @@ function vec3.new(x, y, z)
 
 	-- {x, y, z} or {x=x, y=y, z=z}
 	elseif type(x) == "table" then
-		local x, y, z = x.x or x[1], x.y or x[2], x.z or x[3]
-		assert(type(x) == "number", "new: Wrong argument type for x (<number> expected)")
-		assert(type(y) == "number", "new: Wrong argument type for y (<number> expected)")
-		assert(type(z) == "number", "new: Wrong argument type for z (<number> expected)")
+		local xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]
+		assert(type(xx) == "number", "new: Wrong argument type for x (<number> expected)")
+		assert(type(yy) == "number", "new: Wrong argument type for y (<number> expected)")
+		assert(type(zz) == "number", "new: Wrong argument type for z (<number> expected)")
 
-		return new(x, y, z)
+		return new(xx, yy, zz)
 
 	-- number
 	elseif type(x) == "number" then
 		return new(x, x, x)
 	else
-		return new(0, 0, 0)
+		return new()
 	end
 end
 
@@ -81,91 +79,86 @@ function vec3.clone(a)
 end
 
 --- Add two vectors.
--- @tparam vec3 out Vector to store the result
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn vec3 out
-function vec3.add(out, a, b)
-	out.x = a.x + b.x
-	out.y = a.y + b.y
-	out.z = a.z + b.z
-	return out
+function vec3.add(a, b)
+	return new(
+		a.x + b.x,
+		a.y + b.y,
+		a.z + b.z
+	)
 end
 
 --- Subtract one vector from another.
--- @tparam vec3 out Vector to store the result
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn vec3 out
-function vec3.sub(out, a, b)
-	out.x = a.x - b.x
-	out.y = a.y - b.y
-	out.z = a.z - b.z
-	return out
+function vec3.sub(a, b)
+	return new(
+		a.x - b.x,
+		a.y - b.y,
+		a.z - b.z
+	)
 end
 
 --- Multiply a vector by another vectorr.
--- @tparam vec3 out Vector to store the result
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn vec3 out
-function vec3.mul(out, a, b)
-	out.x = a.x * b.x
-	out.y = a.y * b.y
-	out.z = a.z * b.z
-	return out
+function vec3.mul(a, b)
+	return new(
+		a.x * b.x,
+		a.y * b.y,
+		a.z * b.z
+	)
 end
 
 --- Divide a vector by a scalar.
--- @tparam vec3 out Vector to store the result
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn vec3 out
-function vec3.div(out, a, b)
-	out.x = a.x / b.x
-	out.y = a.y / b.y
-	out.z = a.z / b.z
-	return out
+function vec3.div(a, b)
+	return new(
+		a.x / b.x,
+		a.y / b.y,
+		a.z / b.z
+	)
 end
 
 --- Get the normal of a vector.
--- @tparam vec3 out Vector to store the result
 -- @tparam vec3 a Vector to normalize
 -- @treturn vec3 out
-function vec3.normalize(out, a)
-	local l = vec3.len(a)
-	out.x = a.x / l
-	out.y = a.y / l
-	out.z = a.z / l
-	return out
+function vec3.normalize(a)
+	if a:is_zero() then
+		return new()
+	end
+	return a:scale(1 / a:len())
 end
 
 --- Trim a vector to a given length
--- @tparam vec3 out Vector to store the result
 -- @tparam vec3 a Vector to be trimmed
 -- @tparam number len Length to trim the vector to
 -- @treturn vec3 out
-function vec3.trim(out, a, len)
-	return out
-		:normalize(a)
-		:scale(out, math.min(vec3.len(a), len))
+function vec3.trim(a, len)
+	return a:normalize():scale(math.min(a:len(), len))
 end
 
 --- Get the cross product of two vectors.
--- @tparam vec3 out Vector to store the result
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn vec3 out
-function vec3.cross(out, a, b)
-	out.x = a.y * b.z - a.z * b.y
-	out.y = a.z * b.x - a.x * b.z
-	out.z = a.x * b.y - a.y * b.x
-	return out
+function vec3.cross(a, b)
+	return new(
+		a.y * b.z - a.z * b.y,
+		a.z * b.x - a.x * b.z,
+		a.x * b.y - a.y * b.x
+	)
 end
 
 --- Get the dot product of two vectors.
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn number dot
 function vec3.dot(a, b)
 	return a.x * b.x + a.y * b.y + a.z * b.z
@@ -186,8 +179,8 @@ function vec3.len2(a)
 end
 
 --- Get the distance between two vectors.
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn number dist
 function vec3.dist(a, b)
 	local dx = a.x - b.x
@@ -197,8 +190,8 @@ function vec3.dist(a, b)
 end
 
 --- Get the squared distance between two vectors.
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @treturn number dist
 function vec3.dist2(a, b)
 	local dx = a.x - b.x
@@ -208,29 +201,28 @@ function vec3.dist2(a, b)
 end
 
 --- Scale a vector by a scalar.
--- @tparam vec3 out Vector to store the result
--- @tparam vec3 a Left hand operant
--- @tparam number b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam number b Right hand operand
 -- @treturn vec3 out
-function vec3.scale(out, a, b)
-	out.x = a.x * b
-	out.y = a.y * b
-	out.z = a.z * b
-	return out
+function vec3.scale(a, b)
+	return new(
+		a.x * b,
+		a.y * b,
+		a.z * b
+	)
 end
 
 --- Rotate vector about an axis.
--- @tparam vec3 out Vector to store the result
 -- @tparam vec3 a Vector to rotate
--- @tparam number phi Amount to rotate, in radians
+-- @tparam number phi Angle to rotate vector by (in radians)
 -- @tparam vec3 axis Axis to rotate by
 -- @treturn vec3 out
-function vec3.rotate(out, a, phi, axis)
+function vec3.rotate(a, phi, axis)
 	if not vec3.is_vec3(axis) then
 		return a
 	end
 
-	local u = new():normalize(axis)
+	local u = axis:normalize()
 	local c = cos(phi)
 	local s = sin(phi)
 
@@ -239,30 +231,27 @@ function vec3.rotate(out, a, phi, axis)
 	local m2 = new((u.y * u.x * (1 - c) + u.z * s), (c + u.y * u.y * (1 - c)),       (u.y * u.z * (1 - c) - u.x * s))
 	local m3 = new((u.z * u.x * (1 - c) - u.y * s), (u.z * u.y * (1 - c) + u.x * s), (c + u.z * u.z * (1 - c))      )
 
-	out.x = a:dot(m1)
-	out.y = a:dot(m2)
-	out.z = a:dot(m3)
-	return out
+	return new(
+		a:dot(m1),
+		a:dot(m2),
+		a:dot(m3)
+	)
 end
 
-function vec3.perpendicular(out, a)
-	out.x = -a.y
-	out.y =  a.x
-	out.z =  0
-	return out
+--- Get the perpendicular vector of a vector.
+-- @tparam vec3 a Vector to get perpendicular axes from
+-- @treturn vec3 out
+function vec3.perpendicular(a)
+	return new(-a.y, a.x, 0)
 end
 
 --- Lerp between two vectors.
--- @tparam vec3 out Vector to store the result
--- @tparam vec3 a Left hand operant
--- @tparam vec3 b Right hand operant
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
 -- @tparam number s Step value
 -- @treturn vec3 out
-function vec3.lerp(out, a, b, s)
-	return out
-		:sub(b, a)
-		:scale(out, s)
-		:add(out, a)
+function vec3.lerp(a, b, s)
+	return a + (b - a) * s
 end
 
 --- Unpack a vector into individual components.
@@ -274,6 +263,22 @@ function vec3.unpack(a)
 	return a.x, a.y, a.z
 end
 
+--- Return the component-wise minimum of two vectors.
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
+-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
+function vec3.component_min(a, b)
+	return new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z))
+end
+
+--- Return the component-wise maximum of two vectors.
+-- @tparam vec3 a Left hand operand
+-- @tparam vec3 b Right hand operand
+-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
+function vec3.component_max(a, b)
+	return new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
+end
+
 --- Return a boolean showing if a table is or is not a vec3.
 -- @tparam vec3 a Vector to be tested
 -- @treturn boolean is_vec3
@@ -293,10 +298,7 @@ end
 -- @tparam vec3 a Vector to be tested
 -- @treturn boolean is_zero
 function vec3.is_zero(a)
-	return
-		a.x == 0 and
-		a.y == 0 and
-		a.z == 0
+	return a.x == 0 and a.y == 0 and a.z == 0
 end
 
 --- Return a formatted string.
@@ -325,37 +327,37 @@ function vec3_mt.__eq(a, b)
 end
 
 function vec3_mt.__add(a, b)
-	assert(vec3.is_vec3(a), "__add: Wrong argument type for left hand operant. (<cpml.vec3> expected)")
-	assert(vec3.is_vec3(b), "__add: Wrong argument type for right hand operant. (<cpml.vec3> expected)")
-	return new():add(a, b)
+	assert(vec3.is_vec3(a), "__add: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
+	assert(vec3.is_vec3(b), "__add: Wrong argument type for right hand operand. (<cpml.vec3> expected)")
+	return a:add(b)
 end
 
 function vec3_mt.__sub(a, b)
-	assert(vec3.is_vec3(a), "__sub: Wrong argument type for left hand operant. (<cpml.vec3> expected)")
-	assert(vec3.is_vec3(b), "__sub: Wrong argument type for right hand operant. (<cpml.vec3> expected)")
-	return new():sub(a, b)
+	assert(vec3.is_vec3(a), "__sub: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
+	assert(vec3.is_vec3(b), "__sub: Wrong argument type for right hand operand. (<cpml.vec3> expected)")
+	return a:sub(b)
 end
 
 function vec3_mt.__mul(a, b)
-	assert(vec3.is_vec3(a), "__mul: Wrong argument type for left hand operant. (<cpml.vec3> expected)")
-	assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operant. (<cpml.vec3> or <number> expected)")
+	assert(vec3.is_vec3(a), "__mul: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
+	assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.vec3> or <number> expected)")
 
 	if vec3.is_vec3(b) then
-		return new():mul(a, b)
+		return a:mul(b)
 	end
 
-	return new():scale(a, b)
+	return a:scale(b)
 end
 
 function vec3_mt.__div(a, b)
-	assert(vec3.is_vec3(a), "__div: Wrong argument type for left hand operant. (<cpml.vec3> expected)")
-	assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type for right hand operant. (<cpml.vec3> or <number> expected)")
+	assert(vec3.is_vec3(a), "__div: Wrong argument type for left hand operand. (<cpml.vec3> expected)")
+	assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type for right hand operand. (<cpml.vec3> or <number> expected)")
 
 	if vec3.is_vec3(b) then
-		return new():div(a, b)
+		return a:div(b)
 	end
 
-	return new():scale(a, 1 / b)
+	return a:scale(1 / b)
 end
 
 if status then

+ 180 - 0
lua_modules/cpml/spec/bound2_spec.lua

@@ -0,0 +1,180 @@
+local bound2      = require "modules.bound2"
+local vec2      = require "modules.vec2"
+local DBL_EPSILON = require("modules.constants").DBL_EPSILON
+
+describe("bound2:", function()
+	it("creates an empty bound2", function()
+		local a = bound2()
+		assert.is.equal(0, a.min.x)
+		assert.is.equal(0, a.min.y)
+		assert.is.equal(0, a.max.x)
+		assert.is.equal(0, a.max.y)
+	end)
+
+	it("creates a bound2 from vec2s", function()
+		local a = bound2(vec2(1,2), vec2(4,5))
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+	end)
+
+	it("creates a bound2 using new()", function()
+		local a = bound2.new(vec2(1,2), vec2(4,5))
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+	end)
+
+	it("creates a bound2 using at()", function()
+		local a = bound2.at(vec2(4,5), vec2(1,2))
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+	end)
+
+	it("clones a bound2", function()
+		local a = bound2(vec2(1,2), vec2(4,5))
+		local b = a:clone()
+		a.max = new vec2(9,9)
+		assert.is.equal(a.min, b.min)
+		assert.is.not_equal(a.max, b.max)
+	end)
+
+	it("uses bound2 check()", function()
+		local a = bound2(vec2(4,2), vec2(1,5)):check()
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+	end)
+
+	it("queries a bound2 size", function()
+		local a = bound2(vec2(1,2), vec2(4,6))
+		local v = a:size()
+		local r = a:radius()
+		assert.is.equal(3, v.x)
+		assert.is.equal(4, v.y)
+
+		assert.is.equal(1.5, r.x)
+		assert.is.equal(2, r.y)
+	end)
+
+	it("sets a bound2 size", function()
+		local a = bound2(vec2(1,2), vec2(4,5))
+		local b = a:with_size(vec2(1,1))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+
+		assert.is.equal(1, b.min.x)
+		assert.is.equal(2, b.min.y)
+		assert.is.equal(2, b.max.x)
+		assert.is.equal(3, b.max.y)
+	end)
+
+	it("queries a bound2 center", function()
+		local a = bound2(vec2(1,2), vec2(3,4))
+		local v = a:center()
+		assert.is.equal(2, v.x)
+		assert.is.equal(3, v.y)
+	end)
+
+	it("sets a bound2 center", function()
+		local a = bound2(vec2(1,2), vec2(3,4))
+		local b = a:with_center(vec2(1,1))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.max.x)
+		assert.is.equal(4, a.max.y)
+
+		assert.is.equal(0, b.min.x)
+		assert.is.equal(0, b.min.y)
+		assert.is.equal(2, b.max.x)
+		assert.is.equal(2, b.max.y)
+	end)
+
+	it("sets a bound2 size centered", function()
+		local a = bound2(vec2(1,2), vec2(3,4))
+		local b = a:with_size_centered(vec2(4,4))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.max.x)
+		assert.is.equal(4, a.max.y)
+
+		assert.is.equal(0, b.min.x)
+		assert.is.equal(1, b.min.y)
+		assert.is.equal(4, b.max.x)
+		assert.is.equal(5, b.max.y)
+	end)
+
+	it("insets a bound2", function()
+		local a = bound2(vec2(1,2), vec2(5,10))
+		local b = a:inset(vec2(1,2))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(5, a.max.x)
+		assert.is.equal(10, a.max.y)
+
+		assert.is.equal(2, b.min.x)
+		assert.is.equal(4, b.min.y)
+		assert.is.equal(4, b.max.x)
+		assert.is.equal(8, b.max.y)
+	end)
+
+	it("outsets a bound2", function()
+		local a = bound2(vec2(1,2), vec2(5,6))
+		local b = a:outset(vec2(1,2))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(5, a.max.x)
+		assert.is.equal(6, a.max.y)
+
+		assert.is.equal(0, b.min.x)
+		assert.is.equal(0, b.min.y)
+		assert.is.equal(6, b.max.x)
+		assert.is.equal(8, b.max.y)
+	end)
+
+	it("offsets a bound2", function()
+		local a = bound2(vec2(1,2), vec2(5,6))
+		local b = a:offset(vec2(1,2))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(5, a.max.x)
+		assert.is.equal(6, a.max.y)
+
+		assert.is.equal(2, b.min.x)
+		assert.is.equal(4, b.min.y)
+		assert.is.equal(6, b.max.x)
+		assert.is.equal(8, b.max.y)
+	end)
+
+	it("tests for points inside bound2", function()
+		local a = bound2(vec2(1,2), vec2(4,5))
+
+		assert.is_true(a:contains(vec2(2,3)))
+		assert.is_not_true(a:contains(vec2(0,3)))
+		assert.is_not_true(a:contains(vec2(5,3)))
+		assert.is_not_true(a:contains(vec2(2,1)))
+		assert.is_not_true(a:contains(vec2(2,6)))
+		assert.is_not_true(a:contains(vec2(2,3)))
+		assert.is_not_true(a:contains(vec2(2,3)))
+	end)
+
+	it("checks for bound2.zero", function()
+		assert.is.equal(0, bound2.zero.min.x)
+		assert.is.equal(0, bound2.zero.min.y)
+		assert.is.equal(0, bound2.zero.max.x)
+		assert.is.equal(0, bound2.zero.max.y)
+	end)
+end)

+ 219 - 0
lua_modules/cpml/spec/bound3_spec.lua

@@ -0,0 +1,219 @@
+local bound3      = require "modules.bound3"
+local vec3      = require "modules.vec3"
+local DBL_EPSILON = require("modules.constants").DBL_EPSILON
+
+describe("bound3:", function()
+	it("creates an empty bound3", function()
+		local a = bound3()
+		assert.is.equal(0, a.min.x)
+		assert.is.equal(0, a.min.y)
+		assert.is.equal(0, a.min.z)
+		assert.is.equal(0, a.max.x)
+		assert.is.equal(0, a.max.y)
+		assert.is.equal(0, a.max.z)
+	end)
+
+	it("creates a bound3 from vec3s", function()
+		local a = bound3(vec3(1,2,3), vec3(4,5,6))
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+		assert.is.equal(6, a.max.z)
+	end)
+
+	it("creates a bound3 using new()", function()
+		local a = bound3.new(vec3(1,2,3), vec3(4,5,6))
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+		assert.is.equal(6, a.max.z)
+	end)
+
+	it("creates a bound3 using at()", function()
+		local a = bound3.at(vec3(4,5,6), vec3(1,2,3))
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+		assert.is.equal(6, a.max.z)
+	end)
+
+	it("clones a bound3", function()
+		local a = bound3(vec3(1,2,3), vec3(4,5,6))
+		local b = a:clone()
+		a.max = new vec3(9,9,9)
+		assert.is.equal(a.min, b.min)
+		assert.is.not_equal(a.max, b.max)
+	end)
+
+	it("uses bound3 check()", function()
+		local a = bound3(vec3(4,2,6), vec3(1,5,3)):check()
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+		assert.is.equal(6, a.max.z)
+	end)
+
+	it("queries a bound3 size", function()
+		local a = bound3(vec3(1,2,3), vec3(4,6,8))
+		local v = a:size()
+		local r = a:radius()
+		assert.is.equal(3, v.x)
+		assert.is.equal(4, v.y)
+		assert.is.equal(5, v.z)
+
+		assert.is.equal(1.5, r.x)
+		assert.is.equal(2, r.y)
+		assert.is.equal(2.5, r.z)
+	end)
+
+	it("sets a bound3 size", function()
+		local a = bound3(vec3(1,2,3), vec3(4,5,6))
+		local b = a:with_size(vec3(1,1,1))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(4, a.max.x)
+		assert.is.equal(5, a.max.y)
+		assert.is.equal(6, a.max.z)
+
+		assert.is.equal(1, b.min.x)
+		assert.is.equal(2, b.min.y)
+		assert.is.equal(3, b.min.z)
+		assert.is.equal(2, b.max.x)
+		assert.is.equal(3, b.max.y)
+		assert.is.equal(4, b.max.z)
+	end)
+
+	it("queries a bound3 center", function()
+		local a = bound3(vec3(1,2,3), vec3(3,4,5))
+		local v = a:center()
+		assert.is.equal(2, v.x)
+		assert.is.equal(3, v.y)
+		assert.is.equal(4, v.z)
+	end)
+
+	it("sets a bound3 center", function()
+		local a = bound3(vec3(1,2,3), vec3(3,4,5))
+		local b = a:with_center(vec3(1,1,1))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(3, a.max.x)
+		assert.is.equal(4, a.max.y)
+		assert.is.equal(5, a.max.z)
+
+		assert.is.equal(0, b.min.x)
+		assert.is.equal(0, b.min.y)
+		assert.is.equal(0, b.min.z)
+		assert.is.equal(2, b.max.x)
+		assert.is.equal(2, b.max.y)
+		assert.is.equal(2, b.max.z)
+	end)
+
+	it("sets a bound3 size centered", function()
+		local a = bound3(vec3(1,2,3), vec3(3,4,5))
+		local b = a:with_size_centered(vec3(4,4,4))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(3, a.max.x)
+		assert.is.equal(4, a.max.y)
+		assert.is.equal(5, a.max.z)
+
+		assert.is.equal(0, b.min.x)
+		assert.is.equal(1, b.min.y)
+		assert.is.equal(2, b.min.z)
+		assert.is.equal(4, b.max.x)
+		assert.is.equal(5, b.max.y)
+		assert.is.equal(6, b.max.z)
+	end)
+
+	it("insets a bound3", function()
+		local a = bound3(vec3(1,2,3), vec3(5,10,11))
+		local b = a:inset(vec3(1,2,3))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(5, a.max.x)
+		assert.is.equal(10, a.max.y)
+		assert.is.equal(11, a.max.z)
+
+		assert.is.equal(2, b.min.x)
+		assert.is.equal(4, b.min.y)
+		assert.is.equal(6, b.min.z)
+		assert.is.equal(4, b.max.x)
+		assert.is.equal(8, b.max.y)
+		assert.is.equal(8, b.max.z)
+	end)
+
+	it("outsets a bound3", function()
+		local a = bound3(vec3(1,2,3), vec3(5,6,7))
+		local b = a:outset(vec3(1,2,3))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(5, a.max.x)
+		assert.is.equal(6, a.max.y)
+		assert.is.equal(7, a.max.z)
+
+		assert.is.equal(0, b.min.x)
+		assert.is.equal(0, b.min.y)
+		assert.is.equal(0, b.min.z)
+		assert.is.equal(6, b.max.x)
+		assert.is.equal(8, b.max.y)
+		assert.is.equal(10, b.max.z)
+	end)
+
+	it("offsets a bound3", function()
+		local a = bound3(vec3(1,2,3), vec3(5,6,7))
+		local b = a:offset(vec3(1,2,3))
+
+		assert.is.equal(1, a.min.x)
+		assert.is.equal(2, a.min.y)
+		assert.is.equal(3, a.min.z)
+		assert.is.equal(5, a.max.x)
+		assert.is.equal(6, a.max.y)
+		assert.is.equal(7, a.max.z)
+
+		assert.is.equal(2, b.min.x)
+		assert.is.equal(4, b.min.y)
+		assert.is.equal(6, b.min.z)
+		assert.is.equal(6, b.max.x)
+		assert.is.equal(8, b.max.y)
+		assert.is.equal(10, b.max.z)
+	end)
+
+	it("tests for points inside bound3", function()
+		local a = bound3(vec3(1,2,3), vec3(4,5,6))
+
+		assert.is_true(a:contains(vec3(2,3,4)))
+		assert.is_not_true(a:contains(vec3(0,3,4)))
+		assert.is_not_true(a:contains(vec3(5,3,4)))
+		assert.is_not_true(a:contains(vec3(2,1,4)))
+		assert.is_not_true(a:contains(vec3(2,6,4)))
+		assert.is_not_true(a:contains(vec3(2,3,2)))
+		assert.is_not_true(a:contains(vec3(2,3,7)))
+	end)
+
+	it("checks for bound3.zero", function()
+		assert.is.equal(0, bound3.zero.min.x)
+		assert.is.equal(0, bound3.zero.min.y)
+		assert.is.equal(0, bound3.zero.min.z)
+		assert.is.equal(0, bound3.zero.max.x)
+		assert.is.equal(0, bound3.zero.max.y)
+		assert.is.equal(0, bound3.zero.max.z)
+	end)
+end)

+ 24 - 0
lua_modules/cpml/spec/color_spec.lua

@@ -0,0 +1,24 @@
+local color = require "modules.color"
+
+describe("color:", function()
+end)
+
+--[[
+new(r, g, b, a)
+from_hsv(h, s, v)
+from_hsva(h, s, v, a)
+invert(c)
+lighten(c, v)
+lerp(a, b, s)
+darken(c, v)
+multiply(c, v)
+alpha(c, v)
+opacity(c, v)
+hue(color, hue)
+saturation(color, percent)
+value(color, percent)
+gamma_to_linear(r, g, b, a)
+linear_to_gamma(r, g, b, a)
+is_color(a)
+to_string(a)
+--]]

+ 277 - 0
lua_modules/cpml/spec/intersect_spec.lua

@@ -0,0 +1,277 @@
+local intersect = require "modules.intersect"
+local vec3      = require "modules.vec3"
+local mat4      = require "modules.mat4"
+
+describe("intersect:", function()
+	it("intersects a point with a triangle", function()
+		local a = vec3()
+		local b = vec3(0, 0, 5)
+		local c = {
+			vec3(-1,  -1, 0),
+			vec3( 1,  -1, 0),
+			vec3( 0.5, 1, 0)
+		}
+		assert.is_true(intersect.point_triangle(a, c))
+		assert.is_not_true(intersect.point_triangle(b, c))
+	end)
+
+	it("intersects a point with an aabb", function()
+		local a = vec3()
+		local b = vec3(0, 0, 5)
+		local c = {
+			min = vec3(-1),
+			max = vec3( 1)
+		}
+		assert.is_true(intersect.point_aabb(a, c))
+		assert.is_not_true(intersect.point_aabb(b, c))
+	end)
+
+	it("intersects a point with a frustum", function()
+		pending("TODO")
+	end)
+
+	it("intersects a ray with a triangle", function()
+		local a = {
+			position  = vec3(0.5, 0.5, -1),
+			direction = vec3(0,   0,    1)
+		}
+		local b = {
+			position  = vec3(0.5, 0.5, -1),
+			direction = vec3(0,   0,   -1)
+		}
+		local c = {
+			vec3(-1,  -1, 0),
+			vec3( 1,  -1, 0),
+			vec3( 0.5, 1, 0)
+		}
+		assert.is_true(vec3.is_vec3(intersect.ray_triangle(a, c)))
+		assert.is_not_true(intersect.ray_triangle(b, c))
+	end)
+
+	it("intersects a ray with a sphere", function()
+		local a = {
+			position  = vec3(0, 0, -2),
+			direction = vec3(0, 0,  1)
+		}
+		local b = {
+			position  = vec3(0, 0, -2),
+			direction = vec3(0, 0, -1)
+		}
+		local c = {
+			position = vec3(),
+			radius   = 1
+		}
+
+		local w, x = intersect.ray_sphere(a, c)
+		local y, z = intersect.ray_sphere(b, c)
+		assert.is_true(vec3.is_vec3(w))
+		assert.is_not_true(y)
+	end)
+
+	it("intersects a ray with an aabb", function()
+		local a = {
+			position  = vec3(0, 0, -2),
+			direction = vec3(0, 0,  1)
+		}
+		local b = {
+			position  = vec3(0, 0, -2),
+			direction = vec3(0, 0, -1)
+		}
+		local c = {
+			min = vec3(-1),
+			max = vec3( 1)
+		}
+
+		local w, x = intersect.ray_aabb(a, c)
+		local y, z = intersect.ray_aabb(b, c)
+		assert.is_true(vec3.is_vec3(w))
+		assert.is_not_true(y)
+	end)
+
+	it("intersects a ray with a plane", function()
+		local a = {
+			position  = vec3(0, 0,  1),
+			direction = vec3(0, 0, -1)
+		}
+		local b = {
+			position  = vec3(0, 0, 1),
+			direction = vec3(0, 0, 1)
+		}
+		local c = {
+			position = vec3(),
+			normal   = vec3(0, 0, 1)
+		}
+
+		local w, x = intersect.ray_plane(a, c)
+		local y, z = intersect.ray_plane(b, c)
+		assert.is_true(vec3.is_vec3(w))
+		assert.is_not_true(y)
+	end)
+
+	it("intersects a line with a line", function()
+		local a = {
+			vec3(0, 0, -1),
+			vec3(0, 0,  1)
+		}
+		local b = {
+			vec3(0, 0, -1),
+			vec3(0, 1, -1)
+		}
+		local c = {
+			vec3(-1, 0, 0),
+			vec3( 1, 0, 0)
+		}
+
+		local w, x = intersect.line_line(a, c, 0.001)
+		local y, z = intersect.line_line(b, c, 0.001)
+		local u, v = intersect.line_line(b, c)
+		assert.is_truthy(w)
+		assert.is_not_truthy(y)
+		assert.is_truthy(u)
+	end)
+
+	it("intersects a segment with a segment", function()
+		local a = {
+			vec3(0, 0, -1),
+			vec3(0, 0,  1)
+		}
+		local b = {
+			vec3(0, 0, -1),
+			vec3(0, 1, -1)
+		}
+		local c = {
+			vec3(-1, 0, 0),
+			vec3( 1, 0, 0)
+		}
+
+		local w, x = intersect.segment_segment(a, c, 0.001)
+		local y, z = intersect.segment_segment(b, c, 0.001)
+		local u, v = intersect.segment_segment(b, c)
+		assert.is_truthy(w)
+		assert.is_not_truthy(y)
+		assert.is_truthy(u)
+	end)
+
+	it("intersects an aabb with an aabb", function()
+		local a = {
+			min = vec3(-1),
+			max = vec3( 1)
+		}
+		local b = {
+			min = vec3(-5),
+			max = vec3(-3)
+		}
+		local c = {
+			min = vec3(),
+			max = vec3(2)
+		}
+		assert.is_true(intersect.aabb_aabb(a, c))
+		assert.is_not_true(intersect.aabb_aabb(b, c))
+	end)
+
+	it("intersects an aabb with an obb", function()
+		local r = mat4():rotate(mat4(), math.pi / 4, vec3.unit_z)
+
+		local a = {
+			position = vec3(),
+			extent   = vec3(0.5)
+		}
+		local b = {
+			position = vec3(),
+			extent   = vec3(0.5),
+			rotation = r
+		}
+		local c = {
+			position = vec3(0, 0, 2),
+			extent   = vec3(0.5),
+			rotation = r
+		}
+		assert.is_true(vec3.is_vec3(intersect.aabb_obb(a, b)))
+		assert.is_not_true(intersect.aabb_obb(a, c))
+	end)
+
+	it("intersects an aabb with a sphere", function()
+		local a = {
+			min = vec3(-1),
+			max = vec3( 1)
+		}
+		local b = {
+			min = vec3(-5),
+			max = vec3(-3)
+		}
+		local c = {
+			position = vec3(0, 0, 3),
+			radius   = 3
+		}
+		assert.is_true(intersect.aabb_sphere(a, c))
+		assert.is_not_true(intersect.aabb_sphere(b, c))
+	end)
+
+	it("intersects an aabb with a frustum", function()
+		pending("TODO")
+	end)
+
+	it("encapsulates an aabb", function()
+		local a = {
+			min = vec3(-1),
+			max = vec3( 1)
+		}
+		local b = {
+			min = vec3(-1.5),
+			max = vec3( 1.5)
+		}
+		local c = {
+			min = vec3(-0.5),
+			max = vec3( 0.5)
+		}
+		local d = {
+			min = vec3(-1),
+			max = vec3( 1)
+		}
+		assert.is_true(intersect.encapsulate_aabb(a, d))
+		assert.is_true(intersect.encapsulate_aabb(b, d))
+		assert.is_not_true(intersect.encapsulate_aabb(c, d))
+	end)
+
+	it("intersects a circle with a circle", function()
+		local a = {
+			position = vec3(0, 0, 6),
+			radius   = 3
+		}
+		local b = {
+			position = vec3(0, 0, 7),
+			radius   = 3
+		}
+		local c = {
+			position = vec3(),
+			radius   = 3
+		}
+		assert.is_true(intersect.circle_circle(a, c))
+		assert.is_not_true(intersect.circle_circle(b, c))
+	end)
+
+	it("intersects a sphere with a sphere", function()
+		local a = {
+			position = vec3(0, 0, 6),
+			radius   = 3
+		}
+		local b = {
+			position = vec3(0, 0, 7),
+			radius   = 3
+		}
+		local c = {
+			position = vec3(),
+			radius   = 3
+		}
+		assert.is_true(intersect.sphere_sphere(a, c))
+		assert.is_not_true(intersect.sphere_sphere(b, c))
+	end)
+
+	it("intersects a sphere with a frustum", function()
+		pending("TODO")
+	end)
+
+	it("intersects a capsule with another capsule", function()
+		pending("TODO")
+	end)
+end)

+ 429 - 0
lua_modules/cpml/spec/mat4_spec.lua

@@ -0,0 +1,429 @@
+local mat4  = require "modules.mat4"
+local vec3  = require "modules.vec3"
+local utils = require "modules.utils"
+
+describe("mat4:", function()
+	it("creates an identity matrix", function()
+		local a = mat4()
+		assert.is.equal(1, a[1])
+		assert.is.equal(0, a[2])
+		assert.is.equal(0, a[3])
+		assert.is.equal(0, a[4])
+		assert.is.equal(0, a[5])
+		assert.is.equal(1, a[6])
+		assert.is.equal(0, a[7])
+		assert.is.equal(0, a[8])
+		assert.is.equal(0, a[9])
+		assert.is.equal(0, a[10])
+		assert.is.equal(1, a[11])
+		assert.is.equal(0, a[12])
+		assert.is.equal(0, a[13])
+		assert.is.equal(0, a[14])
+		assert.is.equal(0, a[15])
+		assert.is.equal(1, a[16])
+		assert.is_true(a:is_mat4())
+	end)
+
+	it("creates a filled matrix", function()
+		local a = mat4 {
+			3, 3, 3, 3,
+			4, 4, 4, 4,
+			5, 5, 5, 5,
+			6, 6, 6, 6
+		}
+		assert.is.equal(3, a[1])
+		assert.is.equal(3, a[2])
+		assert.is.equal(3, a[3])
+		assert.is.equal(3, a[4])
+		assert.is.equal(4, a[5])
+		assert.is.equal(4, a[6])
+		assert.is.equal(4, a[7])
+		assert.is.equal(4, a[8])
+		assert.is.equal(5, a[9])
+		assert.is.equal(5, a[10])
+		assert.is.equal(5, a[11])
+		assert.is.equal(5, a[12])
+		assert.is.equal(6, a[13])
+		assert.is.equal(6, a[14])
+		assert.is.equal(6, a[15])
+		assert.is.equal(6, a[16])
+	end)
+
+	it("creates a filled matrix from vec4s", function()
+		local a = mat4 {
+			{ 3, 3, 3, 3 },
+			{ 4, 4, 4, 4 },
+			{ 5, 5, 5, 5 },
+			{ 6, 6, 6, 6 }
+		}
+		assert.is.equal(3, a[1])
+		assert.is.equal(3, a[2])
+		assert.is.equal(3, a[3])
+		assert.is.equal(3, a[4])
+		assert.is.equal(4, a[5])
+		assert.is.equal(4, a[6])
+		assert.is.equal(4, a[7])
+		assert.is.equal(4, a[8])
+		assert.is.equal(5, a[9])
+		assert.is.equal(5, a[10])
+		assert.is.equal(5, a[11])
+		assert.is.equal(5, a[12])
+		assert.is.equal(6, a[13])
+		assert.is.equal(6, a[14])
+		assert.is.equal(6, a[15])
+		assert.is.equal(6, a[16])
+	end)
+
+	it("creates a filled matrix from a 3x3 matrix", function()
+		local a = mat4 {
+			3, 3, 3,
+			4, 4, 4,
+			5, 5, 5
+		}
+		assert.is.equal(3, a[1])
+		assert.is.equal(3, a[2])
+		assert.is.equal(3, a[3])
+		assert.is.equal(0, a[4])
+		assert.is.equal(4, a[5])
+		assert.is.equal(4, a[6])
+		assert.is.equal(4, a[7])
+		assert.is.equal(0, a[8])
+		assert.is.equal(5, a[9])
+		assert.is.equal(5, a[10])
+		assert.is.equal(5, a[11])
+		assert.is.equal(0, a[12])
+		assert.is.equal(0, a[13])
+		assert.is.equal(0, a[14])
+		assert.is.equal(0, a[15])
+		assert.is.equal(1, a[16])
+	end)
+
+	it("creates a matrix from perspective", function()
+		local a = mat4.from_perspective(45, 1, 0.1, 1000)
+		assert.is_true(utils.tolerance( 2.414-a[1],  0.001))
+		assert.is_true(utils.tolerance( 2.414-a[6],  0.001))
+		assert.is_true(utils.tolerance(-1    -a[11], 0.001))
+		assert.is_true(utils.tolerance(-1    -a[12], 0.001))
+		assert.is_true(utils.tolerance(-0.2  -a[15], 0.001))
+	end)
+
+	it("creates a matrix from HMD perspective", function()
+		local t = {
+			LeftTan  = 2.3465312,
+			RightTan = 0.9616399,
+			UpTan    = 2.8664987,
+			DownTan  = 2.8664987
+		}
+		local a = mat4.from_hmd_perspective(t, 0.1, 1000, false, false)
+		assert.is_true(utils.tolerance(a[1] -  0.605, 0.001))
+		assert.is_true(utils.tolerance(a[6] -  0.349, 0.001))
+		assert.is_true(utils.tolerance(a[9] - -0.419, 0.001))
+		assert.is_true(utils.tolerance(a[11]- -1.000, 0.001))
+		assert.is_true(utils.tolerance(a[12]- -1.000, 0.001))
+		assert.is_true(utils.tolerance(a[15]- -0.200, 0.001))
+	end)
+
+	it("clones a matrix", function()
+		local a = mat4.identity()
+		local b = a:clone()
+		assert.is.equal(a, b)
+	end)
+
+	it("multiplies two 4x4 matrices", function()
+		local a = mat4 {
+			1,  2,  3,  4,
+			5,  6,  7,  8,
+			9,  10, 11, 12,
+			13, 14, 15, 16
+		}
+		local b = mat4 {
+			1, 5, 9,  13,
+			2, 6, 10, 14,
+			3, 7, 11, 15,
+			4, 8, 12, 16
+		}
+		local c = mat4():mul(a, b)
+		local d = a * b
+		assert.is.equal(30,  c[1])
+		assert.is.equal(70,  c[2])
+		assert.is.equal(110, c[3])
+		assert.is.equal(150, c[4])
+		assert.is.equal(70,  c[5])
+		assert.is.equal(174, c[6])
+		assert.is.equal(278, c[7])
+		assert.is.equal(382, c[8])
+		assert.is.equal(110, c[9])
+		assert.is.equal(278, c[10])
+		assert.is.equal(446, c[11])
+		assert.is.equal(614, c[12])
+		assert.is.equal(150, c[13])
+		assert.is.equal(382, c[14])
+		assert.is.equal(614, c[15])
+		assert.is.equal(846, c[16])
+		assert.is.equal(c, d)
+	end)
+
+	it("multiplies a matrix and a vec4", function()
+		local a = mat4 {
+			1,  2,  3,  4,
+			5,  6,  7,  8,
+			9,  10, 11, 12,
+			13, 14, 15, 16
+		}
+		local b = { 10, 20, 30, 40 }
+		local c = mat4.mul_vec4(mat4(), a, b)
+		local d = a * b
+		assert.is.equal(900,  c[1])
+		assert.is.equal(1000, c[2])
+		assert.is.equal(1100, c[3])
+		assert.is.equal(1200, c[4])
+
+		assert.is.equal(c[1], d[1])
+		assert.is.equal(c[2], d[2])
+		assert.is.equal(c[3], d[3])
+		assert.is.equal(c[4], d[4])
+	end)
+
+	it("scales a matrix", function()
+		local a = mat4():scale(mat4(), vec3(5, 5, 5))
+		assert.is.equal(5, a[1])
+		assert.is.equal(5, a[6])
+		assert.is.equal(5, a[11])
+	end)
+
+	it("rotates a matrix", function()
+		local a = mat4():rotate(mat4(), math.rad(45), vec3.unit_z)
+		assert.is_true(utils.tolerance( 0.7071-a[1], 0.001))
+		assert.is_true(utils.tolerance( 0.7071-a[2], 0.001))
+		assert.is_true(utils.tolerance(-0.7071-a[5], 0.001))
+		assert.is_true(utils.tolerance( 0.7071-a[6], 0.001))
+	end)
+
+	it("translates a matrix", function()
+		local a = mat4():translate(mat4(), vec3(5, 5, 5))
+		assert.is.equal(5, a[13])
+		assert.is.equal(5, a[14])
+		assert.is.equal(5, a[15])
+	end)
+
+	it("inverts a matrix", function()
+		local a = mat4()
+		a = a:rotate(a, math.pi/4, vec3.unit_y)
+		a = a:translate(a, vec3(4, 5, 6))
+
+		local b = mat4.invert(mat4(), a)
+		local c = a * b
+		assert.is.equal(mat4(), c)
+
+		local d = mat4()
+		d:rotate(d, math.pi/4, vec3.unit_y)
+		d:translate(d, vec3(4, 5, 6))
+
+		local e = -d
+		local f = d * e
+		assert.is.equal(mat4(), f)
+	end)
+
+	it("transposes a matrix", function()
+		local a = mat4({
+			1, 1, 1, 1,
+			2, 2, 2, 2,
+			3, 3, 3, 3,
+			4, 4, 4, 4
+		})
+		a = a:transpose(a)
+		assert.is.equal(1, a[1])
+		assert.is.equal(2, a[2])
+		assert.is.equal(3, a[3])
+		assert.is.equal(4, a[4])
+		assert.is.equal(1, a[5])
+		assert.is.equal(2, a[6])
+		assert.is.equal(3, a[7])
+		assert.is.equal(4, a[8])
+		assert.is.equal(1, a[9])
+		assert.is.equal(2, a[10])
+		assert.is.equal(3, a[11])
+		assert.is.equal(4, a[12])
+		assert.is.equal(1, a[13])
+		assert.is.equal(2, a[14])
+		assert.is.equal(3, a[15])
+		assert.is.equal(4, a[16])
+	end)
+
+	it("shears a matrix", function()
+		local yx, zx, xy, zy, xz, yz = 1, 1, 1, -1, -1, -1
+		local a = mat4():shear(mat4(), yx, zx, xy, zy, xz, yz)
+		assert.is.equal( 1, a[2])
+		assert.is.equal( 1, a[3])
+		assert.is.equal( 1, a[5])
+		assert.is.equal(-1, a[7])
+		assert.is.equal(-1, a[9])
+		assert.is.equal(-1, a[10])
+	end)
+
+	it("reflects a matrix along a plane", function()
+		local origin = vec3(5, 1, 0)
+		local normal = vec3(0, -1, 0):normalize()
+		local a = mat4():reflect(mat4(), origin, normal)
+		local p = a * vec3(-5, 2, 5)
+		assert.is.equal(p.x, -5)
+		assert.is.equal(p.y, 0)
+		assert.is.equal(p.z, 5)
+	end)
+
+	it("projects a matrix into screen space", function()
+		local v  = vec3(0, 0, 10)
+		local a  = mat4()
+		local b  = mat4.from_perspective(45, 1, 0.1, 1000)
+		local vp = { 0, 0, 400, 400 }
+		local c  = mat4.project(v, a, b, vp)
+		assert.is.equal(200, c.x)
+		assert.is.equal(200, c.y)
+		assert.is_true(utils.tolerance(1.0101-c.z, 0.001))
+	end)
+
+	it("unprojects a matrix into world space", function()
+		local v  = vec3(0, 0, 10)
+		local a  = mat4()
+		local b  = mat4.from_perspective(45, 1, 0.1, 1000)
+		local vp = { 0, 0, 400, 400 }
+		local c  = mat4.project(v, a, b, vp)
+		local d  = mat4.unproject(c, a, b, vp)
+		assert.is_true(utils.tolerance(v.x-d.x, 0.001))
+		assert.is_true(utils.tolerance(v.y-d.y, 0.001))
+		assert.is_true(utils.tolerance(v.z-d.z, 0.001))
+	end)
+
+	it("transforms a matrix to look at a point", function()
+		local e = vec3(0, 0, 1.55)
+		local c = vec3(4, 7, 1)
+		local u = vec3(0, 0, 1)
+		local a = mat4():look_at(mat4(), e, c, u)
+
+		assert.is_true(utils.tolerance( 0.868-a[1], 0.001))
+		assert.is_true(utils.tolerance( 0.034-a[2], 0.001))
+		assert.is_true(utils.tolerance(-0.495-a[3], 0.001))
+		assert.is_true(utils.tolerance( 0    -a[4], 0.001))
+
+		assert.is_true(utils.tolerance(-0.496-a[5], 0.001))
+		assert.is_true(utils.tolerance( 0.059-a[6], 0.001))
+		assert.is_true(utils.tolerance(-0.866-a[7], 0.001))
+		assert.is_true(utils.tolerance( 0    -a[8], 0.001))
+
+		assert.is_true(utils.tolerance( 0    -a[9],  0.001))
+		assert.is_true(utils.tolerance( 0.998-a[10], 0.001))
+		assert.is_true(utils.tolerance( 0.068-a[11], 0.001))
+		assert.is_true(utils.tolerance( 0    -a[12], 0.001))
+
+		assert.is_true(utils.tolerance( 0    -a[13], 0.001))
+		assert.is_true(utils.tolerance(-1.546-a[14], 0.001))
+		assert.is_true(utils.tolerance(-0.106-a[15], 0.001))
+		assert.is_true(utils.tolerance( 1    -a[16], 0.001))
+	end)
+
+	it("converts a matrix to vec4s", function()
+		local a = mat4()
+		local v = a:to_vec4s()
+		assert.is_true(type(v)    == "table")
+		assert.is_true(type(v[1]) == "table")
+		assert.is_true(type(v[2]) == "table")
+		assert.is_true(type(v[3]) == "table")
+		assert.is_true(type(v[4]) == "table")
+
+		assert.is.equal(1, v[1][1])
+		assert.is.equal(0, v[1][2])
+		assert.is.equal(0, v[1][3])
+		assert.is.equal(0, v[1][4])
+
+		assert.is.equal(0, v[2][1])
+		assert.is.equal(1, v[2][2])
+		assert.is.equal(0, v[2][3])
+		assert.is.equal(0, v[2][4])
+
+		assert.is.equal(0, v[3][1])
+		assert.is.equal(0, v[3][2])
+		assert.is.equal(1, v[3][3])
+		assert.is.equal(0, v[3][4])
+
+		assert.is.equal(0, v[4][1])
+		assert.is.equal(0, v[4][2])
+		assert.is.equal(0, v[4][3])
+		assert.is.equal(1, v[4][4])
+	end)
+
+	it("converts a matrix to a quaternion", function()
+		local q = mat4({
+			0, 0, 1, 0,
+			1, 0, 0, 0,
+			0, 1, 0, 0,
+			0, 0, 0, 0
+		}):to_quat()
+		assert.is.equal(-0.5, q.x)
+		assert.is.equal(-0.5, q.y)
+		assert.is.equal(-0.5, q.z)
+		assert.is.equal( 0.5, q.w)
+	end)
+
+	it("converts a matrix to a frustum", function()
+		local a = mat4()
+		local b = mat4.from_perspective(45, 1, 0.1, 1000)
+		local f = (b * a):to_frustum()
+
+		assert.is_true(utils.tolerance( 0.9239-f.left.a, 0.001))
+		assert.is_true(utils.tolerance( 0     -f.left.b, 0.001))
+		assert.is_true(utils.tolerance(-0.3827-f.left.c, 0.001))
+		assert.is_true(utils.tolerance( 0     -f.left.d, 0.001))
+
+		assert.is_true(utils.tolerance(-0.9239-f.right.a, 0.001))
+		assert.is_true(utils.tolerance( 0     -f.right.b, 0.001))
+		assert.is_true(utils.tolerance(-0.3827-f.right.c, 0.001))
+		assert.is_true(utils.tolerance( 0     -f.right.d, 0.001))
+
+		assert.is_true(utils.tolerance( 0     -f.bottom.a, 0.001))
+		assert.is_true(utils.tolerance( 0.9239-f.bottom.b, 0.001))
+		assert.is_true(utils.tolerance(-0.3827-f.bottom.c, 0.001))
+		assert.is_true(utils.tolerance( 0     -f.bottom.d, 0.001))
+
+		assert.is_true(utils.tolerance( 0     -f.top.a, 0.001))
+		assert.is_true(utils.tolerance(-0.9239-f.top.b, 0.001))
+		assert.is_true(utils.tolerance(-0.3827-f.top.c, 0.001))
+		assert.is_true(utils.tolerance( 0     -f.top.d, 0.001))
+
+		assert.is_true(utils.tolerance( 0  -f.near.a, 0.001))
+		assert.is_true(utils.tolerance( 0  -f.near.b, 0.001))
+		assert.is_true(utils.tolerance(-1  -f.near.c, 0.001))
+		assert.is_true(utils.tolerance(-0.1-f.near.d, 0.001))
+
+		assert.is_true(utils.tolerance( 0   -f.far.a, 0.001))
+		assert.is_true(utils.tolerance( 0   -f.far.b, 0.001))
+		assert.is_true(utils.tolerance( 1   -f.far.c, 0.001))
+		assert.is_true(utils.tolerance( 1000-f.far.d, 0.001))
+	end)
+
+	it("checks to see if data is a valid matrix (not a table)", function()
+		assert.is_not_true(mat4.is_mat4(0))
+	end)
+
+	it("checks to see if data is a valid matrix (invalid data)", function()
+		assert.is_not_true(mat4.is_mat4({}))
+	end)
+
+	it("gets a string representation of a matrix", function()
+		local a = mat4():to_string()
+		local z = "+0.000"
+		local o = "+1.000"
+		local s = string.format(
+			"[ %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s ]",
+			o, z, z, z, z, o, z, z, z, z, o, z, z, z ,z, o
+		)
+		assert.is.equal(s, a)
+	end)
+end)
+
+--[[
+	from_angle_axis
+	from_quaternion
+	from_direction
+	from_transform
+	from_ortho
+--]]

+ 12 - 0
lua_modules/cpml/spec/mesh_spec.lua

@@ -0,0 +1,12 @@
+local mesh = require "modules.mesh"
+
+describe("mesh:", function()
+end)
+
+--[[
+average(vertices)
+normal(triangle)
+plane_from_triangle(triangle)
+is_front_facing(plane, direction)
+signed_distance(point, plane)
+--]]

+ 34 - 0
lua_modules/cpml/spec/octree_spec.lua

@@ -0,0 +1,34 @@
+local octree = require "modules.octree"
+
+describe("octree:", function()
+end)
+
+--[[
+local function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)
+function Octree:add(obj, objBounds)
+function Octree:remove(obj)
+function Octree:is_colliding(checkBounds)
+function Octree:get_colliding(checkBounds)
+function Octree:cast_ray(ray, func, out)
+function Octree:draw_bounds(cube)
+function Octree:draw_objects(cube, filter)
+function Octree:grow(direction)
+function Octree:shrink()
+function Octree:get_root_pos_index(xDir, yDir, zDir)
+
+function OctreeNode:add(obj, objBounds)
+function OctreeNode:remove(obj)
+function OctreeNode:is_colliding(checkBounds)
+function OctreeNode:get_colliding(checkBounds, results)
+function OctreeNode:cast_ray(ray, func, out, depth)
+function OctreeNode:set_children(childOctrees)
+function OctreeNode:shrink_if_possible(minLength)
+function OctreeNode:set_values(baseLength, minSize, looseness, center)
+function OctreeNode:split()
+function OctreeNode:merge()
+function OctreeNode:best_fit_child(objBounds)
+function OctreeNode:should_merge()
+function OctreeNode:has_any_objects()
+function OctreeNode:draw_bounds(cube, depth)
+function OctreeNode:draw_objects(cube, filter)
+--]]

+ 317 - 0
lua_modules/cpml/spec/quat_spec.lua

@@ -0,0 +1,317 @@
+local quat  = require "modules.quat"
+local vec3  = require "modules.vec3"
+local utils = require "modules.utils"
+
+describe("quat:", function()
+	it("creates an identity quaternion", function()
+		local a = quat()
+		assert.is.equal(0, a.x)
+		assert.is.equal(0, a.y)
+		assert.is.equal(0, a.z)
+		assert.is.equal(1, a.w)
+		assert.is_true(a:is_quat())
+		assert.is_true(a:is_real())
+	end)
+
+	it("creates a quaternion from numbers", function()
+		local a = quat(0, 0, 0, 0)
+		assert.is.equal(0, a.x)
+		assert.is.equal(0, a.y)
+		assert.is.equal(0, a.z)
+		assert.is.equal(0, a.w)
+		assert.is_true(a:is_zero())
+		assert.is_true(a:is_imaginary())
+	end)
+
+	it("creates a quaternion from a list", function()
+		local a = quat { 2, 3, 4, 1 }
+		assert.is.equal(2, a.x)
+		assert.is.equal(3, a.y)
+		assert.is.equal(4, a.z)
+		assert.is.equal(1, a.w)
+	end)
+
+	it("creates a quaternion from a record", function()
+		local a = quat { x=2, y=3, z=4, w=1 }
+		assert.is.equal(2, a.x)
+		assert.is.equal(3, a.y)
+		assert.is.equal(4, a.z)
+		assert.is.equal(1, a.w)
+	end)
+
+	it("creates a quaternion from a direction", function()
+		local v = vec3(-80, 80, -80):normalize()
+		local a = quat.from_direction(v, vec3.unit_z)
+		assert.is_true(utils.tolerance(-0.577-a.x, 0.001))
+		assert.is_true(utils.tolerance(-0.577-a.y, 0.001))
+		assert.is_true(utils.tolerance( 0    -a.z, 0.001))
+		assert.is_true(utils.tolerance( 0.423-a.w, 0.001))
+	end)
+
+	it("clones a quaternion", function()
+		local a = quat()
+		local b = a:clone()
+		assert.is.equal(a.x, b.x)
+		assert.is.equal(a.y, b.y)
+		assert.is.equal(a.z, b.z)
+		assert.is.equal(a.w, b.w)
+	end)
+
+	it("adds a quaternion to another", function()
+		local a = quat(2, 3, 4, 1)
+		local b = quat(3, 6, 9, 1)
+		local c = a:add(b)
+		local d = a + b
+		assert.is.equal(5,  c.x)
+		assert.is.equal(9,  c.y)
+		assert.is.equal(13, c.z)
+		assert.is.equal(2,  c.w)
+		assert.is.equal(c,  d)
+	end)
+
+	it("subtracts a quaternion from another", function()
+		local a = quat(2, 3, 4, 1)
+		local b = quat(3, 6, 9, 1)
+		local c = a:sub(b)
+		local d = a - b
+		assert.is.equal(-1, c.x)
+		assert.is.equal(-3, c.y)
+		assert.is.equal(-5, c.z)
+		assert.is.equal( 0, c.w)
+		assert.is.equal(c,  d)
+	end)
+
+	it("multiplies a quaternion by another", function()
+		local a = quat(2, 3, 4, 1)
+		local b = quat(3, 6, 9, 1)
+		local c = a:mul(b)
+		local d = a * b
+		assert.is.equal( 8,  c.x)
+		assert.is.equal( 3,  c.y)
+		assert.is.equal( 16, c.z)
+		assert.is.equal(-59, c.w)
+		assert.is.equal(c,   d)
+	end)
+
+	it("multiplies a quaternion by a scale factor", function()
+		local a = quat(2, 3, 4, 1)
+		local s = 3
+		local b = a:scale(s)
+		local c = a * s
+		assert.is.equal(6,  b.x)
+		assert.is.equal(9,  b.y)
+		assert.is.equal(12, b.z)
+		assert.is.equal(3,  b.w)
+		assert.is.equal(b,  c)
+	end)
+
+	it("inverts a quaternion", function()
+		local a = quat(2, 3, 4, 1)
+		local b = -a
+		assert.is.equal(-a.x, b.x)
+		assert.is.equal(-a.y, b.y)
+		assert.is.equal(-a.z, b.z)
+		assert.is.equal(-a.w, b.w)
+	end)
+
+	it("multiplies a quaternion by a vec3", function()
+		local a = quat(2, 3, 4, 1)
+		local v = vec3(3, 4, 5)
+		local b = a:mul_vec3(v)
+		local c = a * v
+		assert.is.equal(-21, c.x)
+		assert.is.equal( 4,  c.y)
+		assert.is.equal( 17, c.z)
+		assert.is.equal(b, c)
+	end)
+
+	it("multiplies a quaternion by an exponent of 0", function()
+		local a = quat(2, 3, 4, 1):normalize()
+		local e = 0
+		local b = a:pow(e)
+		local c = a^e
+
+		assert.is.equal(0, b.x)
+		assert.is.equal(0, b.y)
+		assert.is.equal(0, b.z)
+		assert.is.equal(1, b.w)
+		assert.is.equal(b, c)
+	end)
+
+	it("multiplies a quaternion by a positive exponent", function()
+		local a = quat(2, 3, 4, 1):normalize()
+		local e = 0.75
+		local b = a:pow(e)
+		local c = a^e
+
+		assert.is_true(utils.tolerance(-0.3204+b.x, 0.0001))
+		assert.is_true(utils.tolerance(-0.4805+b.y, 0.0001))
+		assert.is_true(utils.tolerance(-0.6407+b.z, 0.0001))
+		assert.is_true(utils.tolerance(-0.5059+b.w, 0.0001))
+		assert.is.equal( b,  c)
+	end)
+
+	it("multiplies a quaternion by a negative exponent", function()
+		local a = quat(2, 3, 4, 1):normalize()
+		local e = -1
+		local b = a:pow(e)
+		local c = a^e
+
+		assert.is_true(utils.tolerance( 0.3651+b.x, 0.0001))
+		assert.is_true(utils.tolerance( 0.5477+b.y, 0.0001))
+		assert.is_true(utils.tolerance( 0.7303+b.z, 0.0001))
+		assert.is_true(utils.tolerance(-0.1826+b.w, 0.0001))
+		assert.is.equal(b, c)
+	end)
+
+	it("inverts a quaternion", function()
+		local a = quat(1, 1, 1, 1):inverse()
+		assert.is.equal(-0.5, a.x)
+		assert.is.equal(-0.5, a.y)
+		assert.is.equal(-0.5, a.z)
+		assert.is.equal( 0.5, a.w)
+	end)
+
+	it("normalizes a quaternion", function()
+		local a = quat(1, 1, 1, 1):normalize()
+		assert.is.equal(0.5, a.x)
+		assert.is.equal(0.5, a.y)
+		assert.is.equal(0.5, a.z)
+		assert.is.equal(0.5, a.w)
+	end)
+
+	it("dots two quaternions", function()
+		local a = quat(1, 1, 1, 1)
+		local b = quat(4, 4, 4, 4)
+		local c = a:dot(b)
+		assert.is.equal(16, c)
+	end)
+
+	it("dots two quaternions (negative)", function()
+		local a = quat(-1, 1, 1, 1)
+		local b = quat(4, 4, 4, 4)
+		local c = a:dot(b)
+		assert.is.equal(8, c)
+	end)
+
+	it("dots two quaternions (tiny)", function()
+		local a = quat(0.1, 0.1, 0.1, 0.1)
+		local b = quat(0.4, 0.4, 0.4, 0.4)
+		local c = a:dot(b)
+		assert.is_true(utils.tolerance(0.16-c, 0.001))
+	end)
+
+	it("gets the length of a quaternion", function()
+		local a = quat(2, 3, 4, 5):len()
+		assert.is.equal(math.sqrt(54), a)
+	end)
+
+	it("gets the square length of a quaternion", function()
+		local a = quat(2, 3, 4, 5):len2()
+		assert.is.equal(54, a)
+	end)
+
+	it("interpolates between two quaternions", function()
+		local a = quat(3, 3, 3, 3)
+		local b = quat(6, 6, 6, 6)
+		local s = 0.1
+		local c = a:lerp(b, s)
+		assert.is.equal(0.5, c.x)
+		assert.is.equal(0.5, c.y)
+		assert.is.equal(0.5, c.z)
+		assert.is.equal(0.5, c.w)
+	end)
+
+	it("interpolates between two quaternions (spherical)", function()
+		local a = quat(3, 3, 3, 3)
+		local b = quat(6, 6, 6, 6)
+		local s = 0.1
+		local c = a:slerp(b, s)
+		assert.is.equal(0.5, c.x)
+		assert.is.equal(0.5, c.y)
+		assert.is.equal(0.5, c.z)
+		assert.is.equal(0.5, c.w)
+	end)
+
+	it("unpacks a quaternion", function()
+		local x, y, z, w = quat(2, 3, 4, 1):unpack()
+		assert.is.equal(2, x)
+		assert.is.equal(3, y)
+		assert.is.equal(4, z)
+		assert.is.equal(1, w)
+	end)
+
+	it("converts quaternion to a vec3", function()
+		local v = quat(2, 3, 4, 1):to_vec3()
+		assert.is.equal(2, v.x)
+		assert.is.equal(3, v.y)
+		assert.is.equal(4, v.z)
+	end)
+
+	it("gets the conjugate quaternion", function()
+		local a = quat(2, 3, 4, 1):conjugate()
+		assert.is.equal(-2, a.x)
+		assert.is.equal(-3, a.y)
+		assert.is.equal(-4, a.z)
+		assert.is.equal( 1, a.w)
+	end)
+
+	it("gets the reciprocal quaternion", function()
+		local a = quat(1, 1, 1, 1)
+		local b = a:reciprocal()
+		local c = b:reciprocal()
+
+		assert.is_not.equal(a.x, b.x)
+		assert.is_not.equal(a.y, b.y)
+		assert.is_not.equal(a.z, b.z)
+		assert.is_not.equal(a.w, b.w)
+
+		assert.is.equal(a.x, c.x)
+		assert.is.equal(a.y, c.y)
+		assert.is.equal(a.z, c.z)
+		assert.is.equal(a.w, c.w)
+	end)
+
+	it("converts between a quaternion and angle/axis", function()
+		local a = quat.from_angle_axis(math.pi, vec3.unit_z)
+		local angle, axis = a:to_angle_axis()
+		assert.is.equal(math.pi,     angle)
+		assert.is.equal(vec3.unit_z, axis)
+	end)
+
+	it("converts between a quaternion and angle/axis (specify by component)", function()
+		local a = quat.from_angle_axis(math.pi, vec3.unit_z.x, vec3.unit_z.y, vec3.unit_z.z)
+		local angle, axis = a:to_angle_axis()
+		assert.is.equal(math.pi,     angle)
+		assert.is.equal(vec3.unit_z, axis)
+	end)
+
+	it("converts between a quaternion and angle/axis (w=2)", function()
+		local angle, axis = quat(1, 1, 1, 2):to_angle_axis()
+		assert.is_true(utils.tolerance(1.427-angle,  0.001))
+		assert.is_true(utils.tolerance(0.577-axis.x, 0.001))
+		assert.is_true(utils.tolerance(0.577-axis.y, 0.001))
+		assert.is_true(utils.tolerance(0.577-axis.z, 0.001))
+	end)
+
+	it("converts between a quaternion and angle/axis (w=2) (by component)", function()
+		local angle, x,y,z = quat(1, 1, 1, 2):to_angle_axis_unpack()
+		assert.is_true(utils.tolerance(1.427-angle,  0.001))
+		assert.is_true(utils.tolerance(0.577-x, 0.001))
+		assert.is_true(utils.tolerance(0.577-y, 0.001))
+		assert.is_true(utils.tolerance(0.577-z, 0.001))
+	end)
+
+	it("converts between a quaternion and angle/axis (w=1)", function()
+		local angle, axis = quat(1, 2, 3, 1):to_angle_axis()
+		assert.is.equal(0, angle)
+		assert.is.equal(1, axis.x)
+		assert.is.equal(2, axis.y)
+		assert.is.equal(3, axis.z)
+	end)
+
+	it("gets a string representation of a quaternion", function()
+		local a = quat():to_string()
+		assert.is.equal("(+0.000,+0.000,+0.000,+1.000)", a)
+	end)
+end)

+ 50 - 0
lua_modules/cpml/spec/utils_spec.lua

@@ -0,0 +1,50 @@
+local vec3      = require "modules.vec3"
+local utils     = require "modules.utils"
+local constants = require "modules.constants"
+
+local function tolerance(v, t)
+	return math.abs(v - t) < 1e-6
+end
+
+describe("utils:", function()
+	it("interpolates between two numbers", function()
+		assert.is_true(tolerance(utils.lerp(0, 1, 0.5), 0.5))
+	end)
+
+	it("interpolates between two vectors", function()
+		local a = vec3(0, 0, 0)
+		local b = vec3(1, 1, 1)
+		local c = vec3(0.5, 0.5, 0.5)
+		assert.is.equal(utils.lerp(a, b, 0.5), c)
+
+		a = vec3(5, 5, 5)
+		b = vec3(0, 0, 0)
+		c = vec3(2.5, 2.5, 2.5)
+		assert.is.equal(utils.lerp(a, b, 0.5), c)
+	end)
+
+	it("decays exponentially", function()
+		local v = utils.decay(0, 1, 0.5, 1)
+		assert.is_true(tolerance(v, 0.39346934028737))
+	end)
+end)
+
+--[[
+clamp(value, min, max)
+deadzone(value, size)
+threshold(value, threshold)
+tolerance(value, threshold)
+map(value, min_in, max_in, min_out, max_out)
+lerp(progress, low, high)
+smoothstep(progress, low, high)
+round(value, precision)
+wrap(value, limit)
+is_pot(value)
+project_on(out, a, b)
+project_from(out, a, b)
+mirror_on(out, a, b)
+reflect(out, i, n)
+refract(out, i, n, ior)
+angle_to(a, b)
+angle_between(a, b)
+--]]

+ 185 - 0
lua_modules/cpml/spec/vec2_spec.lua

@@ -0,0 +1,185 @@
+local vec2        = require "modules.vec2"
+local DBL_EPSILON = require("modules.constants").DBL_EPSILON
+local abs, sqrt   = math.abs, math.sqrt
+
+describe("vec2:", function()
+	it("creates an empty vector", function()
+		local a = vec2()
+		assert.is.equal(0, a.x)
+		assert.is.equal(0, a.y)
+		assert.is_true(a:is_vec2())
+		assert.is_true(a:is_zero())
+	end)
+
+	it("creates a vector from a number", function()
+		local a = vec2(3)
+		assert.is.equal(3, a.x)
+		assert.is.equal(3, a.y)
+	end)
+
+	it("creates a vector from numbers", function()
+		local a = vec2(3, 5)
+		assert.is.equal(3, a.x)
+		assert.is.equal(5, a.y)
+	end)
+
+	it("creates a vector from a list", function()
+		local a = vec2 { 3, 5 }
+		assert.is.equal(3, a.x)
+		assert.is.equal(5, a.y)
+	end)
+
+	it("creates a vector from a record", function()
+		local a = vec2 { x=3, y=5 }
+		assert.is.equal(3, a.x)
+		assert.is.equal(5, a.y)
+	end)
+
+	it("clones a vector", function()
+		local a = vec2(3, 5)
+		local b = a:clone()
+		assert.is.equal(a, b)
+	end)
+
+	it("adds a vector to another", function()
+		local a = vec2(3, 5)
+		local b = vec2(7, 4)
+		local c = a:add(b)
+		local d = a + b
+		assert.is.equal(10, c.x)
+		assert.is.equal(9,  c.y)
+		assert.is.equal(c,  d)
+	end)
+
+	it("subracts a vector from another", function()
+		local a = vec2(3, 5)
+		local b = vec2(7, 4)
+		local c = a:sub(b)
+		local d = a - b
+		assert.is.equal(-4, c.x)
+		assert.is.equal( 1, c.y)
+		assert.is.equal( c, d)
+	end)
+
+	it("multiplies a vector by a scale factor", function()
+		local a = vec2(3, 5)
+		local s = 2
+		local c = a:scale(s)
+		local d = a * s
+		assert.is.equal(6,  c.x)
+		assert.is.equal(10, c.y)
+		assert.is.equal(c,  d)
+	end)
+
+	it("divides a vector by another vector", function()
+		local a = vec2(3, 5)
+		local s = vec2(2, 2)
+		local c = a:div(s)
+		local d = a / s
+		assert.is.equal(1.5, c.x)
+		assert.is.equal(2.5, c.y)
+		assert.is.equal(c,   d)
+	end)
+
+	it("inverts a vector", function()
+		local a = vec2(3, -5)
+		local b = -a
+		assert.is.equal(-a.x, b.x)
+		assert.is.equal(-a.y, b.y)
+	end)
+
+	it("gets the length of a vector", function()
+		local a = vec2(3, 5)
+		assert.is.equal(sqrt(34), a:len())
+		end)
+
+	it("gets the square length of a vector", function()
+		local a = vec2(3, 5)
+		assert.is.equal(34, a:len2())
+	end)
+
+	it("normalizes a vector", function()
+		local a = vec2(3, 5)
+		local b = a:normalize()
+		assert.is_true(abs(b:len()-1) < DBL_EPSILON)
+		end)
+
+	it("trims the length of a vector", function()
+		local a = vec2(3, 5)
+		local b = a:trim(0.5)
+		assert.is_true(abs(b:len()-0.5) < DBL_EPSILON)
+	end)
+
+	it("gets the distance between two vectors", function()
+		local a = vec2(3, 5)
+		local b = vec2(7, 4)
+		local c = a:dist(b)
+		assert.is.equal(sqrt(17), c)
+	end)
+
+	it("gets the square distance between two vectors", function()
+		local a = vec2(3, 5)
+		local b = vec2(7, 4)
+		local c = a:dist2(b)
+		assert.is.equal(17, c)
+	end)
+
+	it("crosses two vectors", function()
+		local a = vec2(3, 5)
+		local b = vec2(7, 4)
+		local c = a:cross(b)
+		assert.is.equal(-23, c)
+	end)
+
+	it("dots two vectors", function()
+		local a = vec2(3, 5)
+		local b = vec2(7, 4)
+		local c = a:dot(b)
+		assert.is.equal(41, c)
+	end)
+
+	it("interpolates between two vectors", function()
+		local a = vec2(3, 5)
+		local b = vec2(7, 4)
+		local s = 0.1
+		local c = a:lerp(b, s)
+		assert.is.equal(3.4, c.x)
+		assert.is.equal(4.9, c.y)
+	end)
+
+	it("unpacks a vector", function()
+		local a    = vec2(3, 5)
+		local x, y = a:unpack()
+		assert.is.equal(3, x)
+		assert.is.equal(5, y)
+	end)
+
+	it("rotates a vector", function()
+		local a = vec2(3, 5)
+		local b = a:rotate( math.pi)
+		local c = b:rotate(-math.pi)
+		assert.is_not.equal(a, b)
+		assert.is.equal(a, c)
+	end)
+
+	it("converts between polar and cartesian coordinates", function()
+		local a    = vec2(3, 5)
+		local r, t = a:to_polar()
+		local b    = vec2.from_cartesian(r, t)
+		assert.is.equal(a.x, b.x)
+		assert.is.equal(a.y, b.y)
+	end)
+
+	it("gets a perpendicular vector", function()
+		local a = vec2(3, 5)
+		local b = a:perpendicular()
+		assert.is.equal(-5, b.x)
+		assert.is.equal( 3, b.y)
+	end)
+
+	it("gets a string representation of a vector", function()
+		local a = vec2()
+		local b = a:to_string()
+		assert.is.equal("(+0.000,+0.000)", b)
+	end)
+end)

+ 199 - 0
lua_modules/cpml/spec/vec3_spec.lua

@@ -0,0 +1,199 @@
+local vec3        = require "modules.vec3"
+local DBL_EPSILON = require("modules.constants").DBL_EPSILON
+local abs, sqrt   = math.abs, math.sqrt
+
+describe("vec3:", function()
+	it("creates an empty vector", function()
+		local a = vec3()
+		assert.is.equal(0, a.x)
+		assert.is.equal(0, a.y)
+		assert.is.equal(0, a.z)
+		assert.is_true(a:is_vec3())
+		assert.is_true(a:is_zero())
+	end)
+
+	it("creates a vector from a number", function()
+		local a = vec3(3)
+		assert.is.equal(3, a.x)
+		assert.is.equal(3, a.y)
+		assert.is.equal(3, a.z)
+	end)
+
+	it("creates a vector from numbers", function()
+		local a = vec3(3, 5, 7)
+		assert.is.equal(3, a.x)
+		assert.is.equal(5, a.y)
+		assert.is.equal(7, a.z)
+	end)
+
+	it("creates a vector from a list", function()
+		local a = vec3 { 3, 5, 7 }
+		assert.is.equal(3, a.x)
+		assert.is.equal(5, a.y)
+		assert.is.equal(7, a.z)
+	end)
+
+	it("creates a vector from a record", function()
+		local a = vec3 { x=3, y=5, z=7 }
+		assert.is.equal(3, a.x)
+		assert.is.equal(5, a.y)
+		assert.is.equal(7, a.z)
+	end)
+
+	it("clones a vector", function()
+		local a = vec3(3, 5, 7)
+		local b = a:clone()
+		assert.is.equal(a, b)
+	end)
+
+	it("adds a vector to another", function()
+		local a = vec3(3, 5, 7)
+		local b = vec3(7, 4, 1)
+		local c = a:add(b)
+		local d = a + b
+		assert.is.equal(10, c.x)
+		assert.is.equal(9,  c.y)
+		assert.is.equal(8,  c.z)
+		assert.is.equal(c,  d)
+	end)
+
+	it("subracts a vector from another", function()
+		local a = vec3(3, 5, 7)
+		local b = vec3(7, 4, 1)
+		local c = a:sub(b)
+		local d = a - b
+		assert.is.equal(-4, c.x)
+		assert.is.equal( 1, c.y)
+		assert.is.equal( 6, c.z)
+		assert.is.equal( c, d)
+	end)
+
+	it("multiplies a vector by a scale factor", function()
+		local a = vec3(3, 5, 7)
+		local s = 2
+		local c = a:scale(s)
+		local d = a * s
+		assert.is.equal(6,  c.x)
+		assert.is.equal(10, c.y)
+		assert.is.equal(14, c.z)
+		assert.is.equal(c,  d)
+	end)
+
+	it("divides a vector by another vector", function()
+		local a = vec3(3, 5, 7)
+		local s = vec3(2, 2, 2)
+		local c = a:div(s)
+		local d = a / s
+		assert.is.equal(1.5, c.x)
+		assert.is.equal(2.5, c.y)
+		assert.is.equal(3.5, c.z)
+		assert.is.equal(c,   d)
+	end)
+
+	it("inverts a vector", function()
+		local a = vec3(3, -5, 7)
+		local b = -a
+		assert.is.equal(-a.x, b.x)
+		assert.is.equal(-a.y, b.y)
+		assert.is.equal(-a.z, b.z)
+	end)
+
+	it("gets the length of a vector", function()
+		local a = vec3(3, 5, 7)
+		assert.is.equal(sqrt(83), a:len())
+		end)
+
+	it("gets the square length of a vector", function()
+		local a = vec3(3, 5, 7)
+		assert.is.equal(83, a:len2())
+	end)
+
+	it("normalizes a vector", function()
+		local a = vec3(3, 5, 7)
+		local b = a:normalize()
+		assert.is_true(abs(b:len()-1) < DBL_EPSILON)
+		end)
+
+	it("trims the length of a vector", function()
+		local a = vec3(3, 5, 7)
+		local b = a:trim(0.5)
+		assert.is_true(abs(b:len()-0.5) < DBL_EPSILON)
+	end)
+
+	it("gets the distance between two vectors", function()
+		local a = vec3(3, 5, 7)
+		local b = vec3(7, 4, 1)
+		local c = a:dist(b)
+		assert.is.equal(sqrt(53), c)
+	end)
+
+	it("gets the square distance between two vectors", function()
+		local a = vec3(3, 5, 7)
+		local b = vec3(7, 4, 1)
+		local c = a:dist2(b)
+		assert.is.equal(53, c)
+	end)
+
+	it("crosses two vectors", function()
+		local a = vec3(3, 5, 7)
+		local b = vec3(7, 4, 1)
+		local c = a:cross(b)
+		assert.is.equal(-23, c.x)
+		assert.is.equal( 46, c.y)
+		assert.is.equal(-23, c.z)
+	end)
+
+	it("dots two vectors", function()
+		local a = vec3(3, 5, 7)
+		local b = vec3(7, 4, 1)
+		local c = a:dot(b)
+		assert.is.equal(48, c)
+	end)
+
+	it("interpolates between two vectors", function()
+		local a = vec3(3, 5, 7)
+		local b = vec3(7, 4, 1)
+		local s = 0.1
+		local c = a:lerp(b, s)
+		assert.is.equal(3.4, c.x)
+		assert.is.equal(4.9, c.y)
+		assert.is.equal(6.4, c.z)
+	end)
+
+	it("unpacks a vector", function()
+		local a       = vec3(3, 5, 7)
+		local x, y, z = a:unpack()
+		assert.is.equal(3, x)
+		assert.is.equal(5, y)
+		assert.is.equal(7, z)
+	end)
+
+	it("rotates a vector", function()
+		local a = vec3(3, 5, 7)
+		local b = a:rotate( math.pi, vec3.unit_z)
+		local c = b:rotate(-math.pi, vec3.unit_z)
+		assert.is_not.equal(a, b)
+		assert.is.equal(7, b.z)
+		assert.is.equal(a, c)
+	end)
+
+	it("cannot rotate a vector without a valis axis", function()
+		local a = vec3(3, 5, 7)
+		local b = a:rotate(math.pi, 0)
+		assert.is_equal(a, b)
+	end)
+
+	it("gets a perpendicular vector", function()
+		local a = vec3(3, 5, 7)
+		local b = a:perpendicular()
+		assert.is.equal(-5, b.x)
+		assert.is.equal( 3, b.y)
+		assert.is.equal( 0, b.z)
+	end)
+
+	it("gets a string representation of a vector", function()
+		local a = vec3()
+		local b = a:to_string()
+		assert.is.equal("(+0.000,+0.000,+0.000)", b)
+	end)
+end)

+ 0 - 0
lib/lume.lua → lua_modules/lume.lua


+ 2 - 8
main.lua

@@ -1,4 +1,4 @@
-_ = require 'lib.lume'
+_ = require 'lume'
 g = lovr.graphics
 local cry = require 'app/cry'
 local sleep = require 'app/sleep'
@@ -40,12 +40,6 @@ end
 function drawTransition(factor)
   if factor > 0 then
     g.setColor(1, 1, 1, factor^2)
-    g.push()
-    g.origin()
-    g.translate(0, 0, -1)
-    g.setDepthTest()
-    g.plane('fill', 0, 0, 0, 5, 0, 0, 1)
-    g.setDepthTest('less')
-    g.pop()
+    lovr.graphics.fill()
   end
 end