Full Code of excessive/cpml for AI

master 9edd67236be2 cached
38 files
232.8 KB
83.1k tokens
1 requests
Download .txt
Showing preview only (245K chars total). Download the full file or copy to clipboard to get everything.
Repository: excessive/cpml
Branch: master
Commit: 9edd67236be2
Files: 38
Total size: 232.8 KB

Directory structure:
gitextract__ayjrotp/

├── .coveralls.yml
├── .editorconfig
├── .github/
│   └── workflows/
│       ├── build.yml
│       └── runtest.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── cpml-scm-1.rockspec
├── doc/
│   ├── config.ld
│   └── install_and_build_docs
├── init.lua
├── modules/
│   ├── _private_precond.lua
│   ├── _private_utils.lua
│   ├── bound2.lua
│   ├── bound3.lua
│   ├── bvh.lua
│   ├── color.lua
│   ├── constants.lua
│   ├── intersect.lua
│   ├── mat4.lua
│   ├── mesh.lua
│   ├── octree.lua
│   ├── quat.lua
│   ├── simplex.lua
│   ├── utils.lua
│   ├── vec2.lua
│   └── vec3.lua
└── spec/
    ├── bound2_spec.lua
    ├── bound3_spec.lua
    ├── color_spec.lua
    ├── intersect_spec.lua
    ├── mat4_spec.lua
    ├── mesh_spec.lua
    ├── octree_spec.lua
    ├── quat_spec.lua
    ├── utils_spec.lua
    ├── vec2_spec.lua
    └── vec3_spec.lua

================================================
FILE CONTENTS
================================================

================================================
FILE: .coveralls.yml
================================================
repo_token: WcsY9jsU97Zt0ZIbGHJftGkC8DsD16FVl

================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_line = lf
insert_final_newline = true

[*.lua]
indent_style = tab


================================================
FILE: .github/workflows/build.yml
================================================
# Based on https://gist.github.com/domenic/ec8b0fc8ab45f39403dd
name: Documentation

on:
  pull_request: # Build on pull requests to ensure they don't break docs.
    branches:
    - master
  push:         # We'll only push new docs when master is updated (see below).
    branches:
    - master

jobs:
  build:
    name: Build Docs
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Setup Lua
      uses: leafo/gh-actions-lua@v8
      with:
        luaVersion: 5.4
    - name: Setup Lua Rocks
      uses: leafo/gh-actions-luarocks@v4
    - name: Setup and run ldoc
      run: bash ./doc/install_and_build_docs
    - name: Deploy
      if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./doc/out


================================================
FILE: .github/workflows/runtest.yml
================================================
name: Validate Code

on:
  pull_request:
    branches:
    - master
    - refactor
  push:
    branches:
    - master
    - refactor

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest

    strategy:
      matrix:
        luaVersion: ["5.1.5", "luajit-2.0.5", "luajit-2.1.0-beta3"]

    steps:
    - uses: actions/checkout@v2
    - name: Setup Lua
      uses: leafo/gh-actions-lua@v8.0.0
      with:
        luaVersion: ${{ matrix.luaVersion }}
    - name: Setup Lua Rocks
      uses: leafo/gh-actions-luarocks@v4
    - name: Install dependencies
      run: |
        luarocks --local install busted
        luarocks --local install luacov
        luarocks --local install luacov-coveralls
    - name: Run busted
      run: ~/.luarocks/bin/busted --verbose --coverage spec
    - name: Upload coverage
      continue-on-error: true # don't know why coveralls isn't uploading. For now, let this fail.
      run: |
        # ignore dotfile directories created by lua setup
        ~/.luarocks/bin/luacov-coveralls --exclude '^%.%a+$' --repo-token WcsY9jsU97Zt0ZIbGHJftGkC8DsD16FVl
    # - name: Run luacheck
    #   run: luacheck --std max+busted *.lua spec



================================================
FILE: .gitignore
================================================
# LDoc generated files.
doc/out


================================================
FILE: LICENSE.md
================================================
# Licenses

CPML is Copyright (c) 2016 Colby Klein <shakesoda@gmail.com>.

CPML is Copyright (c) 2016 Landon Manning <lmanning17@gmail.com>.

Code in vec3.lua is derived from hump.vector. (c) 2010-2013 Matthias Richter. MIT.

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.

# The MIT License (MIT)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

# The BSD License (BSD-2-Clause)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
Cirno's Perfect Math Library
====

![Build Status](https://github.com/excessive/cpml/actions/workflows/runtest.yml/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/excessive/cpml/badge.svg?branch=master)](https://coveralls.io/github/excessive/cpml?branch=master)

Various useful bits of game math. 3D line intersections, ray casting, 2d/3d vectors, 4x4 matrices, quaternions, etc.

Intended to be used with LuaJIT and LÖVE (this is the backbone of LÖVE3D).

Online documentation can be found [here](http://excessive.github.io/cpml/) or you can generate them yourself using `ldoc -c doc/config.ld -o index .`

# Installation
Clone the repository and require it, or if you prefer luarocks: `$ luarocks install --server=http://luarocks.org/dev cpml`. Add `--tree=whatever` for a local install.

# Versions

This library has a major compatibility break at version 1.0. Up to version 0.10, composition `a*b` means "apply b, then a" for quaternions and "apply a, then b" for matrices. Now as of version 1.0, the two are consistent and matrix `a*b` means "apply b, then a".


================================================
FILE: cpml-scm-1.rockspec
================================================
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",
   }
}


================================================
FILE: doc/config.ld
================================================
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"
}
dir='./out'
readme='../README.md'
style='!new'


================================================
FILE: doc/install_and_build_docs
================================================
#! /bin/sh

# on github, leafo/gh-actions-lua leafo/gh-actions-luarocks setup luarocks for us.
#~ sudo apt-get install lua5.3 liblua5.3-dev luarocks

# github ldoc is far ahead of the released version.
echo ldoc version:
git ls-remote https://github.com/lunarmodules/LDoc master
luarocks --local install https://raw.githubusercontent.com/lunarmodules/LDoc/master/ldoc-scm-3.rockspec

echo
cd ./doc
~/.luarocks/bin/ldoc .


================================================
FILE: init.lua
================================================
--[[
-------------------------------------------------------------------------------
-- @author Colby Klein
-- @author Landon Manning
-- @copyright 2016
-- @license MIT/X11
-------------------------------------------------------------------------------
                  .'@@@@@@@@@@@@@@#:
              ,@@@@#;            .'@@@@+
           ,@@@'                      .@@@#
         +@@+            ....            .@@@
       ;@@;         '@@@@@@@@@@@@.          @@@
      @@#         @@@@@@@@++@@@@@@@;         `@@;
    .@@`         @@@@@#        #@@@@@          @@@
   `@@          @@@@@` Cirno's  `@@@@#          +@@
   @@          `@@@@@  Perfect   @@@@@           @@+
  @@+          ;@@@@+   Math     +@@@@+           @@
  @@           `@@@@@  Library   @@@@@@           #@'
 `@@            @@@@@@          @@@@@@@           `@@
 :@@             #@@@@@@.    .@@@@@@@@@            @@
 .@@               #@@@@@@@@@@@@;;@@@@@            @@
  @@                  .;+@@#'.   ;@@@@@           :@@
  @@`                            +@@@@+           @@.
  ,@@                            @@@@@           .@@
   @@#          ;;;;;.          `@@@@@           @@
    @@+         .@@@@@          @@@@@           @@`
     #@@         '@@@@@#`    ;@@@@@@          ;@@
      .@@'         @@@@@@@@@@@@@@@           @@#
        +@@'          '@@@@@@@;            @@@
          '@@@`                         '@@@
             #@@@;                  .@@@@:
                :@@@@@@@++;;;+#@@@@@@+`
                      .;'+++++;.
--]]
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 = "1.2.9",
	_DESCRIPTION = "Cirno's Perfect Math Library: Just about everything you need for 3D games. Hopefully."
}

local files = {
	"bvh",
	"color",
	"constants",
	"intersect",
	"mat4",
	"mesh",
	"octree",
	"quat",
	"simplex",
	"utils",
	"vec2",
	"vec3",
	"bound2",
	"bound3",
}

for _, file in ipairs(files) do
	cpml[file] = require(modules .. file)
end

return cpml


================================================
FILE: modules/_private_precond.lua
================================================
-- Preconditions for cpml functions.
local precond = {}


function precond.typeof(t, expected, msg)
	if type(t) ~= expected then
		error(("%s: %s (<%s> expected)"):format(msg, type(t), expected), 3)
	end
end

function precond.assert(cond, msg, ...)
	if not cond then
		error(msg:format(...), 3)
	end
end

return precond


================================================
FILE: modules/_private_utils.lua
================================================
-- Functions exported by utils.lua but needed by vec2 or vec3 (which utils.lua requires)

local private = {}
local floor   = math.floor
local ceil    = math.ceil

function private.round(value, precision)
	if precision then return private.round(value / precision) * precision end
	return value >= 0 and floor(value+0.5) or ceil(value-0.5)
end

function private.is_nan(a)
	return a ~= a
end

return private


================================================
FILE: modules/bound2.lua
================================================
--- 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

--- Extend bound to include point
-- @tparam bound2 a bound
-- @tparam vec2 point to include
-- @treturn bound2 Bound covering current min, current max and new point
function bound2.extend(a, center)
	return bound2.new(a.min:component_min(center), a.max:component_max(center))
end

--- Extend bound to entirety of other bound
-- @tparam bound2 a bound
-- @tparam bound2 bound to cover
-- @treturn bound2 Bound covering current min and max of each bound in the pair
function bound2.extend_bound(a, b)
	return a:extend(b.min):extend(b.max)
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.max.x >= v.x and a.max.y >= v.y
end

-- Round all components of all vectors to nearest int (or other precision).
-- @tparam vec3 a bound to round.
-- @tparam precision Digits after the decimal (round number if unspecified)
-- @treturn vec3 Rounded bound
function bound2.round(a, precision)
	return bound2.new(a.min:round(precision), a.max:round(precision))
end

--- Return a formatted string.
-- @tparam bound2 a bound to be turned into a string
-- @treturn string formatted
function bound2.to_string(a)
	return string.format("(%s-%s)", a.min, a.max)
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
	xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
		ffi.metatype(new, bound2_mt)
	end, function() end)
end

return setmetatable({}, bound2_mt)


================================================
FILE: modules/bound3.lua
================================================
--- 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

--- Extend bound to include point
-- @tparam bound3 a bound
-- @tparam vec3 point to include
-- @treturn bound3 Bound covering current min, current max and new point
function bound3.extend(a, center)
	return bound3.new(a.min:component_min(center), a.max:component_max(center))
end

--- Extend bound to entirety of other bound
-- @tparam bound3 a bound
-- @tparam bound3 bound to cover
-- @treturn bound3 Bound covering current min and max of each bound in the pair
function bound3.extend_bound(a, b)
	return a:extend(b.min):extend(b.max)
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

-- Round all components of all vectors to nearest int (or other precision).
-- @tparam vec3 a bound to round.
-- @tparam precision Digits after the decimal (round number if unspecified)
-- @treturn vec3 Rounded bound
function bound3.round(a, precision)
	return bound3.new(a.min:round(precision), a.max:round(precision))
end

--- Return a formatted string.
-- @tparam bound3 a bound to be turned into a string
-- @treturn string formatted
function bound3.to_string(a)
	return string.format("(%s-%s)", a.min, a.max)
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
	xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
		ffi.metatype(new, bound3_mt)
	end, function() end)
end

return setmetatable({}, bound3_mt)


================================================
FILE: modules/bvh.lua
================================================
-- https://github.com/benraziel/bvh-tree

--- BVH Tree
-- @module bvh

local modules   = (...):gsub('%.[^%.]+$', '') .. "."
local intersect = require(modules .. "intersect")
local vec3      = require(modules .. "vec3")
local EPSILON   = 1e-6
local BVH       = {}
local BVHNode   = {}
local Node

BVH.__index     = BVH
BVHNode.__index = BVHNode

local function new(triangles, maxTrianglesPerNode)
	local tree = setmetatable({}, BVH)
	local trianglesArray = {}

	for _, triangle in ipairs(triangles) do
		local p1 = triangle[1]
		local p2 = triangle[2]
		local p3 = triangle[3]

		table.insert(trianglesArray, p1.x or p1[1])
		table.insert(trianglesArray, p1.y or p1[2])
		table.insert(trianglesArray, p1.z or p1[3])

		table.insert(trianglesArray, p2.x or p2[1])
		table.insert(trianglesArray, p2.y or p2[2])
		table.insert(trianglesArray, p2.z or p2[3])

		table.insert(trianglesArray, p3.x or p3[1])
		table.insert(trianglesArray, p3.y or p3[2])
		table.insert(trianglesArray, p3.z or p3[3])
	end

	tree._trianglesArray      = trianglesArray
	tree._maxTrianglesPerNode = maxTrianglesPerNode or 10
	tree._bboxArray           = tree.calcBoundingBoxes(trianglesArray)

	-- clone a helper array
	tree._bboxHelper = {}
	for _, bbox in ipairs(tree._bboxArray) do
		table.insert(tree._bboxHelper, bbox)
	end

	-- create the root node, add all the triangles to it
	local triangleCount = #triangles
	local extents = tree:calcExtents(1, triangleCount, EPSILON)
	tree._rootNode = Node(extents[1], extents[2], 1, triangleCount, 1)

	tree._nodes_to_split = { tree._rootNode }
	while #tree._nodes_to_split > 0 do
		local node = table.remove(tree._nodes_to_split)
		tree:splitNode(node)
	end
	return tree
end

function BVH:intersectAABB(aabb)
	local nodesToIntersect             = { self._rootNode }
	local trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)
	local intersectingTriangles        = {}

	-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the box.
	-- note: these triangles may not intersect the box themselves
	while #nodesToIntersect > 0 do
		local node = table.remove(nodesToIntersect)

		local node_aabb = {
			min = node._extentsMin,
			max = node._extentsMax
		}

		if intersect.aabb_aabb(aabb, node_aabb) then
			if node._node0 then
				table.insert(nodesToIntersect, node._node0)
			end

			if node._node1 then
				table.insert(nodesToIntersect, node._node1)
			end

			for i=node._startIndex, node._endIndex do
				table.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])
			end
		end
	end

	-- insert all node triangles, don't bother being more specific yet.
	local triangle = { vec3(), vec3(), vec3() }

	for i=1, #trianglesInIntersectingNodes do
		local triIndex = trianglesInIntersectingNodes[i]

		-- print(triIndex, #self._trianglesArray)
		triangle[1].x = self._trianglesArray[1+(triIndex-1)*9]
		triangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]
		triangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]
		triangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]
		triangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]
		triangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]
		triangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]
		triangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]
		triangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]

		table.insert(intersectingTriangles, {
			triangle             = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },
			triangleIndex        = triIndex
		})
	end

	return intersectingTriangles
end

function BVH:intersectRay(rayOrigin, rayDirection, backfaceCulling)
	local nodesToIntersect             = { self._rootNode }
	local trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)
	local intersectingTriangles        = {}

	local invRayDirection = vec3(
		1 / rayDirection.x,
		1 / rayDirection.y,
		1 / rayDirection.z
	)

	-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the ray.
	-- note: these triangles may not intersect the ray themselves
	while #nodesToIntersect > 0 do
		local node = table.remove(nodesToIntersect)

		if BVH.intersectNodeBox(rayOrigin, invRayDirection, node) then
			if node._node0 then
				table.insert(nodesToIntersect, node._node0)
			end

			if node._node1 then
				table.insert(nodesToIntersect, node._node1)
			end

			for i=node._startIndex, node._endIndex do
				table.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])
			end
		end
	end

	-- go over the list of candidate triangles, and check each of them using ray triangle intersection
	local triangle = { vec3(), vec3(), vec3() }
	local ray      = {
		position  = vec3(rayOrigin.x,    rayOrigin.y,    rayOrigin.z),
		direction = vec3(rayDirection.x, rayDirection.y, rayDirection.z)
	}

	for i=1, #trianglesInIntersectingNodes do
		local triIndex = trianglesInIntersectingNodes[i]

		-- print(triIndex, #self._trianglesArray)
		triangle[1].x = self._trianglesArray[1+(triIndex-1)*9]
		triangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]
		triangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]
		triangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]
		triangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]
		triangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]
		triangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]
		triangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]
		triangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]

		local intersectionPoint, intersectionDistance = intersect.ray_triangle(ray, triangle, backfaceCulling)

		if intersectionPoint then
			table.insert(intersectingTriangles, {
				triangle             = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },
				triangleIndex        = triIndex,
				intersectionPoint    = intersectionPoint,
				intersectionDistance = intersectionDistance
			})
		end
	end

	return intersectingTriangles
end

function BVH.calcBoundingBoxes(trianglesArray)
	local p1x, p1y, p1z
	local p2x, p2y, p2z
	local p3x, p3y, p3z
	local minX, minY, minZ
	local maxX, maxY, maxZ

	local bboxArray = {}

	for i=1, #trianglesArray / 9 do
		p1x = trianglesArray[1+(i-1)*9]
		p1y = trianglesArray[1+(i-1)*9+1]
		p1z = trianglesArray[1+(i-1)*9+2]
		p2x = trianglesArray[1+(i-1)*9+3]
		p2y = trianglesArray[1+(i-1)*9+4]
		p2z = trianglesArray[1+(i-1)*9+5]
		p3x = trianglesArray[1+(i-1)*9+6]
		p3y = trianglesArray[1+(i-1)*9+7]
		p3z = trianglesArray[1+(i-1)*9+8]

		minX = math.min(p1x, p2x, p3x)
		minY = math.min(p1y, p2y, p3y)
		minZ = math.min(p1z, p2z, p3z)
		maxX = math.max(p1x, p2x, p3x)
		maxY = math.max(p1y, p2y, p3y)
		maxZ = math.max(p1z, p2z, p3z)

		BVH.setBox(bboxArray, i, i, minX, minY, minZ, maxX, maxY, maxZ)
	end

	return bboxArray
end

function BVH:calcExtents(startIndex, endIndex, expandBy)
	expandBy = expandBy or 0

	if startIndex > endIndex then
		return { vec3(), vec3() }
	end

	local minX =  math.huge
	local minY =  math.huge
	local minZ =  math.huge
	local maxX = -math.huge
	local maxY = -math.huge
	local maxZ = -math.huge

	for i=startIndex, endIndex do
		minX = math.min(self._bboxArray[1+(i-1)*7+1], minX)
		minY = math.min(self._bboxArray[1+(i-1)*7+2], minY)
		minZ = math.min(self._bboxArray[1+(i-1)*7+3], minZ)
		maxX = math.max(self._bboxArray[1+(i-1)*7+4], maxX)
		maxY = math.max(self._bboxArray[1+(i-1)*7+5], maxY)
		maxZ = math.max(self._bboxArray[1+(i-1)*7+6], maxZ)
	end

	return {
		vec3(minX - expandBy, minY - expandBy, minZ - expandBy),
		vec3(maxX + expandBy, maxY + expandBy, maxZ + expandBy)
	}
end

function BVH:splitNode(node)
	local num_elements = node:elementCount()
	if (num_elements <= self._maxTrianglesPerNode) or (num_elements <= 0) then
		return
	end

	local startIndex = node._startIndex
	local endIndex   = node._endIndex

	local leftNode  = { {},{},{} }
	local rightNode = { {},{},{} }
	local extentCenters = { node:centerX(), node:centerY(), node:centerZ() }

	local extentsLength = {
		node._extentsMax.x - node._extentsMin.x,
		node._extentsMax.y - node._extentsMin.y,
		node._extentsMax.z - node._extentsMin.z
	}

	local objectCenter = {}
	for i=startIndex, endIndex do
		objectCenter[1] = (self._bboxArray[1+(i-1)*7+1] + self._bboxArray[1+(i-1)*7+4]) * 0.5 -- center = (min + max) / 2
		objectCenter[2] = (self._bboxArray[1+(i-1)*7+2] + self._bboxArray[1+(i-1)*7+5]) * 0.5 -- center = (min + max) / 2
		objectCenter[3] = (self._bboxArray[1+(i-1)*7+3] + self._bboxArray[1+(i-1)*7+6]) * 0.5 -- center = (min + max) / 2

		for j=1, 3 do
			if objectCenter[j] < extentCenters[j] then
				table.insert(leftNode[j], i)
			else
				table.insert(rightNode[j], i)
			end
		end
	end

	-- check if we couldn't split the node by any of the axes (x, y or z). halt
	-- here, dont try to split any more (cause it will always fail, and we'll
	-- enter an infinite loop
	local splitFailed = {
		#leftNode[1] == 0 or #rightNode[1] == 0,
		#leftNode[2] == 0 or #rightNode[2] == 0,
		#leftNode[3] == 0 or #rightNode[3] == 0
	}

	if splitFailed[1] and splitFailed[2] and splitFailed[3] then
		return
	end

	-- choose the longest split axis. if we can't split by it, choose next best one.
	local splitOrder = { 1, 2, 3 }
	table.sort(splitOrder, function(a, b)
		return extentsLength[a] > extentsLength[b]
	end)

	local leftElements
	local rightElements

	for i=1, 3 do
		local candidateIndex = splitOrder[i]
		if not splitFailed[candidateIndex] then
			leftElements  = leftNode[candidateIndex]
			rightElements = rightNode[candidateIndex]
			break
		end
	end

	-- sort the elements in range (startIndex, endIndex) according to which node they should be at
	local node0Start = startIndex
	local node1Start = node0Start + #leftElements
	local node0End = node1Start - 1
	local node1End = endIndex
	local currElement

	local helperPos = node._startIndex
	local concatenatedElements = {}

	for _, element in ipairs(leftElements) do
		table.insert(concatenatedElements, element)
	end

	for _, element in ipairs(rightElements) do
		table.insert(concatenatedElements, element)
	end

	-- print(#leftElements, #rightElements, #concatenatedElements)

	for i=1, #concatenatedElements do
		currElement = concatenatedElements[i]
		BVH.copyBox(self._bboxArray, currElement, self._bboxHelper, helperPos)
		helperPos = helperPos + 1
	end

	-- copy results back to main array
	for i=1+(node._startIndex-1)*7, node._endIndex*7 do
		self._bboxArray[i] = self._bboxHelper[i]
	end

	-- create 2 new nodes for the node we just split, and add links to them from the parent node
	local node0Extents = self:calcExtents(node0Start, node0End, EPSILON)
	local node1Extents = self:calcExtents(node1Start, node1End, EPSILON)

	local node0 = Node(node0Extents[1], node0Extents[2], node0Start, node0End, node._level + 1)
	local node1 = Node(node1Extents[1], node1Extents[2], node1Start, node1End, node._level + 1)

	node._node0 = node0
	node._node1 = node1
	node:clearShapes()

	-- add new nodes to the split queue
	table.insert(self._nodes_to_split, node0)
	table.insert(self._nodes_to_split, node1)
end

function BVH._calcTValues(minVal, maxVal, rayOriginCoord, invdir)
	local res = { min=0, max=0 }

	if invdir >= 0 then
		res.min = ( minVal - rayOriginCoord ) * invdir
		res.max = ( maxVal - rayOriginCoord ) * invdir
	else
		res.min = ( maxVal - rayOriginCoord ) * invdir
		res.max = ( minVal - rayOriginCoord ) * invdir
	end

	return res
end

function BVH.intersectNodeBox(rayOrigin, invRayDirection, node)
	local t  = BVH._calcTValues(node._extentsMin.x, node._extentsMax.x, rayOrigin.x, invRayDirection.x)
	local ty = BVH._calcTValues(node._extentsMin.y, node._extentsMax.y, rayOrigin.y, invRayDirection.y)

	if t.min > ty.max or ty.min > t.max then
		return false
	end

	-- These lines also handle the case where tmin or tmax is NaN
	-- (result of 0 * Infinity). x !== x returns true if x is NaN
	if ty.min > t.min or t.min ~= t.min then
		t.min = ty.min
	end

	if ty.max < t.max or t.max ~= t.max then
		t.max = ty.max
	end

	local tz = BVH._calcTValues(node._extentsMin.z, node._extentsMax.z, rayOrigin.z, invRayDirection.z)

	if t.min > tz.max or tz.min > t.max then
		return false
	end

	if tz.min > t.min or t.min ~= t.min then
		t.min = tz.min
	end

	if tz.max < t.max or t.max ~= t.max then
		t.max = tz.max
	end

	--return point closest to the ray (positive side)
	if t.max < 0 then
		return false
	end

	return true
end

function BVH.setBox(bboxArray, pos, triangleId, minX, minY, minZ, maxX, maxY, maxZ)
	bboxArray[1+(pos-1)*7]   = triangleId
	bboxArray[1+(pos-1)*7+1] = minX
	bboxArray[1+(pos-1)*7+2] = minY
	bboxArray[1+(pos-1)*7+3] = minZ
	bboxArray[1+(pos-1)*7+4] = maxX
	bboxArray[1+(pos-1)*7+5] = maxY
	bboxArray[1+(pos-1)*7+6] = maxZ
end

function BVH.copyBox(sourceArray, sourcePos, destArray, destPos)
	destArray[1+(destPos-1)*7]   = sourceArray[1+(sourcePos-1)*7]
	destArray[1+(destPos-1)*7+1] = sourceArray[1+(sourcePos-1)*7+1]
	destArray[1+(destPos-1)*7+2] = sourceArray[1+(sourcePos-1)*7+2]
	destArray[1+(destPos-1)*7+3] = sourceArray[1+(sourcePos-1)*7+3]
	destArray[1+(destPos-1)*7+4] = sourceArray[1+(sourcePos-1)*7+4]
	destArray[1+(destPos-1)*7+5] = sourceArray[1+(sourcePos-1)*7+5]
	destArray[1+(destPos-1)*7+6] = sourceArray[1+(sourcePos-1)*7+6]
end

function BVH.getBox(bboxArray, pos, outputBox)
	outputBox.triangleId = bboxArray[1+(pos-1)*7]
	outputBox.minX       = bboxArray[1+(pos-1)*7+1]
	outputBox.minY       = bboxArray[1+(pos-1)*7+2]
	outputBox.minZ       = bboxArray[1+(pos-1)*7+3]
	outputBox.maxX       = bboxArray[1+(pos-1)*7+4]
	outputBox.maxY       = bboxArray[1+(pos-1)*7+5]
	outputBox.maxZ       = bboxArray[1+(pos-1)*7+6]
end

local function new_node(extentsMin, extentsMax, startIndex, endIndex, level)
	return setmetatable({
		_extentsMin = extentsMin,
		_extentsMax = extentsMax,
		_startIndex = startIndex,
		_endIndex   = endIndex,
		_level      = level
		--_node0    = nil
		--_node1    = nil
	}, BVHNode)
end

function BVHNode:elementCount()
	return (self._endIndex + 1) - self._startIndex
end

function BVHNode:centerX()
	return (self._extentsMin.x + self._extentsMax.x) * 0.5
end

function BVHNode:centerY()
	return (self._extentsMin.y + self._extentsMax.y) * 0.5
end

function BVHNode:centerZ()
	return (self._extentsMin.z + self._extentsMax.z) * 0.5
end

function BVHNode:clearShapes()
	self._startIndex =  0
	self._endIndex   = -1
end

function BVHNode.ngSphereRadius(extentsMin, extentsMax)
	local centerX = (extentsMin.x + extentsMax.x) * 0.5
	local centerY = (extentsMin.y + extentsMax.y) * 0.5
	local centerZ = (extentsMin.z + extentsMax.z) * 0.5

	local extentsMinDistSqr =
		(centerX - extentsMin.x) * (centerX - extentsMin.x) +
		(centerY - extentsMin.y) * (centerY - extentsMin.y) +
		(centerZ - extentsMin.z) * (centerZ - extentsMin.z)

	local extentsMaxDistSqr =
		(centerX - extentsMax.x) * (centerX - extentsMax.x) +
		(centerY - extentsMax.y) * (centerY - extentsMax.y) +
		(centerZ - extentsMax.z) * (centerZ - extentsMax.z)

	return math.sqrt(math.max(extentsMinDistSqr, extentsMaxDistSqr))
end

--[[

--- Draws node boundaries visually for debugging.
-- @param cube Cube model to draw
-- @param depth Used for recurcive calls to self method
function OctreeNode:draw_bounds(cube, depth)
	depth = depth or 0
	local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically

	love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
	local m = mat4()
		:translate(self.center)
		:scale(vec3(self.adjLength, self.adjLength, self.adjLength))

	love.graphics.updateMatrix("transform", m)
	love.graphics.setWireframe(true)
	love.graphics.draw(cube)
	love.graphics.setWireframe(false)

	for _, child in ipairs(self.children) do
		child:draw_bounds(cube, depth + 1)
	end

	love.graphics.setColor(255, 255, 255)
end

--- Draws the bounds of all objects in the tree visually for debugging.
-- @param cube Cube model to draw
-- @param filter a function returning true or false to determine visibility.
function OctreeNode:draw_objects(cube, filter)
	local tint = self.baseLength / 20
	love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)

	for _, object in ipairs(self.objects) do
		if filter and filter(object.data) or not filter then
			local m = mat4()
				:translate(object.bounds.center)
				:scale(object.bounds.size)

			love.graphics.updateMatrix("transform", m)
			love.graphics.draw(cube)
		end
	end

	for _, child in ipairs(self.children) do
		child:draw_objects(cube, filter)
	end

	love.graphics.setColor(255, 255, 255)
end

--]]

Node = setmetatable({
	new = new_node
}, {
	__call = function(_, ...) return new_node(...) end
})

return setmetatable({
	new = new
}, {
	__call = function(_, ...) return new(...) end
})


================================================
FILE: modules/color.lua
================================================
--- Color utilities
-- @module color

local modules  = (...):gsub('%.[^%.]+$', '') .. "."
local constants = require(modules .. "constants")
local utils    = require(modules .. "utils")
local precond  = require(modules .. "_private_precond")
local color    = {}
local color_mt = {}

local function new(r, g, b, a)
	local c = { r, g, b, a }
	c._c = c
	return setmetatable(c, color_mt)
end

-- HSV utilities (adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html)
-- hsv_to_color(hsv)
-- Converts a set of HSV values to a color. hsv is a table.
-- See also: hsv(h, s, v)
local function hsv_to_color(hsv)
	local i
	local f, q, p, t
	local h, s, v
	local a = hsv[4] or 1
	s = hsv[2]
	v = hsv[3]

	if s == 0 then
		return new(v, v, v, a)
	end

	h = hsv[1] * 6 -- sector 0 to 5

	i = math.floor(h)
	f = h - i -- factorial part of h
	p = v * (1-s)
	q = v * (1-s*f)
	t = v * (1-s*(1-f))

	if     i == 0 then return new(v, t, p, a)
	elseif i == 1 then return new(q, v, p, a)
	elseif i == 2 then return new(p, v, t, a)
	elseif i == 3 then return new(p, q, v, a)
	elseif i == 4 then return new(t, p, v, a)
	else               return new(v, p, q, a)
	end
end

-- color_to_hsv(c)
-- Takes in a normal color and returns a table with the HSV values.
local function color_to_hsv(c)
	local r = c[1]
	local g = c[2]
	local b = c[3]
	local a = c[4] or 1
	local h, s, v

	local min = math.min(r, g, b)
	local max = math.max(r, g, b)
	v = max

	local delta = max - min

	-- black, nothing else is really possible here.
	if min == 0 and max == 0 then
		return { 0, 0, 0, a }
	end

	if max ~= 0 then
		s = delta / max
	else
		-- r = g = b = 0 s = 0, v is undefined
		s = 0
		h = -1
		return { h, s, v, 1 }
	end

	-- Prevent division by zero.
	if delta == 0 then
		delta = constants.DBL_EPSILON
	end

	if r == max then
		h = ( g - b ) / delta     -- yellow/magenta
	elseif g == max then
		h = 2 + ( b - r ) / delta -- cyan/yellow
	else
		h = 4 + ( r - g ) / delta -- magenta/cyan
	end

	h = h / 6 -- normalize from segment 0..5

	if h < 0 then
		h = h + 1
	end

	return { h, s, v, a }
end

--- The public constructor.
-- @param x Can be of three types: </br>
-- number red component 0-1
-- table {r, g, b, a}
-- nil for {0,0,0,0}
-- @tparam number g Green component 0-1
-- @tparam number b Blue component 0-1
-- @tparam number a Alpha component 0-1
-- @treturn color out
function color.new(r, g, b, a)
	-- number, number, number, number
	if r and g and b and a then
		precond.typeof(r, "number", "new: Wrong argument type for r")
		precond.typeof(g, "number", "new: Wrong argument type for g")
		precond.typeof(b, "number", "new: Wrong argument type for b")
		precond.typeof(a, "number", "new: Wrong argument type for a")

		return new(r, g, b, a)

	-- {r, g, b, a}
	elseif type(r) == "table" then
		local rr, gg, bb, aa = r[1], r[2], r[3], r[4]
		precond.typeof(rr, "number", "new: Wrong argument type for r")
		precond.typeof(gg, "number", "new: Wrong argument type for g")
		precond.typeof(bb, "number", "new: Wrong argument type for b")
		precond.typeof(aa, "number", "new: Wrong argument type for a")

		return new(rr, gg, bb, aa)
	end

	return new(0, 0, 0, 0)
end

--- Convert hue,saturation,value table to color object.
-- @tparam table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}
-- @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-1, saturation 0-1, value 0-1, alpha 0-1}
color.color_to_hsv_table = color_to_hsv

--- Convert hue,saturation,value to color object.
-- @tparam number h hue 0-1
-- @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 }
end

--- Convert hue,saturation,value to color object.
-- @tparam number h hue 0-1
-- @tparam number s saturation 0-1
-- @tparam number v value 0-1
-- @tparam number a alpha 0-1
-- @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(1 - c[1], 1 - c[2], 1 - 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-1 scale
-- @treturn color out
function color.lighten(c, v)
	return new(
		utils.clamp(c[1] + v, 0, 1),
		utils.clamp(c[2] + v, 0, 1),
		utils.clamp(c[3] + v, 0, 1),
		c[4]
	)
end

--- Interpolate between two colors.
-- @tparam color at start
-- @tparam color at end
-- @tparam number s in 0-1 progress between the two colors
-- @treturn color out
function color.lerp(a, b, s)
	return a + s * (b - a)
end

--- Unpack a color into individual components in 0-1.
-- @tparam color to unpack
-- @treturn number r in 0-1
-- @treturn number g in 0-1
-- @treturn number b in 0-1
-- @treturn number a in 0-1
function color.unpack(c)
	return c[1], c[2], c[3], c[4]
end

--- Unpack a color into individual components in 0-255.
-- @tparam color to unpack
-- @treturn number r in 0-255
-- @treturn number g in 0-255
-- @treturn number b in 0-255
-- @treturn number a in 0-255
function color.as_255(c)
	return c[1] * 255, c[2] * 255, c[3] * 255, c[4] * 255
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-1 scale
-- @treturn color out
function color.darken(c, v)
	return new(
		utils.clamp(c[1] - v, 0, 1),
		utils.clamp(c[2] - v, 0, 1),
		utils.clamp(c[3] - v, 0, 1),
		c[4]
	)
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
		t[i] = c[i] * v
	end

	t[4] = c[4]
	return t
end

-- directly set alpha channel
-- @tparam color to alter
-- @tparam number new alpha 0-1
-- @treturn color out
function color.alpha(c, v)
	local t = color.new()
	for i = 1, 3 do
		t[i] = c[i]
	end

	t[4] = 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
		t[i] = c[i]
	end

	t[4] = c[4] * v
	return t
end

--- Set a color's hue (saturation, value, alpha unchanged)
-- @tparam color to alter
-- @tparam hue to set 0-1
-- @treturn color out
function color.hue(col, hue)
	local c = color_to_hsv(col)
	c[1] = (hue + 1) % 1
	return hsv_to_color(c)
end

--- Set a color's saturation (hue, value, alpha unchanged)
-- @tparam color to alter
-- @tparam saturation to set 0-1
-- @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

--- Set a color's value (saturation, hue, alpha unchanged)
-- @tparam color to alter
-- @tparam value to set 0-1
-- @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

-- https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
function color.gamma_to_linear(r, g, b, a)
	local function convert(c)
		if c > 1.0 then
			return 1.0
		elseif c < 0.0 then
			return 0.0
		elseif c <= 0.04045 then
			return c / 12.92
		else
			return math.pow((c + 0.055) / 1.055, 2.4)
		end
	end

	if type(r) == "table" then
		local c = {}
		for i = 1, 3 do
			c[i] = convert(r[i])
		end

		c[4] = r[4]
		return c
	else
		return convert(r), convert(g), convert(b), a or 1
	end
end

-- https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB
function color.linear_to_gamma(r, g, b, a)
	local function convert(c)
		if c > 1.0 then
			return 1.0
		elseif c < 0.0 then
			return 0.0
		elseif c < 0.0031308 then
			return c * 12.92
		else
			return 1.055 * math.pow(c, 0.41666) - 0.055
		end
	end

	if type(r) == "table" then
		local c = {}
		for i = 1, 3 do
			c[i] = convert(r[i])
		end

		c[4] = r[4]
		return c
	else
		return convert(r), convert(g), convert(b), a or 1
	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
	end

	for i = 1, 4 do
		if type(a[i]) ~= "number" then
			return false
		end
	end

	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

color_mt.__index = color
color_mt.__tostring = color.to_string

function color_mt.__call(_, r, g, b, a)
	return color.new(r, g, b, a)
end

function color_mt.__add(a, b)
	return new(a[1] + b[1], a[2] + b[2], a[3] + b[3], a[4] + b[4])
end

function color_mt.__sub(a, b)
	return new(a[1] - b[1], a[2] - b[2], a[3] - b[3], a[4] - b[4])
end

function color_mt.__mul(a, b)
	if type(a) == "number" then
		return new(a * b[1], a * b[2], a * b[3], a * b[4])
	elseif type(b) == "number" then
		return new(b * a[1], b * a[2], b * a[3], b * a[4])
	else
		return new(a[1] * b[1], a[2] * b[2], a[3] * b[3], a[4] * b[4])
	end
end

return setmetatable({}, color_mt)


================================================
FILE: modules/constants.lua
================================================
--- Various useful constants
-- @module constants

--- Constants
-- @table constants
-- @field FLT_EPSILON Floating point precision breaks down
-- @field DBL_EPSILON Double-precise floating point precision breaks down
-- @field DOT_THRESHOLD Close enough to 1 for interpolations to occur
local constants = {}

-- same as C's FLT_EPSILON
constants.FLT_EPSILON = 1.19209290e-07

-- same as C's DBL_EPSILON
constants.DBL_EPSILON = 2.2204460492503131e-16

-- used for quaternion.slerp
constants.DOT_THRESHOLD = 0.9995

return constants


================================================
FILE: modules/intersect.lua
================================================
--- Various geometric intersections
-- @module intersect

local modules     = (...):gsub('%.[^%.]+$', '') .. "."
local constants   = require(modules .. "constants")
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
local min         = math.min
local max         = math.max
local intersect   = {}

-- Checks if a point belongs to the segment
-- p is a vec3
-- seg[1] is a vec3
-- seg[2] is a vec3
-- Returns a boolean
function intersect.point_segment(p, seg)
	local min, max = vec3.component_sort(seg[1], seg[2])
	if min.x <= p.x and
		min.y <= p.y and
		min.z <= p.z and
		p.x <= max.x and
		p.y <= max.y and
		p.z <= max.z
	then
		return true
	end
	return false
end

-- 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
-- triangle[2] is a vec3
-- triangle[3] is a vec3
function intersect.point_triangle(point, triangle)
	local u = triangle[2] - triangle[1]
	local v = triangle[3] - triangle[1]
	local w = point       - triangle[1]

	local vw = v:cross(w)
	local vu = v:cross(u)

	if vw:dot(vu) < 0 then
		return false
	end

	local uw = u:cross(w)
	local uv = u:cross(v)

	if uw:dot(uv) < 0 then
		return false
	end

	local d = uv:len()
	local r = vw:len() / d
	local t = uw:len() / d

	return r + t <= 1
end

-- point    is a vec3
-- aabb.min is a vec3
-- aabb.max is a vec3
function intersect.point_aabb(point, aabb)
	return
		aabb.min.x <= point.x and
		aabb.max.x >= point.x and
		aabb.min.y <= point.y and
		aabb.max.y >= point.y and
		aabb.min.z <= point.z and
		aabb.max.z >= point.z
end

-- point          is a vec3
-- frustum.left   is a plane { a, b, c, d }
-- frustum.right  is a plane { a, b, c, d }
-- frustum.bottom is a plane { a, b, c, d }
-- frustum.top    is a plane { a, b, c, d }
-- frustum.near   is a plane { a, b, c, d }
-- frustum.far    is a plane { a, b, c, d }
function intersect.point_frustum(point, frustum)
	local x, y, z = point:unpack()
	local planes  = {
		frustum.left,
		frustum.right,
		frustum.bottom,
		frustum.top,
		frustum.near,
		frustum.far or false
	}

	-- Skip the last test for infinite projections, it'll never fail.
	if not planes[6] then
		table.remove(planes)
	end

	local dot
	for i = 1, #planes do
		dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d
		if dot <= 0 then
			return false
		end
	end

	return true
end

-- http://www.lighthouse3d.com/tutorials/maths/ray-triangle-intersection/
-- ray.position  is a vec3
-- ray.direction is a vec3
-- triangle[1]   is a vec3
-- triangle[2]   is a vec3
-- triangle[3]   is a vec3
-- backface_cull is a boolean (optional)
function intersect.ray_triangle(ray, triangle, backface_cull)
	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 negative, ray hits the backface
	if backface_cull and a < 0 then
		return false
	end

	-- if a is too close to 0, ray does not intersect triangle
	if abs(a) <= DBL_EPSILON then
		return false
	end

	local f = 1 / a
	local s = ray.position - triangle[1]
	local u = s:dot(h) * f

	-- ray does not intersect triangle
	if u < 0 or u > 1 then
		return false
	end

	local q = s:cross(e1)
	local v = ray.direction:dot(q) * f

	-- ray does not intersect triangle
	if v < 0 or u + v > 1 then
		return false
	end

	-- at this stage we can compute t to find out where
	-- the intersection point is on the line
	local t = q:dot(e2) * f

	-- return position of intersection and distance from ray origin
	if t >= DBL_EPSILON then
		return ray.position + ray.direction * t, t
	end

	-- ray does not intersect triangle
	return false
end

-- https://gamedev.stackexchange.com/questions/96459/fast-ray-sphere-collision-code
-- ray.position    is a vec3
-- ray.direction   is a vec3
-- sphere.position is a vec3
-- sphere.radius   is a number
function intersect.ray_sphere(ray, sphere)
	local offset = ray.position - sphere.position
	local b = offset:dot(ray.direction)
	local c = offset:dot(offset) - sphere.radius * sphere.radius

	-- ray's position outside sphere (c > 0)
	-- ray's direction pointing away from sphere (b > 0)
	if c > 0 and b > 0 then
		return false
	end

	local discr = b * b - c

	-- negative discriminant
	if discr < 0 then
		return false
	end

	-- Clamp t to 0
	local t = -b - sqrt(discr)
	t = t < 0 and 0 or t

	-- Return collision point and distance from ray origin
	return ray.position + ray.direction * t, t
end

-- http://gamedev.stackexchange.com/a/18459
-- ray.position  is a vec3
-- ray.direction is a vec3
-- aabb.min      is a vec3
-- aabb.max      is a vec3
function intersect.ray_aabb(ray, aabb)
	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
	local t3 = (aabb.min.y - ray.position.y) * dirfrac.y
	local t4 = (aabb.max.y - ray.position.y) * dirfrac.y
	local t5 = (aabb.min.z - ray.position.z) * dirfrac.z
	local t6 = (aabb.max.z - ray.position.z) * dirfrac.z

	local tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6))
	local tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6))

	-- ray is intersecting AABB, but whole AABB is behind us
	if tmax < 0 then
		return false
	end

	-- ray does not intersect AABB
	if tmin > tmax then
		return false
	end

	-- Return collision point and distance from ray origin
	return ray.position + ray.direction * tmin, tmin
end

-- http://stackoverflow.com/a/23976134/1190664
-- ray.position   is a vec3
-- ray.direction  is a vec3
-- plane.position is a vec3
-- plane.normal   is a vec3
function intersect.ray_plane(ray, plane)
	local denom = plane.normal:dot(ray.direction)

	-- ray does not intersect plane
	if abs(denom) < DBL_EPSILON then
		return false
	end

	-- distance of direction
	local d = plane.position - ray.position
	local t = d:dot(plane.normal) / denom

	if t < DBL_EPSILON then
		return false
	end

	-- Return collision point and distance from ray origin
	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/
-- a[1] is a vec3
-- a[2] is a vec3
-- b[1] is a vec3
-- b[2] is a vec3
-- e    is a number
function intersect.line_line(a, b, e)
	-- new points
	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
		return false
	end

	-- dot products
	local d1343 = p13:dot(p43)
	local d4321 = p43:dot(p21)
	local d1321 = p13:dot(p21)
	local d4343 = p43:dot(p43)
	local d2121 = p21:dot(p21)
	local denom = d2121 * d4343 - d4321 * d4321

	-- if denom is too close to 0, lines do not intersect
	if abs(denom) < DBL_EPSILON then
		return false
	end

	local numer = d1343 * d4321 - d1321 * d4343
	local mua   = numer / denom
	local mub   = (d1343 + d4321 * mua) / d4343

	-- return positions of intersection on each line
	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
	if e and dist > e then
		return false
	end

	return { out1, out2 }, dist
end

-- a[1] is a vec3
-- a[2] is a vec3
-- b[1] is a vec3
-- b[2] is a vec3
-- e    is a number
function intersect.segment_segment(a, b, e)
	local c, d = intersect.line_line(a, b, e)
	if c and
		intersect.point_segment(c[1], a) and
		intersect.point_segment(c[2], a) and
		intersect.point_segment(c[1], b) and
		intersect.point_segment(c[2], b) then
		return c, d
	end
	-- segments do not intersect
	return false
end

-- a.min is a vec3
-- a.max is a vec3
-- b.min is a vec3
-- b.max is a vec3
function intersect.aabb_aabb(a, b)
	return
		a.min.x <= b.max.x and
		a.max.x >= b.min.x and
		a.min.y <= b.max.y and
		a.max.y >= b.min.y and
		a.min.z <= b.max.z and
		a.max.z >= b.min.z
end

-- aabb.position is a vec3
-- aabb.extent   is a vec3 (half-size)
-- obb.position  is a vec3
-- obb.extent    is a vec3 (half-size)
-- obb.rotation  is a mat4
function intersect.aabb_obb(aabb, obb)
	local a   = aabb.extent
	local b   = obb.extent
	local T   = obb.position - aabb.position
	local rot = mat4():transpose(obb.rotation)
	local B   = {}
	local t

	for i = 1, 3 do
		B[i] = {}
		for j = 1, 3 do
			assert((i - 1) * 4 + j < 16 and (i - 1) * 4 + j > 0)
			B[i][j] = abs(rot[(i - 1) * 4 + j]) + 1e-6
		end
	end

	t = abs(T.x)
	if not (t <= (b.x + a.x * B[1][1] + b.y * B[1][2] + b.z * B[1][3])) then return false end
	t = abs(T.x * B[1][1] + T.y * B[2][1] + T.z * B[3][1])
	if not (t <= (b.x + a.x * B[1][1] + a.y * B[2][1] + a.z * B[3][1])) then return false end
	t = abs(T.y)
	if not (t <= (a.y + b.x * B[2][1] + b.y * B[2][2] + b.z * B[2][3])) then return false end
	t = abs(T.z)
	if not (t <= (a.z + b.x * B[3][1] + b.y * B[3][2] + b.z * B[3][3])) then return false end
	t = abs(T.x * B[1][2] + T.y * B[2][2] + T.z * B[3][2])
	if not (t <= (b.y + a.x * B[1][2] + a.y * B[2][2] + a.z * B[3][2])) then return false end
	t = abs(T.x * B[1][3] + T.y * B[2][3] + T.z * B[3][3])
	if not (t <= (b.z + a.x * B[1][3] + a.y * B[2][3] + a.z * B[3][3])) then return false end
	t = abs(T.z * B[2][1] - T.y * B[3][1])
	if not (t <= (a.y * B[3][1] + a.z * B[2][1] + b.y * B[1][3] + b.z * B[1][2])) then return false end
	t = abs(T.z * B[2][2] - T.y * B[3][2])
	if not (t <= (a.y * B[3][2] + a.z * B[2][2] + b.x * B[1][3] + b.z * B[1][1])) then return false end
	t = abs(T.z * B[2][3] - T.y * B[3][3])
	if not (t <= (a.y * B[3][3] + a.z * B[2][3] + b.x * B[1][2] + b.y * B[1][1])) then return false end
	t = abs(T.x * B[3][1] - T.z * B[1][1])
	if not (t <= (a.x * B[3][1] + a.z * B[1][1] + b.y * B[2][3] + b.z * B[2][2])) then return false end
	t = abs(T.x * B[3][2] - T.z * B[1][2])
	if not (t <= (a.x * B[3][2] + a.z * B[1][2] + b.x * B[2][3] + b.z * B[2][1])) then return false end
	t = abs(T.x * B[3][3] - T.z * B[1][3])
	if not (t <= (a.x * B[3][3] + a.z * B[1][3] + b.x * B[2][2] + b.y * B[2][1])) then return false end
	t = abs(T.y * B[1][1] - T.x * B[2][1])
	if not (t <= (a.x * B[2][1] + a.y * B[1][1] + b.y * B[3][3] + b.z * B[3][2])) then return false end
	t = abs(T.y * B[1][2] - T.x * B[2][2])
	if not (t <= (a.x * B[2][2] + a.y * B[1][2] + b.x * B[3][3] + b.z * B[3][1])) then return false end
	t = abs(T.y * B[1][3] - T.x * B[2][3])
	if not (t <= (a.x * B[2][3] + a.y * B[1][3] + b.x * B[3][2] + b.y * B[3][1])) then return false end

	-- https://gamedev.stackexchange.com/questions/24078/which-side-was-hit
	-- Minkowski Sum
	local wy = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.y - obb.position.y)
	local hx = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.x - obb.position.x)

	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(obb.rotation * {  0, -1, 0, 1 })
		else
			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(obb.rotation * { 1, 0, 0, 1 })
		else
			return vec3(obb.rotation * { 0, 1, 0, 1 })
		end
	end
end

-- http://stackoverflow.com/a/4579069/1190664
-- aabb.min        is a vec3
-- 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

	for _, axis in ipairs(axes) do
		local pos  = sphere.position[axis]
		local amin = aabb.min[axis]
		local amax = aabb.max[axis]

		if pos < amin then
			dist2 = dist2 - (pos - amin) ^ 2
		elseif pos > amax then
			dist2 = dist2 - (pos - amax) ^ 2
		end
	end

	return dist2 > 0
end

-- aabb.min       is a vec3
-- aabb.max       is a vec3
-- frustum.left   is a plane { a, b, c, d }
-- frustum.right  is a plane { a, b, c, d }
-- frustum.bottom is a plane { a, b, c, d }
-- frustum.top    is a plane { a, b, c, d }
-- frustum.near   is a plane { a, b, c, d }
-- frustum.far    is a plane { a, b, c, d }
function intersect.aabb_frustum(aabb, frustum)
	-- Indexed for the 'index trick' later
	local box = {
		aabb.min,
		aabb.max
	}

	-- We have 6 planes defining the frustum, 5 if infinite.
	local planes = {
		frustum.left,
		frustum.right,
		frustum.bottom,
		frustum.top,
		frustum.near,
		frustum.far or false
	}

	-- Skip the last test for infinite projections, it'll never fail.
	if not planes[6] then
		table.remove(planes)
	end

	for i = 1, #planes do
		-- This is the current plane
		local p = planes[i]

		-- p-vertex selection (with the index trick)
		-- According to the plane normal we can know the
		-- indices of the positive vertex
		local px = p.a > 0.0 and 2 or 1
		local py = p.b > 0.0 and 2 or 1
		local pz = p.c > 0.0 and 2 or 1

		-- project p-vertex on plane normal
		-- (How far is p-vertex from the origin)
		local dot = (p.a * box[px].x) + (p.b * box[py].y) + (p.c * box[pz].z)

		-- Doesn't intersect if it is behind the plane
		if dot < -p.d then
			return false
		end
	end

	return true
end

-- outer.min is a vec3
-- outer.max is a vec3
-- inner.min is a vec3
-- inner.max is a vec3
function intersect.encapsulate_aabb(outer, inner)
	return
		outer.min.x <= inner.min.x and
		outer.max.x >= inner.max.x and
		outer.min.y <= inner.min.y and
		outer.max.y >= inner.max.y and
		outer.min.z <= inner.min.z and
		outer.max.z >= inner.max.z
end

-- a.position is a vec3
-- a.radius   is a number
-- b.position is a vec3
-- b.radius   is a number
function intersect.circle_circle(a, b)
	return a.position:dist(b.position) <= a.radius + b.radius
end

-- a.position is a vec3
-- a.radius   is a number
-- b.position is a vec3
-- b.radius   is a number
function intersect.sphere_sphere(a, b)
	return intersect.circle_circle(a, b)
end

-- http://realtimecollisiondetection.net/blog/?p=103
-- sphere.position is a vec3
-- sphere.radius   is a number
-- triangle[1]     is a vec3
-- triangle[2]     is a vec3
-- triangle[3]     is a vec3
function intersect.sphere_triangle(sphere, triangle)
	-- Sphere is centered at origin
	local A  = triangle[1] - sphere.position
	local B  = triangle[2] - sphere.position
	local C  = triangle[3] - sphere.position

	-- Compute normal of triangle plane
	local V  = (B - A):cross(C - A)

	-- Test if sphere lies outside triangle plane
	local rr = sphere.radius * sphere.radius
	local d  = A:dot(V)
	local e  = V:dot(V)
	local s1 = d * d > rr * e

	-- Test if sphere lies outside triangle vertices
	local aa = A:dot(A)
	local ab = A:dot(B)
	local ac = A:dot(C)
	local bb = B:dot(B)
	local bc = B:dot(C)
	local cc = C:dot(C)

	local s2 = (aa > rr) and (ab > aa) and (ac > aa)
	local s3 = (bb > rr) and (ab > bb) and (bc > bb)
	local s4 = (cc > rr) and (ac > cc) and (bc > cc)

	-- Test is sphere lies outside triangle edges
	local AB = B - A
	local BC = C - B
	local CA = A - C

	local d1 = ab - aa
	local d2 = bc - bb
	local d3 = ac - cc

	local e1 = AB:dot(AB)
	local e2 = BC:dot(BC)
	local e3 = CA:dot(CA)

	local Q1 = A * e1 - AB * d1
	local Q2 = B * e2 - BC * d2
	local Q3 = C * e3 - CA * d3

	local QC = C * e1 - Q1
	local QA = A * e2 - Q2
	local QB = B * e3 - Q3

	local s5 = (Q1:dot(Q1) > rr * e1 * e1) and (Q1:dot(QC) > 0)
	local s6 = (Q2:dot(Q2) > rr * e2 * e2) and (Q2:dot(QA) > 0)
	local s7 = (Q3:dot(Q3) > rr * e3 * e3) and (Q3:dot(QB) > 0)

	-- Return whether or not any of the tests passed
	return s1 or s2 or s3 or s4 or s5 or s6 or s7
end

-- sphere.position is a vec3
-- sphere.radius   is a number
-- frustum.left    is a plane { a, b, c, d }
-- frustum.right   is a plane { a, b, c, d }
-- frustum.bottom  is a plane { a, b, c, d }
-- frustum.top     is a plane { a, b, c, d }
-- frustum.near    is a plane { a, b, c, d }
-- frustum.far     is a plane { a, b, c, d }
function intersect.sphere_frustum(sphere, frustum)
	local x, y, z = sphere.position:unpack()
	local planes  = {
		frustum.left,
		frustum.right,
		frustum.bottom,
		frustum.top,
		frustum.near
	}

	if frustum.far then
		table.insert(planes, frustum.far, 5)
	end

	local dot
	for i = 1, #planes do
		dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d

		if dot <= -sphere.radius then
			return false
		end
	end

	-- dot + radius is the distance of the object from the near plane.
	-- make sure that the near plane is the last test!
	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


================================================
FILE: modules/mat4.lua
================================================
--- double 4x4, 1-based, column major matrices
-- @module mat4
local modules   = (...):gsub('%.[^%.]+$', '') .. "."
local constants = require(modules .. "constants")
local vec2      = require(modules .. "vec2")
local vec3      = require(modules .. "vec3")
local quat      = require(modules .. "quat")
local utils     = require(modules .. "utils")
local precond   = require(modules .. "_private_precond")
local private   = require(modules .. "_private_utils")
local sqrt      = math.sqrt
local cos       = math.cos
local sin       = math.sin
local tan       = math.tan
local rad       = math.rad
local mat4      = {}
local mat4_mt   = {}

-- Private constructor.
local function new(m)
	m = m or {
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0
	}
	m._m = m
	return setmetatable(m, mat4_mt)
end

-- Convert matrix into identity
local function identity(m)
	m[1],  m[2],  m[3],  m[4]  = 1, 0, 0, 0
	m[5],  m[6],  m[7],  m[8]  = 0, 1, 0, 0
	m[9],  m[10], m[11], m[12] = 0, 0, 1, 0
	m[13], m[14], m[15], m[16] = 0, 0, 0, 1
	return m
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 { double _m[16]; } cpml_mat4;"
		new = ffi.typeof("cpml_mat4")
	end
end

-- Statically allocate a temporary variable used in some of our functions.
local tmp = new()
local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
local tv4 = { 0, 0, 0, 0 }

--- The public constructor.
-- @param a Can be of four types: </br>
-- table Length 16 (4x4 matrix)
-- table Length 9 (3x3 matrix)
-- table Length 4 (4 vec4s)
-- nil
-- @treturn mat4 out
function mat4.new(a)
	local out = new()

	-- 4x4 matrix
	if type(a) == "table" and #a == 16 then
		for i = 1, 16 do
			out[i] = tonumber(a[i])
		end

	-- 3x3 matrix
	elseif type(a) == "table" and #a == 9 then
		out[1], out[2],  out[3]  = a[1], a[2], a[3]
		out[5], out[6],  out[7]  = a[4], a[5], a[6]
		out[9], out[10], out[11] = a[7], a[8], a[9]
		out[16] = 1

	-- 4 vec4s
	elseif type(a) == "table" and type(a[1]) == "table" then
		local idx = 1
		for i = 1, 4 do
			for j = 1, 4 do
				out[idx] = a[i][j]
				idx = idx + 1
			end
		end

	-- nil
	else
		out[1]  = 1
		out[6]  = 1
		out[11] = 1
		out[16] = 1
	end

	return out
end

--- Create an identity matrix.
-- @tparam mat4 a Matrix to overwrite
-- @treturn mat4 out
function mat4.identity(a)
	return identity(a or new())
end

--- Create a matrix from an angle/axis pair.
-- @tparam number angle Angle of rotation
-- @tparam vec3 axis Axis of rotation
-- @treturn mat4 out
function mat4.from_angle_axis(angle, axis)
	local l = axis:len()
	if l == 0 then
		return new()
	end

	local x, y, z = axis.x / l, axis.y / l, axis.z / l
	local c = cos(angle)
	local s = sin(angle)

	return new {
		x*x*(1-c)+c,   y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
		x*y*(1-c)-z*s, y*y*(1-c)+c,   y*z*(1-c)+x*s, 0,
		x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c,   0,
		0, 0, 0, 1
	}
end

--- Create a matrix from a quaternion.
-- @tparam quat q Rotation quaternion
-- @treturn mat4 out
function mat4.from_quaternion(q)
	return mat4.from_angle_axis(q:to_angle_axis())
end

--- Create a matrix from a direction/up pair.
-- @tparam vec3 direction Vector direction
-- @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()
	local new_up = vec3.cross(side, forward):normalize()

	local out = new()
	out[1]    = side.x
	out[5]    = side.y
	out[9]    = side.z
	out[2]    = new_up.x
	out[6]    = new_up.y
	out[10]   = new_up.z
	out[3]    = forward.x
	out[7]    = forward.y
	out[11]   = forward.z
	out[16]   = 1

	return out
end

--- Create a matrix from a transform.
-- @tparam vec3 trans Translation vector
-- @tparam quat rot Rotation quaternion
-- @tparam vec3 scale Scale vector
-- @treturn mat4 out
function mat4.from_transform(trans, rot, scale)
	local rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w

	local sm = new {
		scale.x, 0,       0,       0,
		0,       scale.y, 0,       0,
		0,       0,       scale.z, 0,
		0,       0,       0,       1,
	}

	local rm = new {
		1-2*(ry*ry+rz*rz), 2*(rx*ry-rz*rw), 2*(rx*rz+ry*rw), 0,
		2*(rx*ry+rz*rw), 1-2*(rx*rx+rz*rz), 2*(ry*rz-rx*rw), 0,
		2*(rx*rz-ry*rw), 2*(ry*rz+rx*rw), 1-2*(rx*rx+ry*ry), 0,
		0, 0, 0, 1
	}

	local rsm = rm * sm

	rsm[13] = trans.x
	rsm[14] = trans.y
	rsm[15] = trans.z

	return rsm
end

--- Create matrix from orthogonal.
-- @tparam number left
-- @tparam number right
-- @tparam number top
-- @tparam number bottom
-- @tparam number near
-- @tparam number far
-- @treturn mat4 out
function mat4.from_ortho(left, right, top, bottom, near, far)
	local out = new()
	out[1]    =  2 / (right - left)
	out[6]    =  2 / (top - bottom)
	out[11]   = -2 / (far - near)
	out[13]   = -((right + left) / (right - left))
	out[14]   = -((top + bottom) / (top - bottom))
	out[15]   = -((far + near) / (far - near))
	out[16]   =  1

	return out
end

--- Create matrix from perspective.
-- @tparam number fovy Field of view
-- @tparam number aspect Aspect ratio
-- @tparam number near Near plane
-- @tparam number far Far plane
-- @treturn mat4 out
function mat4.from_perspective(fovy, aspect, near, far)
	assert(aspect ~= 0)
	assert(near   ~= far)

	local t   = tan(rad(fovy) / 2)
	local out = new()
	out[1]    =  1 / (t * aspect)
	out[6]    =  1 / t
	out[11]   = -(far + near) / (far - near)
	out[12]   = -1
	out[15]   = -(2 * far * near) / (far - near)
	out[16]   =  0

	return out
end

-- Adapted from the Oculus SDK.
--- Create matrix from HMD perspective.
-- @tparam number tanHalfFov Tangent of half of the field of view
-- @tparam number zNear Near plane
-- @tparam number zFar Far plane
-- @tparam boolean flipZ Z axis is flipped or not
-- @tparam boolean farAtInfinity Far plane is infinite or not
-- @treturn mat4 out
function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)
	-- CPML is right-handed and intended for GL, so these don't need to be arguments.
	local rightHanded = true
	local isOpenGL    = true

	local function CreateNDCScaleAndOffsetFromFov(tanHalfFov)
		local x_scale  = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)
		local x_offset =     (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5
		local y_scale  = 2 / (tanHalfFov.UpTan   + tanHalfFov.DownTan )
		local y_offset =     (tanHalfFov.UpTan   - tanHalfFov.DownTan ) * y_scale * 0.5

		local result = {
			Scale  = vec2(x_scale, y_scale),
			Offset = vec2(x_offset, y_offset)
		}

		-- Hey - why is that Y.Offset negated?
		-- It's because a projection matrix transforms from world coords with Y=up,
		-- whereas this is from NDC which is Y=down.
		 return result
	end

	if not flipZ and farAtInfinity then
		print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped")
		farAtInfinity = false
	end

	 -- A projection matrix is very like a scaling from NDC, so we can start with that.
	local scaleAndOffset  = CreateNDCScaleAndOffsetFromFov(tanHalfFov)
	local handednessScale = rightHanded and -1.0 or 1.0
	local projection      = new()

	-- Produces X result, mapping clip edges to [-w,+w]
	projection[1] = scaleAndOffset.Scale.x
	projection[2] = 0
	projection[3] = handednessScale * scaleAndOffset.Offset.x
	projection[4] = 0

	-- Produces Y result, mapping clip edges to [-w,+w]
	-- Hey - why is that YOffset negated?
	-- It's because a projection matrix transforms from world coords with Y=up,
	-- whereas this is derived from an NDC scaling, which is Y=down.
	projection[5] = 0
	projection[6] = scaleAndOffset.Scale.y
	projection[7] = handednessScale * -scaleAndOffset.Offset.y
	projection[8] = 0

	-- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.
	-- We'll just use some defaults for now.
	projection[9]  = 0
	projection[10] = 0

	if farAtInfinity then
		if isOpenGL then
			-- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
			projection[11] = -handednessScale
			projection[12] = 2.0 * zNear
		else
			projection[11] = 0
			projection[12] = zNear
		end
	else
		if isOpenGL then
			-- Clip range is [-w,+w], so 0 is at the middle of the range.
			projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
			projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
		else
			-- Clip range is [0,+w], so 0 is at the start of the range.
			projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)
			projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
		end
	end

	-- Produces W result (= Z in)
	projection[13] = 0
	projection[14] = 0
	projection[15] = handednessScale
	projection[16] = 0

	return projection:transpose(projection)
end

--- Clone a matrix.
-- @tparam mat4 a Matrix to clone
-- @treturn mat4 out
function mat4.clone(a)
	return new(a)
end

function mul_internal(out, a, b)
	tm4[1]  = b[1]  * a[1] + b[2]  * a[5] + b[3]  * a[9]  + b[4]  * a[13]
	tm4[2]  = b[1]  * a[2] + b[2]  * a[6] + b[3]  * a[10] + b[4]  * a[14]
	tm4[3]  = b[1]  * a[3] + b[2]  * a[7] + b[3]  * a[11] + b[4]  * a[15]
	tm4[4]  = b[1]  * a[4] + b[2]  * a[8] + b[3]  * a[12] + b[4]  * a[16]
	tm4[5]  = b[5]  * a[1] + b[6]  * a[5] + b[7]  * a[9]  + b[8]  * a[13]
	tm4[6]  = b[5]  * a[2] + b[6]  * a[6] + b[7]  * a[10] + b[8]  * a[14]
	tm4[7]  = b[5]  * a[3] + b[6]  * a[7] + b[7]  * a[11] + b[8]  * a[15]
	tm4[8]  = b[5]  * a[4] + b[6]  * a[8] + b[7]  * a[12] + b[8]  * a[16]
	tm4[9]  = b[9]  * a[1] + b[10] * a[5] + b[11] * a[9]  + b[12] * a[13]
	tm4[10] = b[9]  * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
	tm4[11] = b[9]  * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
	tm4[12] = b[9]  * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
	tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9]  + b[16] * a[13]
	tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
	tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
	tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]

	for i = 1, 16 do
		out[i] = tm4[i]
	end
end

--- Multiply N matrices.
-- @tparam mat4 out Matrix to store the result
-- @tparam mat4 or {mat4, ...} left hand operand(s)
-- @tparam mat4 right hand operand if a is not table
-- @treturn mat4 out multiplied matrix result
function mat4.mul(out, a, b)
	if mat4.is_mat4(a) then
		mul_internal(out, a, b)
		return out
	end
	if #a == 0 then
		identity(out)
	elseif #a == 1 then
		-- only one matrix, just copy
		for i = 1, 16 do
			out[i] = a[1][i]
		end
	else
		local ma = a[1]
		local mb = a[2]
		for i = 2, #a do
			mul_internal(out, ma, mb)
			ma = out
		end
	end
	return out
end

--- Multiply a matrix and a vec3, with perspective division.
-- This function uses an implicit 1 for the fourth component.
-- @tparam vec3 out vec3 to store the result
-- @tparam mat4 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3 out
function mat4.mul_vec3_perspective(out, a, b)
	local v4x = b.x * a[1] + b.y * a[5] + b.z * a[9]  + a[13]
	local v4y = b.x * a[2] + b.y * a[6] + b.z * a[10] + a[14]
	local v4z = b.x * a[3] + b.y * a[7] + b.z * a[11] + a[15]
	local v4w = b.x * a[4] + b.y * a[8] + b.z * a[12] + a[16]
	local inv_w = 0
	if v4w ~= 0 then
		inv_w = utils.sign(v4w) / v4w
	end
	out.x = v4x * inv_w
	out.y = v4y * inv_w
	out.z = v4z * inv_w
	return out
end

--- Multiply a matrix and a vec4.
-- @tparam table out table to store the result
-- @tparam mat4 a Left hand operand
-- @tparam table b Right hand operand
-- @treturn vec4 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]
	tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
	tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
	tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]

	for i = 1, 4 do
		out[i] = tv4[i]
	end

	return out
end

--- Invert a matrix.
-- @tparam mat4 out Matrix to store the result
-- @tparam mat4 a Matrix to invert
-- @treturn mat4 out
function mat4.invert(out, a)
	tm4[1]  =  a[6] * a[11] * a[16] - a[6] * a[12] * a[15] - a[10] * a[7] * a[16] + a[10] * a[8] * a[15] + a[14] * a[7] * a[12] - a[14] * a[8] * a[11]
	tm4[2]  = -a[2] * a[11] * a[16] + a[2] * a[12] * a[15] + a[10] * a[3] * a[16] - a[10] * a[4] * a[15] - a[14] * a[3] * a[12] + a[14] * a[4] * a[11]
	tm4[3]  =  a[2] * a[7]  * a[16] - a[2] * a[8]  * a[15] - a[6]  * a[3] * a[16] + a[6]  * a[4] * a[15] + a[14] * a[3] * a[8]  - a[14] * a[4] * a[7]
	tm4[4]  = -a[2] * a[7]  * a[12] + a[2] * a[8]  * a[11] + a[6]  * a[3] * a[12] - a[6]  * a[4] * a[11] - a[10] * a[3] * a[8]  + a[10] * a[4] * a[7]
	tm4[5]  = -a[5] * a[11] * a[16] + a[5] * a[12] * a[15] + a[9]  * a[7] * a[16] - a[9]  * a[8] * a[15] - a[13] * a[7] * a[12] + a[13] * a[8] * a[11]
	tm4[6]  =  a[1] * a[11] * a[16] - a[1] * a[12] * a[15] - a[9]  * a[3] * a[16] + a[9]  * a[4] * a[15] + a[13] * a[3] * a[12] - a[13] * a[4] * a[11]
	tm4[7]  = -a[1] * a[7]  * a[16] + a[1] * a[8]  * a[15] + a[5]  * a[3] * a[16] - a[5]  * a[4] * a[15] - a[13] * a[3] * a[8]  + a[13] * a[4] * a[7]
	tm4[8]  =  a[1] * a[7]  * a[12] - a[1] * a[8]  * a[11] - a[5]  * a[3] * a[12] + a[5]  * a[4] * a[11] + a[9]  * a[3] * a[8]  - a[9]  * a[4] * a[7]
	tm4[9]  =  a[5] * a[10] * a[16] - a[5] * a[12] * a[14] - a[9]  * a[6] * a[16] + a[9]  * a[8] * a[14] + a[13] * a[6] * a[12] - a[13] * a[8] * a[10]
	tm4[10] = -a[1] * a[10] * a[16] + a[1] * a[12] * a[14] + a[9]  * a[2] * a[16] - a[9]  * a[4] * a[14] - a[13] * a[2] * a[12] + a[13] * a[4] * a[10]
	tm4[11] =  a[1] * a[6]  * a[16] - a[1] * a[8]  * a[14] - a[5]  * a[2] * a[16] + a[5]  * a[4] * a[14] + a[13] * a[2] * a[8]  - a[13] * a[4] * a[6]
	tm4[12] = -a[1] * a[6]  * a[12] + a[1] * a[8]  * a[10] + a[5]  * a[2] * a[12] - a[5]  * a[4] * a[10] - a[9]  * a[2] * a[8]  + a[9]  * a[4] * a[6]
	tm4[13] = -a[5] * a[10] * a[15] + a[5] * a[11] * a[14] + a[9]  * a[6] * a[15] - a[9]  * a[7] * a[14] - a[13] * a[6] * a[11] + a[13] * a[7] * a[10]
	tm4[14] =  a[1] * a[10] * a[15] - a[1] * a[11] * a[14] - a[9]  * a[2] * a[15] + a[9]  * a[3] * a[14] + a[13] * a[2] * a[11] - a[13] * a[3] * a[10]
	tm4[15] = -a[1] * a[6]  * a[15] + a[1] * a[7]  * a[14] + a[5]  * a[2] * a[15] - a[5]  * a[3] * a[14] - a[13] * a[2] * a[7]  + a[13] * a[3] * a[6]
	tm4[16] =  a[1] * a[6]  * a[11] - a[1] * a[7]  * a[10] - a[5]  * a[2] * a[11] + a[5]  * a[3] * a[10] + a[9]  * a[2] * a[7]  - a[9]  * a[3] * a[6]

	local det = a[1] * tm4[1] + a[2] * tm4[5] + a[3] * tm4[9] + a[4] * tm4[13]

	if det == 0 then return a end

	det = 1 / det

	for i = 1, 16 do
		out[i] = tm4[i] * det
	end

	return out
end

--- Scale a matrix.
-- @tparam mat4 out Matrix to store the result
-- @tparam mat4 a Matrix to scale
-- @tparam vec3 s Scalar
-- @treturn mat4 out
function mat4.scale(out, a, s)
	identity(tmp)
	tmp[1]  = s.x
	tmp[6]  = s.y
	tmp[11] = s.z

	return out:mul(tmp, a)
end

--- Rotate a matrix.
-- @tparam mat4 out Matrix to store the result
-- @tparam mat4 a Matrix to rotate
-- @tparam number angle Angle to rotate by (in radians)
-- @tparam vec3 axis Axis to rotate on
-- @treturn mat4 out
function mat4.rotate(out, a, angle, axis)
	if type(angle) == "table" or type(angle) == "cdata" then
		angle, axis = angle:to_angle_axis()
	end

	local l = axis:len()

	if l == 0 then
		return a
	end

	local x, y, z = axis.x / l, axis.y / l, axis.z / l
	local c = cos(angle)
	local s = sin(angle)

	identity(tmp)
	tmp[1]  = x * x * (1 - c) + c
	tmp[2]  = y * x * (1 - c) + z * s
	tmp[3]  = x * z * (1 - c) - y * s
	tmp[5]  = x * y * (1 - c) - z * s
	tmp[6]  = y * y * (1 - c) + c
	tmp[7]  = y * z * (1 - c) + x * s
	tmp[9]  = x * z * (1 - c) + y * s
	tmp[10] = y * z * (1 - c) - x * s
	tmp[11] = z * z * (1 - c) + c

	return out:mul(tmp, a)
end

--- Translate a matrix.
-- @tparam mat4 out Matrix to store the result
-- @tparam mat4 a Matrix to translate
-- @tparam vec3 t Translation vector
-- @treturn mat4 out
function mat4.translate(out, a, t)
	identity(tmp)
	tmp[13] = t.x
	tmp[14] = t.y
	tmp[15] = t.z

	return out:mul(tmp, a)
end

--- Shear a matrix.
-- @tparam mat4 out Matrix to store the result
-- @tparam mat4 a Matrix to translate
-- @tparam number yx
-- @tparam number zx
-- @tparam number xy
-- @tparam number zy
-- @tparam number xz
-- @tparam number yz
-- @treturn mat4 out
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
	identity(tmp)
	tmp[2]  = yx or 0
	tmp[3]  = zx or 0
	tmp[5]  = xy or 0
	tmp[7]  = zy or 0
	tmp[9]  = xz or 0
	tmp[10] = yz or 0

	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 vec3 eye Location of viewer's view plane
-- @tparam vec3 center Location of object to view
-- @tparam vec3 up Up direction
-- @treturn mat4 out
function mat4.look_at(out, 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] = -out[  1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z
	out[14] = -out[  2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z
	out[15] = -out[  3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z
	out[16] = -out[  4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1
	return out
end

--- Transform matrix to target a point.
-- @tparam mat4 out Matrix to store result
-- @tparam vec3 eye Location of viewer's view plane
-- @tparam vec3 center Location of object to view
-- @tparam vec3 up Up direction
-- @treturn mat4 out
function mat4.target(out, from, to, up)
	local z_axis = (from - to):normalize()
	local x_axis = up:cross(z_axis):normalize()
	local y_axis = z_axis:cross(x_axis)
	out[1] = x_axis.x
	out[2] = x_axis.y
	out[3] = x_axis.z
	out[4] = 0
	out[5] = y_axis.x
	out[6] = y_axis.y
	out[7] = y_axis.z
	out[8] = 0
	out[9] = z_axis.x
	out[10] = z_axis.y
	out[11] = z_axis.z
	out[12] = 0
	out[13] = from.x
	out[14] = from.y
	out[15] = from.z
	out[16] = 1
	return out
end

--- Transpose a matrix.
-- @tparam mat4 out Matrix to store the result
-- @tparam mat4 a Matrix to transpose
-- @treturn mat4 out
function mat4.transpose(out, a)
	tm4[1]  = a[1]
	tm4[2]  = a[5]
	tm4[3]  = a[9]
	tm4[4]  = a[13]
	tm4[5]  = a[2]
	tm4[6]  = a[6]
	tm4[7]  = a[10]
	tm4[8]  = a[14]
	tm4[9]  = a[3]
	tm4[10] = a[7]
	tm4[11] = a[11]
	tm4[12] = a[15]
	tm4[13] = a[4]
	tm4[14] = a[8]
	tm4[15] = a[12]
	tm4[16] = a[16]

	for i = 1, 16 do
		out[i] = tm4[i]
	end

	return out
end

--- Project a point into screen space
-- @tparam vec3 obj Object position in world space
-- @tparam mat4 mvp Projection matrix
-- @tparam table viewport XYWH of viewport
-- @treturn vec3 win
function mat4.project(obj, mvp, viewport)
	local point = mat4.mul_vec3_perspective(vec3(), mvp, obj)
	point.x = point.x * 0.5 + 0.5
	point.y = point.y * 0.5 + 0.5
	point.z = point.z * 0.5 + 0.5
	point.x = point.x * viewport[3] + viewport[1]
	point.y = point.y * viewport[4] + viewport[2]
	return point
end

--- Unproject a point from screen space to world space.
-- @tparam vec3 win Object position in screen space
-- @tparam mat4 mvp Projection matrix
-- @tparam table viewport XYWH of viewport
-- @treturn vec3 obj
function mat4.unproject(win, mvp, viewport)
	local point = vec3.clone(win)

	-- 0..n -> 0..1
	point.x = (point.x - viewport[1]) / viewport[3]
	point.y = (point.y - viewport[2]) / viewport[4]

	-- 0..1 -> -1..1
	point.x = point.x * 2 - 1
	point.y = point.y * 2 - 1
	point.z = point.z * 2 - 1

	return mat4.mul_vec3_perspective(point, tmp:invert(mvp), point)
end

--- Return a boolean showing if a table is or is not a mat4.
-- @tparam mat4 a Matrix to be tested
-- @treturn boolean is_mat4
function mat4.is_mat4(a)
	if type(a) == "cdata" then
		return ffi.istype("cpml_mat4", a)
	end

	if type(a) ~= "table" then
		return false
	end

	for i = 1, 16 do
		if type(a[i]) ~= "number" then
			return false
		end
	end

	return true
end

--- Return whether any component is NaN
-- @tparam mat4 a Matrix to be tested
-- @treturn boolean if any component is NaN
function vec2.has_nan(a)
	for i = 1, 16 do
		if private.is_nan(a[i]) then
			return true
		end
	end
	return false
end

--- Return a formatted string.
-- @tparam mat4 a Matrix to be turned into a string
-- @treturn string formatted
function mat4.to_string(a)
	local str = "[ "
	for i = 1, 16 do
		str = str .. string.format("%+0.3f", a[i])
		if i < 16 then
			str = str .. ", "
		end
	end
	str = str .. " ]"
	return str
end

--- Convert a matrix to row vec4s.
-- @tparam mat4 a Matrix to be converted
-- @treturn table vec4s
function mat4.to_vec4s(a)
	return {
		{ a[1],  a[2],  a[3],  a[4]  },
		{ a[5],  a[6],  a[7],  a[8]  },
		{ a[9],  a[10], a[11], a[12] },
		{ a[13], a[14], a[15], a[16] }
	}
end

--- Convert a matrix to col vec4s.
-- @tparam mat4 a Matrix to be converted
-- @treturn table vec4s
function mat4.to_vec4s_cols(a)
	return {
		{ a[1], a[5], a[9],  a[13] },
		{ a[2], a[6], a[10], a[14] },
		{ a[3], a[7], a[11], a[15] },
		{ a[4], a[8], a[12], a[16] }
	}
end

-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
--- Convert a matrix to a quaternion.
-- @tparam mat4 a Matrix to be converted
-- @treturn quat out
function mat4.to_quat(a)
	identity(tmp):transpose(a)

	local w     = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
	local scale = w * 4
	local q     = quat.new(
		tmp[10] - tmp[7] / scale,
		tmp[3]  - tmp[9] / scale,
		tmp[5]  - tmp[2] / scale,
		w
	)

	return q:normalize(q)
end

-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
--- Convert a matrix to a frustum.
-- @tparam mat4 a Matrix to be converted (projection * view)
-- @tparam boolean infinite Infinite removes the far plane
-- @treturn frustum out
function mat4.to_frustum(a, infinite)
	local t
	local frustum = {}

	-- Extract the LEFT plane
	frustum.left   = {}
	frustum.left.a = a[4]  + a[1]
	frustum.left.b = a[8]  + a[5]
	frustum.left.c = a[12] + a[9]
	frustum.left.d = a[16] + a[13]

	-- Normalize the result
	t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
	frustum.left.a = frustum.left.a / t
	frustum.left.b = frustum.left.b / t
	frustum.left.c = frustum.left.c / t
	frustum.left.d = frustum.left.d / t

	-- Extract the RIGHT plane
	frustum.right   = {}
	frustum.right.a = a[4]  - a[1]
	frustum.right.b = a[8]  - a[5]
	frustum.right.c = a[12] - a[9]
	frustum.right.d = a[16] - a[13]

	-- Normalize the result
	t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
	frustum.right.a = frustum.right.a / t
	frustum.right.b = frustum.right.b / t
	frustum.right.c = frustum.right.c / t
	frustum.right.d = frustum.right.d / t

	-- Extract the BOTTOM plane
	frustum.bottom   = {}
	frustum.bottom.a = a[4]  + a[2]
	frustum.bottom.b = a[8]  + a[6]
	frustum.bottom.c = a[12] + a[10]
	frustum.bottom.d = a[16] + a[14]

	-- Normalize the result
	t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
	frustum.bottom.a = frustum.bottom.a / t
	frustum.bottom.b = frustum.bottom.b / t
	frustum.bottom.c = frustum.bottom.c / t
	frustum.bottom.d = frustum.bottom.d / t

	-- Extract the TOP plane
	frustum.top   = {}
	frustum.top.a = a[4]  - a[2]
	frustum.top.b = a[8]  - a[6]
	frustum.top.c = a[12] - a[10]
	frustum.top.d = a[16] - a[14]

	-- Normalize the result
	t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
	frustum.top.a = frustum.top.a / t
	frustum.top.b = frustum.top.b / t
	frustum.top.c = frustum.top.c / t
	frustum.top.d = frustum.top.d / t

	-- Extract the NEAR plane
	frustum.near   = {}
	frustum.near.a = a[4]  + a[3]
	frustum.near.b = a[8]  + a[7]
	frustum.near.c = a[12] + a[11]
	frustum.near.d = a[16] + a[15]

	-- Normalize the result
	t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
	frustum.near.a = frustum.near.a / t
	frustum.near.b = frustum.near.b / t
	frustum.near.c = frustum.near.c / t
	frustum.near.d = frustum.near.d / t

	if not infinite then
		-- Extract the FAR plane
		frustum.far   = {}
		frustum.far.a = a[4]  - a[3]
		frustum.far.b = a[8]  - a[7]
		frustum.far.c = a[12] - a[11]
		frustum.far.d = a[16] - a[15]

		-- Normalize the result
		t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
		frustum.far.a = frustum.far.a / t
		frustum.far.b = frustum.far.b / t
		frustum.far.c = frustum.far.c / t
		frustum.far.d = frustum.far.d / t
	end

	return frustum
end

function mat4_mt.__index(t, k)
	if type(t) == "cdata" then
		if type(k) == "number" then
			return t._m[k-1]
		end
	end

	return rawget(mat4, k)
end

function mat4_mt.__newindex(t, k, v)
	if type(t) == "cdata" then
		if type(k) == "number" then
			t._m[k-1] = v
		end
	end
end

mat4_mt.__tostring = mat4.to_string

function mat4_mt.__call(_, a)
	return mat4.new(a)
end

function mat4_mt.__unm(a)
	return new():invert(a)
end

function mat4_mt.__eq(a, b)
	if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
		return false
	end

	for i = 1, 16 do
		if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
			return false
		end
	end

	return true
end

function mat4_mt.__mul(a, b)
	precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)", type(a))

	if vec3.is_vec3(b) then
		return mat4.mul_vec3_perspective(vec3(), a, b)
	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)
	end

	return mat4.mul_vec4({}, a, b)
end

if status then
	xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
		ffi.metatype(new, mat4_mt)
	end, function() end)
end

return setmetatable({}, mat4_mt)


================================================
FILE: modules/mesh.lua
================================================
--- Mesh utilities
-- @module mesh

local modules = (...):gsub('%.[^%.]+$', '') .. "."
local vec3    = require(modules .. "vec3")
local mesh    = {}

-- vertices is an arbitrary list of vec3s
function mesh.average(vertices)
	local out = vec3()
	for _, v in ipairs(vertices) do
		out = out + v
	end
	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 = triangle[2] - triangle[1]
	local ca = triangle[3] - triangle[1]
	return ba:cross(ca):normalize()
end

-- triangle[1] is a vec3
-- triangle[2] is a vec3
-- triangle[3] is a vec3
function mesh.plane_from_triangle(triangle)
	return {
		origin = triangle[1],
		normal = mesh.normal(triangle)
	}
end

-- plane.origin is a vec3
-- plane.normal is a vec3
-- direction    is a vec3
function mesh.is_front_facing(plane, direction)
	return plane.normal:dot(direction) >= 0
end

-- point        is a vec3
-- plane.origin is a vec3
-- plane.normal is a vec3
-- plane.dot    is a number
function mesh.signed_distance(point, plane)
	return point:dot(plane.normal) - plane.normal:dot(plane.origin)
end

return mesh


================================================
FILE: modules/octree.lua
================================================
-- https://github.com/Nition/UnityOctree
-- https://github.com/Nition/UnityOctree/blob/master/LICENCE
-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctree.cs
-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctreeNode.cs

--- Octree
-- @module octree

local modules = (...):gsub('%.[^%.]+$', '') .. "."
local intersect  = require(modules .. "intersect")
local mat4       = require(modules .. "mat4")
local utils      = require(modules .. "utils")
local vec3       = require(modules .. "vec3")
local Octree     = {}
local OctreeNode = {}
local Node

Octree.__index     = Octree
OctreeNode.__index = OctreeNode

--== Octree ==--

--- Constructor for the bounds octree.
-- @param initialWorldSize Size of the sides of the initial node, in metres. The octree will never shrink smaller than this
-- @param initialWorldPos Position of the centre of the initial node
-- @param minNodeSize Nodes will stop splitting if the new nodes would be smaller than this (metres)
-- @param looseness Clamped between 1 and 2. Values > 1 let nodes overlap
local function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)
	local tree = setmetatable({}, Octree)

	if minNodeSize > initialWorldSize then
		print("Minimum node size must be at least as big as the initial world size. Was: " .. minNodeSize .. " Adjusted to: " .. initialWorldSize)
		minNodeSize = initialWorldSize
	end

	-- The total amount of objects currently in the tree
	tree.count = 0

	-- Size that the octree was on creation
	tree.initialSize = initialWorldSize

	-- Minimum side length that a node can be - essentially an alternative to having a max depth
	tree.minSize = minNodeSize

	-- Should be a value between 1 and 2. A multiplier for the base size of a node.
	-- 1.0 is a "normal" octree, while values > 1 have overlap
	tree.looseness = utils.clamp(looseness, 1, 2)

	-- Root node of the octree
	tree.rootNode = Node(tree.initialSize, tree.minSize, tree.looseness, initialWorldPos)

	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
function Octree:add(obj, objBounds)
	-- Add object or expand the octree until it can be added
	local count = 0 -- Safety check against infinite/excessive growth

	while not self.rootNode:add(obj, objBounds) do
		count = count + 1
		self:grow(objBounds.center - self.rootNode.center)

		if count > 20 then
			print("Aborted Add operation as it seemed to be going on forever (" .. count - 1 .. ") attempts at growing the octree.")
			return
		end

		self.count = self.count + 1
	end
end

--- Remove an object. Makes the assumption that the object only exists once in the tree.
-- @param obj Object to remove
-- @return bool True if the object was removed successfully
function Octree:remove(obj)
	local removed = self.rootNode:remove(obj)

	-- See if we can shrink the octree down now that we've removed the item
	if removed then
		self.count = self.count - 1
		self:shrink()
	end

	return removed
end

--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
-- @param checkBounds bounds to check
-- @return bool True if there was a collision
function Octree:is_colliding(checkBounds)
	return self.rootNode:is_colliding(checkBounds)
end

--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
-- @param checkBounds bounds to check
-- @return table Objects that intersect with the specified bounds
function Octree:get_colliding(checkBounds)
	return self.rootNode:get_colliding(checkBounds)
end

--- Cast a ray through the node and its children
-- @param ray Ray with a position and a direction
-- @param func Function to execute on any objects within child nodes
-- @param out Table to store results of func in
-- @return boolean True if an intersect detected
function Octree:cast_ray(ray, func, out)
	assert(func)
	return self.rootNode:cast_ray(ray, func, out)
end

--- Draws node boundaries visually for debugging.
function Octree:draw_bounds(cube)
	self.rootNode:draw_bounds(cube)
end

--- Draws the bounds of all objects in the tree visually for debugging.
function Octree:draw_objects(cube, filter)
	self.rootNode:draw_objects(cube, filter)
end

--- Grow the octree to fit in all objects.
-- @param direction Direction to grow
function Octree:grow(direction)
	local xDirection = direction.x >= 0 and 1 or -1
	local yDirection = direction.y >= 0 and 1 or -1
	local zDirection = direction.z >= 0 and 1 or -1

	local oldRoot   = self.rootNode
	local half      = self.rootNode.baseLength / 2
	local newLength = self.rootNode.baseLength * 2
	local newCenter = self.rootNode.center + vec3(xDirection * half, yDirection * half, zDirection * half)

	-- Create a new, bigger octree root node
	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  = get_root_pos_index(xDirection, yDirection, zDirection)
	local children = {}

	for i = 0, 7 do
		if i == rootPos then
			children[i+1] = oldRoot
		else
			xDirection  = i % 2 == 0 and -1 or 1
			yDirection  = i > 3 and -1 or 1
			zDirection  = (i < 2 or (i > 3 and i < 6)) and -1 or 1
			children[i+1] = Node(self.rootNode.baseLength, self.minSize, self.looseness, newCenter + vec3(xDirection * half, yDirection * half, zDirection * half))
		end
	end

	-- Attach the new children to the new root node
	self.rootNode:set_children(children)
end

--- Shrink the octree if possible, else leave it the same.
function Octree:shrink()
	self.rootNode = self.rootNode:shrink_if_possible(self.initialSize)
end

--== Octree Node ==--

--- Constructor.
-- @param baseLength Length of this node, not taking looseness into account
-- @param minSize Minimum size of nodes in this octree
-- @param looseness Multiplier for baseLengthVal to get the actual size
-- @param center Centre position of this node
local function new_node(baseLength, minSize, looseness, center)
	local node = setmetatable({}, OctreeNode)

	-- Objects in this node
	node.objects = {}

	-- Child nodes
	node.children = {}

	-- If there are already numObjectsAllowed in a node, we split it into children
	-- A generally good number seems to be something around 8-15
	node.numObjectsAllowed = 8

	node:set_values(baseLength, minSize, looseness, center)

	return node
end

local function new_bound(center, size)
	return {
		center = center,
		size   = size,
		min    = center - (size / 2),
		max    = center + (size / 2)
	}
end

--- Add an object.
-- @param obj Object to add
-- @param objBounds 3D bounding box around the object
-- @return boolean True if the object fits entirely within this node
function OctreeNode:add(obj, objBounds)
	if not intersect.encapsulate_aabb(self.bounds, objBounds) then
		return false
	end

	-- We know it fits at this level if we've got this far
	-- Just add if few objects are here, or children would be below min size
	if #self.objects < self.numObjectsAllowed
	or self.baseLength / 2 < self.minSize then
		table.insert(self.objects, {
			data   = obj,
			bounds = objBounds
		})
	else
		-- Fits at this level, but we can go deeper. Would it fit there?

		local best_fit_child

		-- Create the 8 children
		if #self.children == 0 then
			self:split()

			if #self.children == 0 then
				print("Child creation failed for an unknown reason. Early exit.")
				return false
			end

			-- Now that we have the new children, see if this node's existing objects would fit there
			for i = #self.objects, 1, -1 do
				local object = self.objects[i]
				-- Find which child the object is closest to based on where the
				-- object's center is located in relation to the octree's center.
				best_fit_child = self:best_fit_child(object.bounds)

				-- Does it fit?
				if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, object.bounds) then
					self.children[best_fit_child]:add(object.data, object.bounds) -- Go a level deeper
					table.remove(self.objects, i) -- Remove from here
				end
			end
		end

		-- Now handle the new object we're adding now
		best_fit_child = self:best_fit_child(objBounds)

		if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, objBounds) then
			self.children[best_fit_child]:add(obj, objBounds)
		else
			table.insert(self.objects, {
				data   = obj,
				bounds = objBounds
			})
		end
	end

	return true
end

--- Remove an object. Makes the assumption that the object only exists once in the tree.
-- @param obj Object to remove
-- @return boolean True if the object was removed successfully
function OctreeNode:remove(obj)
	local removed = false

	for i, object in ipairs(self.objects) do
		if object == obj then
			removed = table.remove(self.objects, i) and true or false
			break
		end
	end

	if not removed then
		for _, child in ipairs(self.children) do
			removed = child:remove(obj)
			if removed then break end
		end
	end

	if removed then
		-- Check if we should merge nodes now that we've removed an item
		if self:should_merge() then
			self:merge()
		end
	end

	return removed
end

--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
-- @param checkBounds Bounds to check
-- @return boolean True if there was a collision
function OctreeNode:is_colliding(checkBounds)
	-- Are the input bounds at least partially in this node?
	if not intersect.aabb_aabb(self.bounds, checkBounds) then
		return false
	end

	-- Check against any objects in this node
	for _, object in ipairs(self.objects) do
		if intersect.aabb_aabb(object.bounds, checkBounds) then
			return true
		end
	end

	-- Check children
	for _, child in ipairs(self.children) do
		if child:is_colliding(checkBounds) then
			return true
		end
	end

	return false
end

--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
-- @param checkBounds Bounds to check. Passing by ref as it improve performance with structs
-- @param results List results
-- @return table Objects that intersect with the specified bounds
function OctreeNode:get_colliding(checkBounds, results)
	results = results or {}

	-- Are the input bounds at least partially in this node?
	if not intersect.aabb_aabb(self.bounds, checkBounds) then
		return results
	end

	-- Check against any objects in this node
	for _, object in ipairs(self.objects) do
		if intersect.aabb_aabb(object.bounds, checkBounds) then
			table.insert(results, object.data)
		end
	end

	-- Check children
	for _, child in ipairs(self.children) do
		results = child:get_colliding(checkBounds, results)
	end

	return results
end

--- Cast a ray through the node and its children
-- @param ray Ray with a position and a direction
-- @param func Function to execute on any objects within child nodes
-- @param out Table to store results of func in
-- @param depth (used internally)
-- @return boolean True if an intersect is detected
function OctreeNode:cast_ray(ray, func, out, depth)
	depth = depth or 1

	if intersect.ray_aabb(ray, self.bounds) then
		if #self.objects > 0 then
			local hit = func(ray, self.objects, out)

			if hit then
				return hit
			end
		end

		for _, child in ipairs(self.children) do
			local hit = child:cast_ray(ray, func, out, depth + 1)

			if hit then
				return hit
			end
		end
	end

	return false
end

--- Set the 8 children of this octree.
-- @param childOctrees The 8 new child nodes
function OctreeNode:set_children(childOctrees)
	if #childOctrees ~= 8 then
		print("Child octree array must be length 8. Was length: " .. #childOctrees)
		return
	end

	self.children = childOctrees
end

--- We can shrink the octree if:
--- - This node is >= double minLength in length
--- - All objects in the root node are within one octant
--- - This node doesn't have children, or does but 7/8 children are empty
--- We can also shrink it if there are no objects left at all!
-- @param minLength Minimum dimensions of a node in this octree
-- @return table The new root, or the existing one if we didn't shrink
function OctreeNode:shrink_if_possible(minLength)
	if self.baseLength < 2 * minLength then
		return self
	end

	if #self.objects == 0 and #self.children == 0 then
		return self
	end

	-- Check objects in root
	local bestFit = 0

	for i, object in ipairs(self.objects) do
		local newBestFit = self:best_fit_child(object.bounds)

		if i == 1 or newBestFit == bestFit then
			-- In same octant as the other(s). Does it fit completely inside that octant?
			if intersect.encapsulate_aabb(self.childBounds[newBestFit], object.bounds) then
				if bestFit < 1 then
					bestFit = newBestFit
				end
			else
				-- Nope, so we can't reduce. Otherwise we continue
				return self
			end
		else
			return self -- Can't reduce - objects fit in different octants
		end
	end

	-- Check objects in children if there are any
	if #self.children > 0 then
		local childHadContent = false

		for i, child in ipairs(self.children) do
			if child:has_any_objects() then
				if childHadContent then
					return self -- Can't shrink - another child had content already
				end

				if bestFit > 0 and bestFit ~= i then
					return self -- Can't reduce - objects in root are in a different octant to objects in child
				end

				childHadContent = true
				bestFit = i
			end
		end
	end

	-- Can reduce
	if #self.children == 0 then
		-- We don't have any children, so just shrink this node to the new size
		-- We already know that everything will still fit in it
		self:set_values(self.baseLength / 2, self.minSize, self.looseness, self.childBounds[bestFit].center)
		return self
	end

	-- We have children. Use the appropriate child as the new root node
	return self.children[bestFit]
end

--- Set values for this node.
-- @param baseLength Length of this node, not taking looseness into account
-- @param minSize Minimum size of nodes in this octree
-- @param looseness Multiplier for baseLengthVal to get the actual size
-- @param center Centre position of this node
function OctreeNode:set_values(baseLength, minSize, looseness, center)
	-- Length of this node if it has a looseness of 1.0
	self.baseLength = baseLength

	-- Minimum size for a node in this octree
	self.minSize = minSize

	-- Looseness value for this node
	self.looseness = looseness

	-- Centre of this node
	self.center = center

	-- Actual length of sides, taking the looseness value into account
	self.adjLength = self.looseness * self.baseLength

	-- Create the bounding box.
	self.size = vec3(self.adjLength, self.adjLength, self.adjLength)

	-- Bounding box that represents this node
	self.bounds = new_bound(self.center, self.size)

	self.quarter           = self.baseLength / 4
	self.childActualLength = (self.baseLength / 2) * self.looseness
	self.childActualSize   = vec3(self.childActualLength, self.childActualLength, self.childActualLength)

	-- Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
	self.childBounds =  {
		new_bound(self.center + vec3(-self.quarter,  self.quarter, -self.quarter), self.childActualSize),
		new_bound(self.center + vec3( self.quarter,  self.quarter, -self.quarter), self.childActualSize),
		new_bound(self.center + vec3(-self.quarter,  self.quarter,  self.quarter), self.childActualSize),
		new_bound(self.center + vec3( self.quarter,  self.quarter,  self.quarter), self.childActualSize),
		new_bound(self.center + vec3(-self.quarter, -self.quarter, -self.quarter), self.childActualSize),
		new_bound(self.center + vec3( self.quarter, -self.quarter, -self.quarter), self.childActualSize),
		new_bound(self.center + vec3(-self.quarter, -self.quarter,  self.quarter), self.childActualSize),
		new_bound(self.center + vec3( self.quarter, -self.quarter,  self.quarter), self.childActualSize)
	}
end

--- Splits the octree into eight children.
function OctreeNode:split()
	if #self.children > 0 then return end

	local quarter   = self.baseLength / 4
	local newLength = self.baseLength / 2

	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter,  quarter, -quarter)))
	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter,  quarter, -quarter)))
	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter,  quarter,  quarter)))
	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter,  quarter,  quarter)))
	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, -quarter)))
	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, -quarter)))
	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter,  quarter)))
	table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter,  quarter)))
end

--- Merge all children into this node - the opposite of Split.
--- Note: We only have to check one level down since a merge will never happen if the children already have children,
--- since THAT won't happen unless there are already too many objects to merge.
function OctreeNode:merge()
	for _, child in ipairs(self.children) do
		for _, object in ipairs(child.objects) do
			table.insert(self.objects, object)
		end
	end

	-- Remove the child nodes (and the objects in them - they've been added elsewhere now)
	self.children = {}
end

--- Find which child node this object would be most likely to fit in.
-- @param objBounds The object's bounds
-- @return number One of the eight child octants
function OctreeNode:best_fit_child(objBounds)
	return (objBounds.center.x <= self.center.x and 0 or 1) + (objBounds.center.y >= self.center.y and 0 or 4) + (objBounds.center.z <= self.center.z and 0 or 2) + 1
end

--- Checks if there are few enough objects in this node and its children that the children should all be merged into this.
-- @return boolean True there are less or the same abount of objects in this and its children than numObjectsAllowed
function OctreeNode:should_merge()
	local totalObjects = #self.objects

	for _, child in ipairs(self.children) do
		if #child.children > 0 then
			-- If any of the *children* have children, there are definitely too many to merge,
			-- or the child would have been merged already
			return false
		end

		totalObjects = totalObjects + #child.objects
	end

	return totalObjects <= self.numObjectsAllowed
end

--- Checks if this node or anything below it has something in it.
-- @return boolean True if this node or any of its children, grandchildren etc have something in the
function OctreeNode:has_any_objects()
	if #self.objects > 0 then return true end

	for _, child in ipairs(self.children) do
		if child:has_any_objects() then return true end
	end

	return false
end

--- Draws node boundaries visually for debugging.
-- @param cube Cube model to draw
-- @param depth Used for recurcive calls to this method
function OctreeNode:draw_bounds(cube, depth)
	depth = depth or 0
	local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically

	love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
	local m = mat4()
		:translate(self.center)
		:scale(vec3(self.adjLength, self.adjLength, self.adjLength))

	love.graphics.updateMatrix("transform", m)
	love.graphics.setWireframe(true)
	love.graphics.draw(cube)
	love.graphics.setWireframe(false)

	for _, child in ipairs(self.children) do
		child:draw_bounds(cube, depth + 1)
	end

	love.graphics.setColor(255, 255, 255)
end

--- Draws the bounds of all objects in the tree visually for debugging.
-- @param cube Cube model to draw
-- @param filter a function returning true or false to determine visibility.
function OctreeNode:draw_objects(cube, filter)
	local tint = self.baseLength / 20
	love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)

	for _, object in ipairs(self.objects) do
		if filter and filter(object.data) or not filter then
			local m = mat4()
				:translate(object.bounds.center)
				:scale(object.bounds.size)

			love.graphics.updateMatrix("transform", m)
			love.graphics.draw(cube)
		end
	end

	for _, child in ipairs(self.children) do
		child:draw_objects(cube, filter)
	end

	love.graphics.setColor(255, 255, 255)
end

Node = setmetatable({
	new = new_node
}, {
	__call = function(_, ...) return new_node(...) end
})

return setmetatable({
	new = new
}, {
	__call = function(_, ...) return new(...) end
})


================================================
FILE: modules/quat.lua
================================================
--- A quaternion and associated utilities.
-- @module quat

local modules       = (...):gsub('%.[^%.]+$', '') .. "."
local constants     = require(modules .. "constants")
local vec3          = require(modules .. "vec3")
local precond       = require(modules .. "_private_precond")
local private       = require(modules .. "_private_utils")
local DOT_THRESHOLD = constants.DOT_THRESHOLD
local DBL_EPSILON   = constants.DBL_EPSILON
local acos          = math.acos
local cos           = math.cos
local sin           = math.sin
local min           = math.min
local max           = math.max
local sqrt          = math.sqrt
local quat          = {}
local quat_mt       = {}

-- Private constructor.
local function new(x, y, z, w)
	return setmetatable({
		x = x or 0,
		y = y or 0,
		z = z or 0,
		w = w or 1
	}, quat_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 { 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
-- @field zero Empty quaternion
quat.unit = new(0, 0, 0, 1)
quat.zero = new(0, 0, 0, 0)

--- The public constructor.
-- @param x Can be of two types: </br>
-- number x X component
-- table {x, y, z, w} or {x=x, y=y, z=z, w=w}
-- @tparam number y Y component
-- @tparam number z Z component
-- @tparam number w W component
-- @treturn quat out
function quat.new(x, y, z, w)
	-- number, number, number, number
	if x and y and z and w then
		precond.typeof(x, "number", "new: Wrong argument type for x")
		precond.typeof(y, "number", "new: Wrong argument type for y")
		precond.typeof(z, "number", "new: Wrong argument type for z")
		precond.typeof(w, "number", "new: Wrong argument type for w")

		return new(x, y, z, w)

	-- {x, y, z, w} or {x=x, y=y, z=z, w=w}
	elseif type(x) == "table" or type (x) == "cdata" then
		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]
		precond.typeof(xx, "number", "new: Wrong argument type for x")
		precond.typeof(yy, "number", "new: Wrong argument type for y")
		precond.typeof(zz, "number", "new: Wrong argument type for z")
		precond.typeof(ww, "number", "new: Wrong argument type for w")

		return new(xx, yy, zz, ww)

	else
		precond.assert(x == nil, "new: Wrong arguments")
		return new(0, 0, 0, 1)
	end
end

--- Create a quaternion from an angle/axis pair.
-- @tparam number angle Angle (in radians)
-- @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, 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 (optional)
-- @treturn quat out
function quat.from_direction(normal, up)
	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

--- Clone a quaternion.
-- @tparam quat a Quaternion to clone
-- @treturn quat out
function quat.clone(a)
	return new(a.x, a.y, a.z, a.w)
end

--- Add two quaternions.
-- @tparam quat a Left hand operand
-- @tparam quat b Right hand operand
-- @treturn quat 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 a Left hand operand
-- @tparam quat b Right hand operand
-- @treturn quat 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 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 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3 out
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(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

	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 a Quaternion to normalize
-- @treturn quat out
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 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
end

--- Return the length of a quaternion.
-- @tparam quat a Quaternion to get length of
-- @treturn number len
function quat.len(a)
	return sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w)
end

--- Return the squared length of a quaternion.
-- @tparam quat a Quaternion to get length of
-- @treturn number len
function quat.len2(a)
	return a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w
end

--- Multiply a quaternion by a scalar.
-- @tparam quat a Left hand operand
-- @tparam number s Right hand operand
-- @treturn quat 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)
-- @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, a3, a4)
	return quat.from_angle_axis(angle, axis, a3, a4)
end

--- Return the conjugate of a quaternion.
-- @tparam quat a Quaternion to conjugate
-- @treturn quat out
function quat.conjugate(a)
	return new(-a.x, -a.y, -a.z, a.w)
end

--- Return the inverse of a quaternion.
-- @tparam quat a Quaternion to invert
-- @treturn quat 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 a Quaternion to reciprocate
-- @treturn quat out
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 a Left hand operand
-- @tparam quat b Right hand operand
-- @tparam number s Step value
-- @treturn quat out
function quat.lerp(a, b, s)
	return (a + (b - a) * s):normalize()
end

--- Slerp between two quaternions.
-- @tparam quat a Left hand operand
-- @tparam quat b Right hand operand
-- @tparam number s Step value
-- @treturn quat out
function quat.slerp(a, b, s)
	local dot = a:dot(b)

	if dot < 0 then
		a   = -a
		dot = -dot
	end

	if dot > DOT_THRESHOLD then
		return a:lerp(b, s)
	end

	dot = min(max(dot, -1), 1)

	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.
-- @tparam quat a Quaternion to unpack
-- @treturn number x
-- @treturn number y
-- @treturn number z
-- @treturn number w
function quat.unpack(a)
	return a.x, a.y, a.z, a.w
end

--- Return a boolean showing if a table is or is not a quat.
-- @tparam quat a Quaternion to be tested
-- @treturn boolean is_quat
function quat.is_quat(a)
	if type(a) == "cdata" then
		return ffi.istype("cpml_quat", a)
	end

	return
		type(a)   == "table"  and
		type(a.x) == "number" and
		type(a.y) == "number" and
		type(a.z) == "number" and
		type(a.w) == "number"
end

--- Return a boolean showing if a table is or is not a zero quat.
-- @tparam quat a Quaternion to be tested
-- @treturn boolean is_zero
function quat.is_zero(a)
	return
		a.x == 0 and
		a.y == 0 and
		a.z == 0 and
		a.w == 0
end

--- Return a boolean showing if a table is or is not a real quat.
-- @tparam quat a Quaternion to be tested
-- @treturn boolean is_real
function quat.is_real(a)
	return
		a.x == 0 and
		a.y == 0 and
		a.z == 0
end

--- Return a boolean showing if a table is or is not an imaginary quat.
-- @tparam quat a Quaternion to be tested
-- @treturn boolean is_imaginary
function quat.is_imaginary(a)
	return a.w == 0
end

--- Return whether any component is NaN
-- @tparam quat a Quaternion to be tested
-- @treturn boolean if x,y,z, or w is NaN
function quat.has_nan(a)
	return private.is_nan(a.x) or
		private.is_nan(a.y) or
		private.is_nan(a.z) or
		private.is_nan(a.w)
end

--- Convert a quaternion into an angle plus axis components.
-- @tparam quat a Quaternion to convert
-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,0,0,1)
-- @treturn number angle
-- @treturn x axis-x
-- @treturn y axis-y
-- @treturn z axis-z
function quat.to_angle_axis_unpack(a, identityAxis)
	if a.w > 1 or a.w < -1 then
		a = a:normalize()
	end

	-- If length of xyz components is less than DBL_EPSILON, this is zero or close enough (an identity quaternion)
	-- Normally an identity quat would return a nonsense answer, so we return an arbitrary zero rotation early.
	-- FIXME: Is it safe to assume there are *no* valid quaternions with nonzero degenerate lengths?
	if a.x*a.x + a.y*a.y + a.z*a.z < constants.DBL_EPSILON*constants.DBL_EPSILON then
		if identityAxis then
			return 0,identityAxis:unpack()
		else
			return 0,0,0,1
		end
	end

	local x, y, z
	local angle = 2 * acos(a.w)
	local s     = sqrt(1 - a.w * a.w)

	if s < DBL_EPSILON then
		x = a.x
		y = a.y
		z = a.z
	else
		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
-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,vec3(0,0,1))
-- @treturn number angle
-- @treturn vec3 axis
function quat.to_angle_axis(a, identityAxis)
	local angle, x, y, z = a:to_angle_axis_unpack(identityAxis)
	return angle, vec3(x, y, z)
end

--- Convert a quaternion into a vec3.
-- @tparam quat a Quaternion to convert
-- @treturn vec3 out
function quat.to_vec3(a)
	return vec3(a.x, a.y, a.z)
end

--- Return a formatted string.
-- @tparam quat a Quaternion to be turned into a string
-- @treturn string formatted
function quat.to_string(a)
	return string.format("(%+0.3f,%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z, a.w)
end

quat_mt.__index    = quat
quat_mt.__tostring = quat.to_string

function quat_mt.__call(_, x, y, z, w)
	return quat.new(x, y, z, w)
end

function quat_mt.__unm(a)
	return a:scale(-1)
end

function quat_mt.__eq(a,b)
	if not quat.is_quat(a) or not quat.is_quat(b) then
		return false
	end
	return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w
end

function quat_mt.__add(a, b)
	precond.assert(quat.is_quat(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
	precond.assert(quat.is_quat(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
	return a:add(b)
end

function quat_mt.__sub(a, b)
	precond.assert(quat.is_quat(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
	precond.assert(quat.is_quat(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
	return a:sub(b)
end

function quat_mt.__mul(a, b)
	precond.assert(quat.is_quat(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
	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 a:mul(b)
	end

	if type(b) == "number" then
		return a:scale(b)
	end

	return a:mul_vec3(b)
end

function quat_mt.__pow(a, n)
	precond.assert(quat.is_quat(a), "__pow: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
	precond.typeof(n, "number", "__pow: Wrong argument type for right hand operand.")
	return a:pow(n)
end

if status then
	xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
		ffi.metatype(new, quat_mt)
	end, function() end)
end

return setmetatable({}, quat_mt)


================================================
FILE: modules/simplex.lua
================================================
--- Simplex Noise
-- @module simplex

--
-- Based on code in "Simplex noise demystified", by Stefan Gustavson
-- www.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
--
-- Thanks to Mike Pall for some cleanup and improvements (and for LuaJIT!)
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--
-- [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
--

if _G.love and _G.love.math then
	return love.math.noise
end

-- 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
	end
end

-- Modules --
local bit = require("bit")

-- Imports --
local band   = bit.band
local bor    = bit.bor
local floor  = math.floor
local lshift = bit.lshift
local max    = math.max
local rshift = bit.rshift

-- Permutation of 0-255, replicated to allow easy indexing with sums of two bytes --
local Perms = ffi.new("uint8_t[512]", {
	151, 160, 137,  91,  90,  15, 131,  13, 201,  95,  96,  53, 194, 233,   7, 225,
	140,  36, 103,  30,  69, 142,   8,  99,  37, 240,  21,  10,  23, 190,   6, 148,
	247, 120, 234,  75,   0,  26, 197,  62,  94, 252, 219, 203, 117,  35,  11,  32,
	 57, 177,  33,  88, 237, 149,  56,  87, 174,  20, 125, 136, 171, 168,  68, 175,
	 74, 165,  71, 134, 139,  48,  27, 166,  77, 146, 158, 231,  83, 111, 229, 122,
	 60, 211, 133, 230, 220, 105,  92,  41,  55,  46, 245,  40, 244, 102, 143,  54,
	 65,  25,  63, 161,   1, 216,  80,  73, 209,  76, 132, 187, 208,  89,  18, 169,
	200, 196, 135, 130, 116, 188, 159,  86, 164, 100, 109, 198, 173, 186,   3,  64,
	 52, 217, 226, 250, 124, 123,   5, 202,  38, 147, 118, 126, 255,  82,  85, 212,
	207, 206,  59, 227,  47,  16,  58,  17, 182, 189,  28,  42, 223, 183, 170, 213,
	119, 248, 152,   2,  44, 154, 163,  70, 221, 153, 101, 155, 167,  43, 172,   9,
	129,  22,  39, 253,  19,  98, 108, 110,  79, 113, 224, 232, 178, 185, 112, 104,
	218, 246,  97, 228, 251,  34, 242, 193, 238, 210, 144,  12, 191, 179, 162, 241,
	 81,  51, 145, 235, 249,  14, 239, 107,  49, 192, 214,  31, 181, 199, 106, 157,
	184,  84, 204, 176, 115, 121,  50,  45, 127,   4, 150, 254, 138, 236, 205,  93,
	222, 114,  67,  29,  24,  72, 243, 141, 128, 195,  78,  66, 215,  61, 156, 180
})

-- The above, mod 12 for each element --
local Perms12 = ffi.new("uint8_t[512]")

for i = 0, 255 do
	local x = Perms[i] % 12

	Perms[i + 256], Perms12[i], Perms12[i + 256] = Perms[i], x, x
end

-- Gradients for 2D, 3D case --
local Grads3 = ffi.new("const double[12][3]",
	{ 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 },
	{ 1, 0, 1 }, { -1, 0, 1 }, { 1, 0, -1 }, { -1, 0, -1 },
	{ 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }
)

-- 2D weight contribution
local function GetN2(bx, by, x, y)
	local t = .5 - x * x - y * y
	local index = Perms12[bx + Perms[by]]

	return max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y)
end

local function simplex_2d(x, y)
	--[[
		2D skew factors:
		F = (math.sqrt(3) - 1) / 2
		G = (3 - math.sqrt(3)) / 6
		G2 = 2 * G - 1
	]]

	-- Skew the input space to determine which simplex cell we are in.
	local s = (x + y) * 0.366025403 -- F
	local ix, iy = floor(x + s), floor(y + s)

	-- Unskew the cell origin back to (x, y) space.
	local t = (ix + iy) * 0.211324865 -- G
	local x0 = x + t - ix
	local y0 = y + t - iy

	-- Calculate the contribution from the two fixed corners.
	-- A step of (1,0) in (i,j) means a step of (1-G,-G) in (x,y), and
	-- A step of (0,1) in (i,j) means a step of (-G,1-G) in (x,y).
	ix, iy = band(ix, 255), band(iy, 255)

	local n0 = GetN2(ix, iy, x0, y0)
	local n2 = GetN2(ix + 1, iy + 1, x0 - 0.577350270, y0 - 0.577350270) -- G2

	--[[
		Determine other corner based on simplex (equilateral triangle) we are in:
		if x0 > y0 then
			ix, x1 = ix + 1, x1 - 1
		else
			iy, y1 = iy + 1, y1 - 1
		end
	]]
	local xi = rshift(floor(y0 - x0), 31) -- y0 < x0
	local n1 = GetN2(ix + xi, iy + (1 - xi), x0 + 0.211324865 - xi, y0 - 0.788675135 + xi) -- x0 + G - xi, y0 + G - (1 - xi)

	-- Add contributions from each corner to get the final noise value.
	-- The result is scaled to return values in the interval [-1,1].
	return 70.1480580019 * (n0 + n1 + n2)
end

-- 3D weight contribution
local function GetN3(ix, iy, iz, x, y, z)
	local t = .6 - x * x - y * y - z * z
	local index = Perms12[ix + Perms[iy + Perms[iz]]]

	return max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y + Grads3[index][2] * z)
end

local function simplex_3d(x, y, z)
	--[[
		3D skew factors:
		F = 1 / 3
		G = 1 / 6
		G2 = 2 * G
		G3 = 3 * G - 1
	]]

	-- Skew the input space to determine which simplex cell we are in.
	local s = (x + y + z) * 0.333333333 -- F
	local ix, iy, iz = floor(x + s), floor(y + s), floor(z + s)

	-- Unskew the cell origin back to (x, y, z) space.
	local t = (ix + iy + iz) * 0.166666667 -- G
	local x0 = x + t - ix
	local y0 = y + t - iy
	local z0 = z + t - iz

	-- Calculate the contribution from the two fixed corners.
	-- A step of (1,0,0) in (i,j,k) means a step of (1-G,-G,-G) in (x,y,z);
	-- a step of (0,1,0) in (i,j,k) means a step of (-G,1-G,-G) in (x,y,z);
	-- a step of (0,0,1) in (i,j,k) means a step of (-G,-G,1-G) in (x,y,z).
	ix, iy, iz = band(ix, 255), band(iy, 255), band(iz, 255)

	local n0 = GetN3(ix, iy, iz, x0, y0, z0)
	local n3 = GetN3(ix + 1, iy + 1, iz + 1, x0 - 0.5, y0 - 0.5, z0 - 0.5) -- G3

	--[[
		Determine other corners based on simplex (skewed tetrahedron) we are in:

		if x0 >= y0 then -- ~A
			if y0 >= z0 then -- ~A and ~B
				i1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 1, 0
			elseif x0 >= z0 then -- ~A and B and ~C
				i1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 0, 1
			else -- ~A and B and C
				i1, j1, k1, i2, j2, k2 = 0, 0, 1, 1, 0, 1
			end
		else -- A
			if y0 < z0 then -- A and B
				i1, j1, k1, i2, j2, k2 = 0, 0, 1, 0, 1, 1
			elseif x0 < z0 then -- A and ~B and C
				i1, j1, k1, i2, j2, k2 = 0, 1, 0, 0, 1, 1
			else -- A and ~B and ~C
				i1, j1, k1, i2, j2, k2 = 0, 1, 0, 1, 1, 0
			end
		end
	]]

	local xLy = rshift(floor(x0 - y0), 31) -- x0 < y0
	local yLz = rshift(floor(y0 - z0), 31) -- y0 < z0
	local xLz = rshift(floor(x0 - z0), 31) -- x0 < z0

	local i1 = band(1 - xLy, bor(1 - yLz, 1 - xLz)) -- x0 >= y0 and (y0 >= z0 or x0 >= z0)
	local j1 = band(xLy, 1 - yLz) -- x0 < y0 and y0 >= z0
	local k1 = band(yLz, bor(xLy, xLz)) -- y0 < z0 and (x0 < y0 or x0 < z0)

	local i2 = bor(1 - xLy, band(1 - yLz, 1 - xLz)) -- x0 >= y0 or (y0 >= z0 and x0 >= z0)
	local j2 = bor(xLy, 1 - yLz) -- x0 < y0 or y0 >= z0
	local k2 = bor(band(1 - xLy, yLz), band(xLy, bor(yLz, xLz))) -- (x0 >= y0 and y0 < z0) or (x0 < y0 and (y0 < z0 or x0 < z0))

	local n1 = GetN3(ix + i1, iy + j1, iz + k1, x0 + 0.166666667 - i1, y0 + 0.166666667 - j1, z0 + 0.166666667 - k1) -- G
	local n2 = GetN3(ix + i2, iy + j2, iz + k2, x0 + 0.333333333 - i2, y0 + 0.333333333 - j2, z0 + 0.333333333 - k2) -- G2

	-- Add contributions from each corner to get the final noise value.
	-- The result is scaled to stay just inside [-1,1]
	return 28.452842 * (n0 + n1 + n2 + n3)
end

-- Gradients for 4D case --
local Grads4 = ffi.new("const double[32][4]",
	{ 0, 1, 1, 1 }, { 0, 1, 1, -1 }, { 0, 1, -1, 1 }, { 0, 1, -1, -1 },
	{ 0, -1, 1, 1 }, { 0, -1, 1, -1 }, { 0, -1, -1, 1 }, { 0, -1, -1, -1 },
	{ 1, 0, 1, 1 }, { 1, 0, 1, -1 }, { 1, 0, -1, 1 }, { 1, 0, -1, -1 },
	{ -1, 0, 1, 1 }, { -1, 0, 1, -1 }, { -1, 0, -1, 1 }, { -1, 0, -1, -1 },
	{ 1, 1, 0, 1 }, { 1, 1, 0, -1 }, { 1, -1, 0, 1 }, { 1, -1, 0, -1 },
	{ -1, 1, 0, 1 }, { -1, 1, 0, -1 }, { -1, -1, 0, 1 }, { -1, -1, 0, -1 },
	{ 1, 1, 1, 0 }, { 1, 1, -1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 },
	{ -1, 1, 1, 0 }, { -1, 1, -1, 0 }, { -1, -1, 1, 0 }, { -1, -1, -1, 0 }
)

-- 4D weight contribution
local function GetN4(ix, iy, iz, iw, x, y, z, w)
	local t = .6 - x * x - y * y - z * z - w * w
	local index = band(Perms[ix + Perms[iy + Perms[iz + Perms[iw]]]], 0x1F)

	return max(0, (t * t) * (t * t)) * (Grads4[index][0] * x + Grads4[index][1] * y + Grads4[index][2] * z + Grads4[index][3] * w)
end

-- A lookup table to traverse the simplex around a given point in 4D.
-- Details can be found where this table is used, in the 4D noise method.
local Simplex = ffi.new("uint8_t[64][4]",
	{ 0, 1, 2, 3 }, { 0, 1, 3, 2 }, {}, { 0, 2, 3, 1 }, {}, {}, {}, { 1, 2, 3 },
	{ 0, 2, 1, 3 }, {}, { 0, 3, 1, 2 }, { 0, 3, 2, 1 }, {}, {}, {}, { 1, 3, 2 },
	{}, {}, {}, {}, {}, {}, {}, {},
	{ 1, 2, 0, 3 }, {}, { 1, 3, 0, 2 }, {}, {}, {}, { 2, 3, 0, 1 }, { 2, 3, 1 },
	{ 1, 0, 2, 3 }, { 1, 0, 3, 2 }, {}, {}, {}, { 2, 0, 3, 1 }, {}, { 2, 1, 3 },
	{}, {}, {}, {}, {}, {}, {}, {},
	{ 2, 0, 1, 3 }, {}, {}, {}, { 3, 0, 1, 2 }, { 3, 0, 2, 1 }, {}, { 3, 1, 2 },
	{ 2, 1, 0, 3 }, {}, {}, {}, { 3, 1, 0, 2 }, {}, { 3, 2, 0, 1 }, { 3, 2, 1 }
)

-- Convert the above indices to masks that can be shifted / anded into offsets --
for i = 0, 63 do
	Simplex[i][0] = lshift(1, Simplex[i][0]) - 1
	Simplex[i][1] = lshift(1, Simplex[i][1]) - 1
	Simplex[i][2] = lshift(1, Simplex[i][2]) - 1
	Simplex[i][3] = lshift(1, Simplex[i][3]) - 1
end

local function simplex_4d(x, y, z, w)
	--[[
		4D skew factors:
		F = (math.sqrt(5) - 1) / 4
		G = (5 - math.sqrt(5)) / 20
		G2 = 2 * G
		G3 = 3 * G
		G4 = 4 * G - 1
	]]

	-- Skew the input space to determine which simplex cell we are in.
	local s = (x + y + z + w) * 0.309016994 -- F
	local ix, iy, iz, iw = floor(x + s), floor(y + s), floor(z + s), floor(w + s)

	-- Unskew the cell origin back to (x, y, z) space.
	local t = (ix + iy + iz + iw) * 0.138196601 -- G
	local x0 = x + t - ix
	local y0 = y + t - iy
	local z0 = z + t - iz
	local w0 = w + t - iw

	-- For the 4D case, the simplex is a 4D shape I won't even try to describe.
	-- To find out which of the 24 possible simplices we're in, we need to
	-- determine the magnitude ordering of x0, y0, z0 and w0.
	-- The method below is a good way of finding the ordering of x,y,z,w and
	-- then find the correct traversal order for the simplex we�re in.
	-- First, six pair-wise comparisons are performed between each possible pair
	-- of the four coordinates, and the results are used to add up binary bits
	-- for an integer index.
	local c1 = band(rshift(floor(y0 - x0), 26), 32)
	local c2 = band(rshift(floor(z0 - x0), 27), 16)
	local c3 = band(rshift(floor(z0 - y0), 28), 8)
	local c4 = band(rshift(floor(w0 - x0), 29), 4)
	local c5 = band(rshift(floor(w0 - y0), 30), 2)
	local c6 = rshift(floor(w0 - z0), 31)

	-- Simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
	-- Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
	-- impossible. Only the 24 indices which have non-zero entries make any sense.
	-- We use a thresholding to set the coordinates in turn from the largest magnitude.
	local c = c1 + c2 + c3 + c4 + c5 + c6

	-- The number 3 (i.e. bit 2) in the "simplex" array is at the position of the largest coordinate.
	local i1 = rshift(Simplex[c][0], 2)
	local j1 = rshift(Simplex[c][1], 2)
	local k1 = rshift(Simplex[c][2], 2)
	local l1 = rshift(Simplex[c][3], 2)

	-- The number 2 (i.e. bit 1) in the "simplex" array is at the second largest coordinate.
	local i2 = band(rshift(Simplex[c][0], 1), 1)
	local j2 = band(rshift(Simplex[c][1], 1), 1)
	local k2 = band(rshift(Simplex[c][2], 1), 1)
	local l2 = band(rshift(Simplex[c][3], 1), 1)

	-- The number 1 (i.e. bit 0) in the "simplex" array is at the second smallest coordinate.
	local i3 = band(Simplex[c][0], 1)
	local j3 = band(Simplex[c][1], 1)
	local k3 = band(Simplex[c][2], 1)
	local l3 = band(Simplex[c][3], 1)

	-- Work out the hashed gradient indices of the five simplex corners
	-- Sum up and scale the result to cover the range [-1,1]
	ix, iy, iz, iw = band(ix, 255), band(iy, 255), band(iz, 255), band(iw, 255)

	local n0 = GetN4(ix, iy, iz, iw, x0, y0, z0, w0)
	local n1 = GetN4(ix + i1, iy + j1, iz + k1, iw + l1, x0 + 0.138196601 - i1, y0 + 0.138196601 - j1, z0 + 0.138196601 - k1, w0 + 0.138196601 - l1) -- G
	local n2 = GetN4(ix + i2, iy + j2, iz + k2, iw + l2, x0 + 0.276393202 - i2, y0 + 0.276393202 - j2, z0 + 0.276393202 - k2, w0 + 0.276393202 - l2) -- G2
	local n3 = GetN4(ix + i3, iy + j3, iz + k3, iw + l3, x0 + 0.414589803 - i3, y0 + 0.414589803 - j3, z0 + 0.414589803 - k3, w0 + 0.414589803 - l3) -- G3
	local n4 = GetN4(ix + 1, iy + 1, iz + 1, iw + 1, x0 - 0.447213595, y0 - 0.447213595, z0 - 0.447213595, w0 - 0.447213595) -- G4

	return 2.210600293 * (n0 + n1 + n2 + n3 + n4)
end

--- Simplex Noise
-- @param x
-- @param y
-- @param z optional
-- @param w optional
-- @return Noise value in the range [-1, +1]
return function(x, y, z, w)
	if w then
		return simplex_4d(x, y, z, w)
	end
	if z then
		return simplex_3d(x, y, z)
	end
	if y then
		return simplex_2d(x, y)
	end
	error "Simplex requires at least two arguments"
end


================================================
FILE: modules/utils.lua
================================================
--- Various utility functions
-- @module utils

local modules = (...): gsub('%.[^%.]+$', '') .. "."
local vec2    = require(modules .. "vec2")
local vec3    = require(modules .. "vec3")
local private = require(modules .. "_private_utils")
local abs     = math.abs
local ceil    = math.ceil
local floor   = math.floor
local log     = math.log
local utils   = {}

-- reimplementation of math.frexp, due to its removal from Lua 5.3 :(
-- courtesy of airstruck
local log2 = log(2)

local frexp = math.frexp or function(x)
	if x == 0 then return 0, 0 end
	local e = floor(log(abs(x)) / log2 + 1)
	return x / 2 ^ e, e
end

--- Clamps a value within the specified range.
-- @param value Input value
-- @param min Minimum output value
-- @param max Maximum output value
-- @return number
function utils.clamp(value, min, max)
	return math.max(math.min(value, max), min)
end

--- Returns `value` if it is equal or greater than |`size`|, or 0.
-- @param value
-- @param size
-- @return number
function utils.deadzone(value, size)
	return abs(value) >= size and value or 0
end

--- Check if value is equal or greater than threshold.
-- @param value
-- @param threshold
-- @return boolean
function utils.threshold(value, threshold)
	-- I know, it barely saves any typing at all.
	return abs(value) >= threshold
end

--- Check if value is equal or less than threshold.
-- @param value
-- @param threshold
-- @return boolean
function utils.tolerance(value, threshold)
	-- I know, it barely saves any typing at all.
	return abs(value) <= threshold
end

--- Scales a value from one range to another.
-- @param value Input value
-- @param min_in Minimum input value
-- @param max_in Maximum input value
-- @param min_out Minimum output value
-- @param max_out Maximum output value
-- @return number
function utils.map(value, min_in, max_in, min_out, max_out)
	return ((value) - (min_in)) * ((max_out) - (min_out)) / ((max_in) - (min_in)) + (min_out)
end

--- Linear interpolation.
-- Performs linear interpolation between 0 and 1 when `low` < `progress` < `high`.
-- @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.decay(low, high, rate, dt)
	return utils.lerp(low, high, 1.0 - math.exp(-rate * dt))
end

--- Hermite interpolation.
-- Performs smooth Hermite 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
-- @return number
function utils.smoothstep(progress, low, high)
	local t = utils.clamp((progress - low) / (high - low), 0.0, 1.0)
	return t * t * (3.0 - 2.0 * t)
end

--- Round number at a given precision.
-- Truncates `value` at `precision` points after the decimal (whole number if
-- left unspecified).
-- @param value
-- @param precision
-- @return number
utils.round = private.round

--- Wrap `value` around if it exceeds `limit`.
-- @param value
-- @param limit
-- @return number
function utils.wrap(value, limit)
	if value < 0 then
		value = value + utils.round(((-value/limit)+1))*limit
	end
	return value % limit
end

--- Check if a value is a power-of-two.
-- Returns true if a number is a valid power-of-two, otherwise false.
-- @author undef
-- @param value
-- @return boolean
function utils.is_pot(value)
	-- found here: https://love2d.org/forums/viewtopic.php?p=182219#p182219
	-- check if a number is a power-of-two
	return (frexp(value)) == 0.5
end

--- Check if a value is NaN
-- Returns true if a number is not a valid number
-- @param value
-- @return boolean
utils.is_nan = private.is_nan

-- Originally from vec3
function utils.project_on(a, b)
	local s =
		(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 vec2(
		b.x * s,
		b.y * s
	)
end

-- Originally from vec3
function utils.project_from(a, b)
	local s =
		(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 vec2(
		b.x * s,
		b.y * s
	)
end

-- Originally from vec3
function utils.mirror_on(a, b)
	local s =
		(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 vec2(
		b.x * s - a.x,
		b.y * s - a.y
	)
end

-- Originally from vec3
function utils.reflect(i, n)
	return i - (n * (2 * n:dot(i)))
end

-- Originally from vec3
function utils.refract(i, n, ior)
	local d = n:dot(i)
	local k = 1 - ior * ior * (1 - d * d)

	if k >= 0 then
		return (i * ior) - (n * (ior * d + k ^ 0.5))
	end

	return vec3()
end

--- Get the sign of a number
-- returns 1 for positive values, -1 for negative and 0 for zero.
-- @param value
-- @return number
function utils.sign(n)
	if n > 0 then
		return 1
	elseif n < 0 then
		return -1
	else
		return 0
	end
end

return utils


================================================
FILE: modules/vec2.lua
================================================
--- A 2 component vector.
-- @module vec2

local modules = (...):gsub('%.[^%.]+$', '') .. "."
local vec3    = require(modules .. "vec3")
local precond = require(modules .. "_private_precond")
local private = require(modules .. "_private_utils")
local acos    = math.acos
local atan2   = math.atan2 or math.atan
local sqrt    = math.sqrt
local cos     = math.cos
local sin     = math.sin
local vec2    = {}
local vec2_mt = {}

-- Private constructor.
local function new(x, y)
	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")
	if status then
		ffi.cdef "typedef struct { double x, y;} cpml_vec2;"
		new = ffi.typeof("cpml_vec2")
	end
end

--- Constants
-- @table vec2
-- @field unit_x X axis of rotation
-- @field unit_y Y axis of rotation
-- @field zero Empty vector
vec2.unit_x = new(1, 0)
vec2.unit_y = new(0, 1)
vec2.zero   = new(0, 0)

--- The public constructor.
-- @param x Can be of three types: </br>
-- number X component
-- table {x, y} or {x = x, y = y}
-- scalar to fill the vector eg. {x, x}
-- @tparam number y Y component
-- @treturn vec2 out
function vec2.new(x, y)
	-- number, number
	if x and y then
		precond.typeof(x, "number", "new: Wrong argument type for x")
		precond.typeof(y, "number", "new: Wrong argument type for y")

		return new(x, y)

	-- {x, y} or {x=x, y=y}
	elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
		local xx, yy = x.x or x[1], x.y or x[2]
		precond.typeof(xx, "number", "new: Wrong argument type for x")
		precond.typeof(yy, "number", "new: Wrong argument type for y")

		return new(xx, yy)

	-- number
	elseif type(x) == "number" then
		return new(x, x)
	else
		return new()
	end
end

--- Convert point from polar to cartesian.
-- @tparam number radius Radius of the point
-- @tparam number theta Angle of the point (in radians)
-- @treturn vec2 out
function vec2.from_cartesian(radius, theta)
	return new(radius * cos(theta), radius * sin(theta))
end

--- Clone a vector.
-- @tparam vec2 a Vector to be cloned
-- @treturn vec2 out
function vec2.clone(a)
	return new(a.x, a.y)
end

--- Add two vectors.
-- @tparam vec2 a Left hand operand
-- @tparam vec2 b Right hand operand
-- @treturn vec2 out
function vec2.add(a, b)
	return new(
		a.x + b.x,
		a.y + b.y
	)
end

--- Subtract one vector from another.
-- Order: If a and b are positions, computes the direction and distance from b
-- to a.
-- @tparam vec2 a Left hand operand
-- @tparam vec2 b Right hand operand
-- @treturn vec2 out
function vec2.sub(a, b)
	return new(
		a.x - b.x,
		a.y - b.y
	)
end

--- Multiply a vector by another vector.
-- Component-size multiplication not matrix multiplication.
-- @tparam vec2 a Left hand operand
-- @tparam vec2 b Right hand operand
-- @treturn vec2 out
function vec2.mul(a, b)
	return new(
		a.x * b.x,
		a.y * b.y
	)
end

--- Divide a vector by another vector.
-- Component-size inv multiplication. Like a non-uniform scale().
-- @tparam vec2 a Left hand operand
-- @tparam vec2 b Right hand operand
-- @treturn vec2 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 a Vector to normalize
-- @treturn vec2 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 a Vector to be trimmed
-- @tparam number len Length to trim the vector to
-- @treturn vec2 out
function vec2.trim(a, len)
	return a:normalize():scale(math.min(a:len(), len))
end

--- Get the cross product of two vectors.
-- Order: Positive if a is clockwise from b. Magnitude is the area spanned by
-- the parallelograms that a and b span.
-- @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 operand
-- @tparam vec2 b Right hand operand
-- @treturn number dot
function vec2.dot(a, b)
	return a.x * b.x + a.y * b.y
end

--- Get the length of a vector.
-- @tparam vec2 a Vector to get the length of
-- @treturn number len
function vec2.len(a)
	return sqrt(a.x * a.x + a.y * a.y)
end

--- Get the squared length of a vector.
-- @tparam vec2 a Vector to get the squared length of
-- @treturn number len
function vec2.len2(a)
	return a.x * a.x + a.y * a.y
end

--- Get the distance between two vectors.
-- @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
	local dy = a.y - b.y
	return sqrt(dx * dx + dy * dy)
end

--- Get the squared distance between two vectors.
-- @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
	local dy = a.y - b.y
	return dx * dx + dy * dy
end

--- Scale a vector by a scalar.
-- @tparam vec2 a Left hand operand
-- @tparam number b Right hand operand
-- @treturn vec2 out
function vec2.scale(a, b)
	return new(
		a.x * b,
		a.y * b
	)
end

--- Rotate a vector.
-- @tparam vec2 a Vector to rotate
-- @tparam number phi Angle to rotate vector by (in radians)
-- @treturn vec2 out
function vec2.rotate(a, phi)
	local c = cos(phi)
	local s = sin(phi)
	return new(
		c * a.x - s * a.y,
		s * a.x + c * a.y
	)
end

--- Get the perpendicular vector of a vector.
-- @tparam vec2 a Vector to get perpendicular axes from
-- @treturn vec2 out
function vec2.perpendicular(a)
	return new(-a.y, a.x)
end

--- Signed angle from one vector to another.
-- Rotations from +x to +y are positive.
-- @tparam vec2 a Vector
-- @tparam vec2 b Vector
-- @treturn number angle in (-pi, pi]
function vec2.angle_to(a, b)
	if b then
		local angle = atan2(b.y, b.x) - atan2(a.y, a.x)
		-- convert to (-pi, pi]
		if angle > math.pi       then
			angle = angle - 2 * math.pi
		elseif angle <= -math.pi then
			angle = angle + 2 * math.pi
		end
		return angle
	end

	return atan2(a.y, a.x)
end

--- Unsigned angle between two vectors.
-- Directionless and thus commutative.
-- @tparam vec2 a Vector
-- @tparam vec2 b Vector
-- @treturn number angle in [0, pi]
function vec2.angle_between(a, b)
	if b then
		if vec2.is_vec2(a) then
			return acos(a:dot(b) / (a:len() * b:len()))
		end

		return acos(vec3.dot(a, b) / (vec3.len(a) * vec3.len(b)))
	end

	return 0
end

--- Lerp between two vectors.
-- @tparam vec2 a Left hand operand
-- @tparam vec2 b Right hand operand
-- @tparam number s Step value
-- @treturn vec2 out
function vec2.lerp(a, b, s)
	return a + (b - a) * s
end

--- Unpack a vector into individual components.
-- @tparam vec2 a Vector to unpack
-- @treturn number x
-- @treturn number y
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
function vec2.is_vec2(a)
	if type(a) == "cdata" then
		return ffi.istype("cpml_vec2", a)
	end

	return
		type(a)   == "table"  and
		type(a.x) == "number" and
		type(a.y) == "number"
end

--- Return a boolean showing if a table is or is not a zero vec2.
-- @tparam vec2 a Vector to be tested
-- @treturn boolean is_zero
function vec2.is_zero(a)
	return a.x == 0 and a.y == 0
end

--- Return whether either value is NaN
-- @tparam vec2 a Vector to be tested
-- @treturn boolean if x or y is nan
function vec2.has_nan(a)
	return private.is_nan(a.x) or
		private.is_nan(a.y)
end

--- Convert point from cartesian to polar.
-- @tparam vec2 a Vector to convert
-- @treturn number radius
-- @treturn number theta
function vec2.to_polar(a)
	local radius = sqrt(a.x^2 + a.y^2)
	local theta  = atan2(a.y, a.x)
	theta = theta > 0 and theta or theta + 2 * math.pi
	return radius, theta
end

-- Round all components to nearest int (or other precision).
-- @tparam vec2 a Vector to round.
-- @tparam precision Digits after the decimal (integer if unspecified)
-- @treturn vec2 Rounded vector
function vec2.round(a, precision)
	return vec2.new(private.round(a.x, precision), private.round(a.y, precision))
end

-- Negate x axis only of vector.
-- @tparam vec2 a Vector to x-flip.
-- @treturn vec2 x-flipped vector
function vec2.flip_x(a)
	return vec2.new(-a.x, a.y)
end

-- Negate y axis only of vector.
-- @tparam vec2 a Vector to y-flip.
-- @treturn vec2 y-flipped vector
function vec2.flip_y(a)
	return vec2.new(a.x, -a.y)
end

-- Convert vec2 to vec3.
-- @tparam vec2 a Vector to convert.
-- @tparam number the new z component, or nil for 0
-- @treturn vec3 Converted vector
function vec2.to_vec3(a, z)
	return vec3(a.x, a.y, z or 0)
end

--- Return a formatted string.
-- @tparam vec2 a Vector to be turned into a string
-- @treturn string formatted
function vec2.to_string(a)
	return string.format("(%+0.3f,%+0.3f)", a.x, a.y)
end

vec2_mt.__index    = vec2
vec2_mt.__tostring = vec2.to_string

function vec2_mt.__call(_, x, y)
	return vec2.new(x, y)
end

function vec2_mt.__unm(a)
	return new(-a.x, -a.y)
end

function vec2_mt.__eq(a, b)
	if not vec2.is_vec2(a) or not vec2.is_vec2(b) then
		return false
	end
	return a.x == b.x and a.y == b.y
end

function vec2_mt.__add(a, b)
	precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
	precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
	return a:add(b)
end

function vec2_mt.__sub(a, b)
	precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
	precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
	return a:sub(b)
end

function vec2_mt.__mul(a, b)
	precond.assert(vec2.is_vec2(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
	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 a:mul(b)
	end

	return a:scale(b)
end

function vec2_mt.__div(a, b)
	precond.assert(vec2.is_vec2(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
	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 a:div(b)
	end

	return a:scale(1 / b)
end

if status then
	xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
		ffi.metatype(new, vec2_mt)
	end, function() end)
end

return setmetatable({}, vec2_mt)


================================================
FILE: modules/vec3.lua
================================================
--- A 3 component vector.
-- @module vec3

local modules = (...):gsub('%.[^%.]+$', '') .. "."
local precond = require(modules .. "_private_precond")
local private = require(modules .. "_private_utils")
local sqrt    = math.sqrt
local cos     = math.cos
local sin     = math.sin
local vec3    = {}
local vec3_mt = {}

-- Private constructor.
local function new(x, y, z)
	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")
	if status then
		ffi.cdef "typedef struct { double x, y, z;} cpml_vec3;"
		new = ffi.typeof("cpml_vec3")
	end
end

--- Constants
-- @table vec3
-- @field unit_x X axis of rotation
-- @field unit_y Y axis of rotation
-- @field unit_z Z axis of rotation
-- @field zero Empty vector
vec3.unit_x = new(1, 0, 0)
vec3.unit_y = new(0, 1, 0)
vec3.unit_z = new(0, 0, 1)
vec3.zero   = new(0, 0, 0)

--- The public constructor.
-- @param x Can be of three types: </br>
-- number X component
-- table {x, y, z} or {x=x, y=y, z=z}
-- scalar To fill the vector eg. {x, x, x}
-- @tparam number y Y component
-- @tparam number z Z component
-- @treturn vec3 out
function vec3.new(x, y, z)
	-- number, number, number
	if x and y and z then
		precond.typeof(x, "number", "new: Wrong argument type for x")
		precond.typeof(y, "number", "new: Wrong argument type for y")
		precond.typeof(z, "number", "new: Wrong argument type for z")

		return new(x, y, z)

	-- {x, y, z} or {x=x, y=y, z=z}
	elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
		local xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]
		precond.typeof(xx, "number", "new: Wrong argument type for x")
		precond.typeof(yy, "number", "new: Wrong argument type for y")
		precond.typeof(zz, "number", "new: Wrong argument type for z")

		return new(xx, yy, zz)

	-- number
	elseif type(x) == "number" then
		return new(x, x, x)
	else
		return new()
	end
end

--- Clone a vector.
-- @tparam vec3 a Vector to be cloned
-- @treturn vec3 out
function vec3.clone(a)
	return new(a.x, a.y, a.z)
end

--- Add two vectors.
-- @tparam vec3 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3 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.
-- Order: If a and b are positions, computes the direction and distance from b
-- to a.
-- @tparam vec3 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3 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 vector.
-- Component-wise multiplication not matrix multiplication.
-- @tparam vec3 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3 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 another.
-- Component-wise inv multiplication. Like a non-uniform scale().
-- @tparam vec3 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3 out
function vec3.div(a, b)
	return new(
		a.x / b.x,
		a.y / b.y,
		a.z / b.z
	)
end

--- Scale a vector to unit length (1).
-- @tparam vec3 a vector to normalize
-- @treturn vec3 out
function vec3.normalize(a)
	if a:is_zero() then
		return new()
	end
	return a:scale(1 / a:len())
end

--- Scale a vector to unit length (1), and return the input length.
-- @tparam vec3 a vector to normalize
-- @treturn vec3 out
-- @treturn number input vector length
function vec3.normalize_len(a)
	if a:is_zero() then
		return new(), 0
	end
	local len = a:len()
	return a:scale(1 / len), len
end

--- Trim a vector to a given length
-- @tparam vec3 a vector to be trimmed
-- @tparam number len Length to trim the vector to
-- @treturn vec3 out
function vec3.trim(a, len)
	return a:normalize():scale(math.min(a:len(), len))
end

--- Get the cross product of two vectors.
-- Resulting direction is right-hand rule normal of plane defined by a and b.
-- Magnitude is the area spanned by the parallelograms that a and b span.
-- Order: Direction determined by right-hand rule.
-- @tparam vec3 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3 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 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
end

--- Get the length of a vector.
-- @tparam vec3 a Vector to get the length of
-- @treturn number len
function vec3.len(a)
	return sqrt(a.x * a.x + a.y * a.y + a.z * a.z)
end

--- Get the squared length of a vector.
-- @tparam vec3 a Vector to get the squared length of
-- @treturn number len
function vec3.len2(a)
	return a.x * a.x + a.y * a.y + a.z * a.z
end

--- Get the distance between two vectors.
-- @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
	local dy = a.y - b.y
	local dz = a.z - b.z
	return sqrt(dx * dx + dy * dy + dz * dz)
end

--- Get the squared distance between two vectors.
-- @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
	local dy = a.y - b.y
	local dz = a.z - b.z
	return dx * dx + dy * dy + dz * dz
end

--- Scale a vector by a scalar.
-- @tparam vec3 a Left hand operand
-- @tparam number b Right hand operand
-- @treturn vec3 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 a Vector to rotate
-- @tparam number phi Angle to rotate vector by (in radians)
-- @tparam vec3 axis Axis to rotate by
-- @treturn vec3 out
function vec3.rotate(a, phi, axis)
	if not vec3.is_vec3(axis) then
		return a
	end

	local u = axis:normalize()
	local c = cos(phi)
	local s = sin(phi)

	-- Calculate generalized rotation matrix
	local m1 = new((c + u.x * u.x * (1 - c)),       (u.x * u.y * (1 - c) - u.z * s), (u.x * u.z * (1 - c) + u.y * s))
	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))      )

	return new(
		a:dot(m1),
		a:dot(m2),
		a:dot(m3)
	)
end

--- 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 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @tparam number s Step value
-- @treturn vec3 out
function vec3.lerp(a, b, s)
	return a + (b - a) * s
end

-- Round all components to nearest int (or other precision).
-- @tparam vec3 a Vector to round.
-- @tparam precision Digits after the decimal (round numebr if unspecified)
-- @treturn vec3 Rounded vector
function vec3.round(a, precision)
	return vec3.new(private.round(a.x, precision), private.round(a.y, precision), private.round(a.z, precision))
end

--- Unpack a vector into individual components.
-- @tparam vec3 a Vector to unpack
-- @treturn number x
-- @treturn number y
-- @treturn number z
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 the component-wise minimum and maximum of two vectors.
-- @tparam vec3 a Left hand operand
-- @tparam vec3 b Right hand operand
-- @treturn vec3, vec3 sorted vectors
function vec3.component_sort(a, b)
	return vec3.component_min(a, b), vec3.component_max(a, b)
end

-- Negate x axis only of vector.
-- @tparam vec3 a Vector to x-flip.
-- @treturn vec3 x-flipped vector
function vec3.flip_x(a)
	return vec3.new(-a.x, a.y, a.z)
end

-- Negate y axis only of vector.
-- @tparam vec3 a Vector to y-flip.
-- @treturn vec3 y-flipped vector
function vec3.flip_y(a)
	return vec3.new(a.x, -a.y, a.z)
end

-- Negate z axis only of vector.
-- @tparam vec3 a Vector to z-flip.
-- @treturn vec3 z-flipped vector
function vec3.flip_z(a)
	return vec3.new(a.x, a.y, -a.z)
end

function vec3.angle_to(a, b)
	local v = a:normalize():dot(b:normalize())
	return math.acos(v)
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
function vec3.is_vec3(a)
	if type(a) == "cdata" then
		return ffi.istype("cpml_vec3", a)
	end

	return
		type(a)   == "table"  and
		type(a.x) == "number" and
		type(a.y) == "number" and
		type(a.z) == "number"
end

--- Return a boolean showing if a table is or is not a zero vec3.
-- @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
end

--- Return whether any component is NaN
-- @tparam vec3 a Vector to be tested
-- @treturn boolean if x,y, or z are nan
function vec3.has_nan(a)
	return private.is_nan(a.x) or
		private.is_nan(a.y) or
		private.is_nan(a.z)
end

--- Return a formatted string.
-- @tparam vec3 a Vector to be turned into a string
-- @treturn string formatted
function vec3.to_string(a)
	return string.format("(%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z)
end

vec3_mt.__index    = vec3
vec3_mt.__tostring = vec3.to_string

function vec3_mt.__call(_, x, y, z)
	return vec3.new(x, y, z)
end

function vec3_mt.__unm(a)
	return new(-a.x, -a.y, -a.z)
end

function vec3_mt.__eq(a, b)
	if not vec3.is_vec3(a) or not vec3.is_vec3(b) then
		return false
	end
	return a.x == b.x and a.y == b.y and a.z == b.z
end

function vec3_mt.__add(a, b)
	precond.assert(vec3.is_vec3(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
	precond.assert(vec3.is_vec3(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
	return a:add(b)
end

function vec3_mt.__sub(a, b)
	precond.assert(vec3.is_vec3(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
	precond.assert(vec3.is_vec3(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
	return a:sub(b)
end

function vec3_mt.__mul(a, b)
	precond.assert(vec3.is_vec3(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
	precond.assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))

	if vec3.is_vec3(b) then
		return a:mul(b)
	end

	return a:scale(b)
end

function vec3_mt.__div(a, b)
	precond.assert(vec3.is_vec3(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
	precond.assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))

	if vec3.is_vec3(b) then
		return a:div(b)
	end

	return a:scale(1 / b)
end

if status then
	xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
		ffi.metatype(new, vec3_mt)
	end, function() end)
end

return setmetatable({}, vec3_mt)


================================================
FILE: spec/bound2_spec.lua
================================================
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 = vec2.new(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(1,2)))
		assert.is_true(a:contains(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)))
	end)

	it("rounds a bound2", function()
		local a = bound2(vec2(1.1,1.9), vec2(3.9,5.1)):round()

		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("extends a bound2 with a point", function()
		local min = vec2(1,2)
		local max = vec2(4,5)
		local downright = vec2(8,8)
		local downleft = vec2(-4,8)
		local top = vec2(2, 0)

		local a = bound2(min, max)
		local temp

		temp = a:extend(downright)
		assert.is_true(a.min == min and a.max == max)
		assert.is_true(temp.min == min and temp.max == downright)
		temp = a:extend(downleft)
		assert.is_true(temp.min == vec2(-4,2) and temp.max == vec2(4,8))
		temp = a:extend(top)
		assert.is_true(temp.min == vec2(1,0) and temp.max == max)
	end)

	it("extends a bound with another bound", function()
		local min = vec2(1,2)
		local max = vec2(4,5)
		local leftexpand = bound2.new(vec2(0,0), vec2(1.5, 6))
		local rightexpand = bound2.new(vec2(1.5,0), vec2(5, 6))

		local a = bound2(min, max)
		local temp

		temp = a:extend_bound(leftexpand)
		assert.is_equal(temp.min, vec2(0,0))
		assert.is_equal(temp.max, vec2(4,6))
		temp = temp:extend_bound(rightexpand)
		assert.is_equal(temp.min, vec2(0,0))
		assert.is_equal(temp.max, vec2(5,6))
	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)


================================================
FILE: spec/bound3_spec.lua
================================================
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 = vec3.new(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(1,2,3)))
		assert.is_true(a:contains(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("rounds a bound3", function()
		local a = bound3(vec3(1.1,1.9,3), vec3(3.9,5.1,6)):round()

		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("extends a bound3 with a point", function()
		local min = vec3(1,2,6)
		local max = vec3(4,5,9)
		local downright = vec3(8,8,10)
		local downleft = vec3(-4,8,10)
		local top = vec3(2, 0, 7)

		local a = bound3(min, max)
		local temp

		temp = a:extend(downright)
		assert.is_true(a.min == min and a.max == max)
		assert.is_true(temp.min == min and temp.max == downright)
		temp = a:extend(downleft)
		assert.is_true(temp.min == vec3(-4,2,6) and temp.max == vec3(4,8,10))
		temp = a:extend(top)
		assert.is_true(temp.min == vec3(1,0,6) and temp.max == max)
	end)

	it("extends a bound with another bound", function()
		local min = vec3(1,2,3)
		local max = vec3(4,5,6)
		local leftexpand = bound3.new(vec3(0,0,4), vec3(1.5,6,5))
		local rightexpand = bound3.new(vec3(1.5,0,1), vec3(5,6,7))

		local a = bound3(min, max)
		local temp

		temp = a:extend_bound(leftexpand)
		assert.is_equal(temp.min, vec3(0,0,3))
		assert.is_equal(temp.max, vec3(4,6,6))
		temp = temp:extend_bound(rightexpand)
		assert.is_equal(temp.min, vec3(0,0,1))
		assert.is_equal(temp.max, vec3(5,6,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)


================================================
FILE: spec/color_spec.lua
================================================
local color = require "modules.color"
local DBL_EPSILON = require("modules.constants").DBL_EPSILON

local function assert_is_float_equal(a, b)
	if math.abs(a - b) > DBL_EPSILON then
		assert.is.equal(a, b)
	end
end

local function assert_is_approx_equal(a, b)
	if math.abs(a - b) > 0.001 then
		assert.is.equal(a, b)
	end
end


describe("color:", function()
	it("operators: add, subract, multiply", function()
		local c = color(1, 1, 1, 1)
		assert.is_true(c:is_color())
		local r = c + c
		assert.is_true(r:is_color())
		assert_is_float_equal(r[1], 2)
		assert_is_float_equal(r[2], 2)
		assert_is_float_equal(r[3], 2)
		r = c - c
		assert.is_true(r:is_color())
		assert_is_float_equal(r[1], 0)
		assert_is_float_equal(r[2], 0)
		assert_is_float_equal(r[3], 0)
		r = c * 5
		assert.is_true(r:is_color())
		assert_is_float_equal(r[1], 5)
		assert_is_float_equal(r[2], 5)
		assert_is_float_equal(r[3], 5)
	end)

	it("rgb -> hsv -> rgb", function()
		local c = color(1,1,1,1)
		local hsv = c:color_to_hsv_table()
		local c1 = color.hsv_to_color_table(hsv)
		local c2 = color.from_hsva(hsv[1], hsv[2], hsv[3], hsv[4])
		local c3 = color.from_hsv(hsv[1], hsv[2], hsv[3])
		c3[4] = c[4]
		for i=1,4 do
			assert_is_float_equal(c[i], c1[i])
			assert_is_float_equal(c[i], c2[i])
			assert_is_float_equal(c[i], c3[i])
		end
		assert.is_true(c:is_color())
		assert.is_true(c1:is_color())
		assert.is_true(c2:is_color())
		assert.is_true(c3:is_color())
	end)

	it("hsv -> rgb -> hsv", function()
		local hsv1 = { 0, 0.3, 0.8, 0.9 }
		for h=0,1, 0.1 do
			hsv1[1] = h
			local cc = color.hsv_to_color_table(hsv1)
			local hsv2 = cc:color_to_hsv_table()
			for i=1,4 do
				assert_is_approx_equal(hsv1[i], hsv2[i])
			end
		end
	end)

	it("unpack", function()
		local c = color(122/255, 20/255, 122/255, 255/255)
		local r, g, b, a = c:unpack()
		assert_is_float_equal(c[1], r)
		assert_is_float_equal(c[2], g)
		assert_is_float_equal(c[3], b)
		assert_is_float_equal(c[4], a)
		r, g, b, a = c:as_255()
		assert_is_float_equal(122, r)
		assert_is_float_equal(20, g)
		assert_is_float_equal(122, b)
		assert_is_float_equal(255, a)
	end)

	it("set hsv", function()
		-- hsv value conversion values from http://colorizer.org/
		local c = color(122/255, 20/255, 122/255, 1)
		local hsv = c:color_to_hsv_table()
		assert_is_approx_equal(hsv[1], 300/360)
		assert_is_approx_equal(hsv[2], 0.8361)
		assert_is_approx_equal(hsv[3], 0.4784)
		local r = c:hue(200/360)
		assert_is_approx_equal(r[1], 20/255)
		assert_is_approx_equal(r[2], 88/255)
		assert_is_approx_equal(r[3], 122/255)
		r = c:saturation(0.2)
		assert_is_approx_equal(r[1], 122/255)
		assert_is_approx_equal(r[2], 97.6/255)
		assert_is_approx_equal(r[3], 122/255)
		r = c:value(0.2)
		assert_is_approx_equal(r[1], 51/255)
		assert_is_approx_equal(r[2], 8.36/255)
		assert_is_approx_equal(r[3], 51/255)
	end)

	it("lighten a color", function()
		local c = color(0, 0, 0, 0)
		local r = c:lighten(0.1)
		assert.is.equal(r[1], 0.1)
		r = c:lighten(1000)
		assert.is.equal(r[1], 1)
	end)

	it("darken a color", function()
		local c = color(1, 1, 1, 1)
		local r = c:darken(0.04)
		assert.is.equal(r[1], 0.96)
		r = c:darken(1000)
		assert.is.equal(r[1], 0)
	end)

	it("multiply a color by a scalar", function()
		local c = color(1, 1, 1, 1)
		local r = c:multiply(0.04)
		assert.is.equal(r[1], 0.04)

		r = c:multiply(0)
		for i=1,3 do
			assert.is.equal(0, r[i])
		end
		assert.is.equal(1, r[4])
	end)

	it("modify alpha", function()
		local c = color(1, 1, 1, 1)
		local r = c:alpha(0.1)
		assert.is.equal(r[4], 0.1)
		r = c:opacity(0.5)
		assert.is.equal(r[4], 0.5)
		r = c:opacity(0.5)
			:opacity(0.5)
		assert.is.equal(r[4], 0.25)
	end)

	it("invert", function()
		local c = color(1, 0.6, 0.25, 1)
		local r = c:invert()
		assert_is_float_equal(r[1], 0)
		assert_is_float_equal(r[2], 0.4)
		assert_is_float_equal(r[3], 0.75)
		assert_is_float_equal(r[4], 1)
		r = c:invert()
			:invert()
		for i=1,4 do
			assert.is.equal(c[i], r[i])
		end
	end)

	it("lerp", function()
		local a = color(1, 0.6, 0.25, 1)
		local b = color(1, 0.8, 0.75, 0.5)
		local r = a:lerp(b, 0.5)
		assert_is_float_equal(r[1], 1)
		assert_is_float_equal(r[2], 0.7)
		assert_is_float_equal(r[3], 0.5)
		assert_is_float_equal(r[4], 0.75)
		local r_a = a:lerp(b, 0)
		local r_b = a:lerp(b, 1)
		for i=1,4 do
			assert.is.equal(a[i], r_a[i])
			assert.is.equal(b[i], r_b[i])
		end
	end)

	it("linear_to_gamma -> gamma_to_linear round trip", function()
		local c = color(0.25, 0.25, 0.25, 1)
		local r = color.gamma_to_linear(c:linear_to_gamma())
		for i=1,4 do
			assert_is_approx_equal(c[i], r[i])
		end
	end)

end)

--[[
to_string(a)
--]]


================================================
FILE: spec/intersect_spec.lua
================================================
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
		}
		loc
Download .txt
gitextract__ayjrotp/

├── .coveralls.yml
├── .editorconfig
├── .github/
│   └── workflows/
│       ├── build.yml
│       └── runtest.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── cpml-scm-1.rockspec
├── doc/
│   ├── config.ld
│   └── install_and_build_docs
├── init.lua
├── modules/
│   ├── _private_precond.lua
│   ├── _private_utils.lua
│   ├── bound2.lua
│   ├── bound3.lua
│   ├── bvh.lua
│   ├── color.lua
│   ├── constants.lua
│   ├── intersect.lua
│   ├── mat4.lua
│   ├── mesh.lua
│   ├── octree.lua
│   ├── quat.lua
│   ├── simplex.lua
│   ├── utils.lua
│   ├── vec2.lua
│   └── vec3.lua
└── spec/
    ├── bound2_spec.lua
    ├── bound3_spec.lua
    ├── color_spec.lua
    ├── intersect_spec.lua
    ├── mat4_spec.lua
    ├── mesh_spec.lua
    ├── octree_spec.lua
    ├── quat_spec.lua
    ├── utils_spec.lua
    ├── vec2_spec.lua
    └── vec3_spec.lua
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (258K chars).
[
  {
    "path": ".coveralls.yml",
    "chars": 45,
    "preview": "repo_token: WcsY9jsU97Zt0ZIbGHJftGkC8DsD16FVl"
  },
  {
    "path": ".editorconfig",
    "chars": 90,
    "preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n[*.lua]\nindent_style = tab\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 878,
    "preview": "# Based on https://gist.github.com/domenic/ec8b0fc8ab45f39403dd\nname: Documentation\n\non:\n  pull_request: # Build on pull"
  },
  {
    "path": ".github/workflows/runtest.yml",
    "chars": 1166,
    "preview": "name: Validate Code\n\non:\n  pull_request:\n    branches:\n    - master\n    - refactor\n  push:\n    branches:\n    - master\n  "
  },
  {
    "path": ".gitignore",
    "chars": 32,
    "preview": "# LDoc generated files.\ndoc/out\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 2897,
    "preview": "# Licenses\n\nCPML is Copyright (c) 2016 Colby Klein <shakesoda@gmail.com>.\n\nCPML is Copyright (c) 2016 Landon Manning <lm"
  },
  {
    "path": "README.md",
    "chars": 1083,
    "preview": "Cirno's Perfect Math Library\n====\n\n![Build Status](https://github.com/excessive/cpml/actions/workflows/runtest.yml/badge"
  },
  {
    "path": "cpml-scm-1.rockspec",
    "chars": 1056,
    "preview": "package = \"cpml\"\nversion = \"scm-1\"\nsource = {\n   url = \"git://github.com/excessive/cpml.git\"\n}\ndescription = {\n   summar"
  },
  {
    "path": "doc/config.ld",
    "chars": 260,
    "preview": "project=\"CPML\"\ntitle=\"CPML documentation\"\ndescription=\"A math library with (hopefully) everything you need for 2D/3D gam"
  },
  {
    "path": "doc/install_and_build_docs",
    "chars": 421,
    "preview": "#! /bin/sh\n\n# on github, leafo/gh-actions-lua leafo/gh-actions-luarocks setup luarocks for us.\n#~ sudo apt-get install l"
  },
  {
    "path": "init.lua",
    "chars": 2123,
    "preview": "--[[\n-------------------------------------------------------------------------------\n-- @author Colby Klein\n-- @author L"
  },
  {
    "path": "modules/_private_precond.lua",
    "chars": 320,
    "preview": "-- Preconditions for cpml functions.\nlocal precond = {}\n\n\nfunction precond.typeof(t, expected, msg)\n\tif type(t) ~= expec"
  },
  {
    "path": "modules/_private_utils.lua",
    "chars": 405,
    "preview": "-- Functions exported by utils.lua but needed by vec2 or vec3 (which utils.lua requires)\n\nlocal private = {}\nlocal floor"
  },
  {
    "path": "modules/bound2.lua",
    "chars": 5906,
    "preview": "--- A 2 component bounding box.\r\n-- @module bound2\r\n\r\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\r\nlocal vec2    "
  },
  {
    "path": "modules/bound3.lua",
    "chars": 5979,
    "preview": "--- A 3-component axis-aligned bounding box.\r\n-- @module bound3\r\n\r\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\r\nl"
  },
  {
    "path": "modules/bvh.lua",
    "chars": 16748,
    "preview": "-- https://github.com/benraziel/bvh-tree\n\n--- BVH Tree\n-- @module bvh\n\nlocal modules   = (...):gsub('%.[^%.]+$', '') .. "
  },
  {
    "path": "modules/color.lua",
    "chars": 9269,
    "preview": "--- Color utilities\n-- @module color\n\nlocal modules  = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal constants = require(modu"
  },
  {
    "path": "modules/constants.lua",
    "chars": 532,
    "preview": "--- Various useful constants\n-- @module constants\n\n--- Constants\n-- @table constants\n-- @field FLT_EPSILON Floating poin"
  },
  {
    "path": "modules/intersect.lua",
    "chars": 18429,
    "preview": "--- Various geometric intersections\n-- @module intersect\n\nlocal modules     = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal c"
  },
  {
    "path": "modules/mat4.lua",
    "chars": 26870,
    "preview": "--- double 4x4, 1-based, column major matrices\n-- @module mat4\nlocal modules   = (...):gsub('%.[^%.]+$', '') .. \".\"\nloca"
  },
  {
    "path": "modules/mesh.lua",
    "chars": 1145,
    "preview": "--- Mesh utilities\n-- @module mesh\n\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal vec3    = require(modules ."
  },
  {
    "path": "modules/octree.lua",
    "chars": 21112,
    "preview": "-- https://github.com/Nition/UnityOctree\n-- https://github.com/Nition/UnityOctree/blob/master/LICENCE\n-- https://github."
  },
  {
    "path": "modules/quat.lua",
    "chars": 13446,
    "preview": "--- A quaternion and associated utilities.\n-- @module quat\n\nlocal modules       = (...):gsub('%.[^%.]+$', '') .. \".\"\nloc"
  },
  {
    "path": "modules/simplex.lua",
    "chars": 13748,
    "preview": "--- Simplex Noise\n-- @module simplex\n\n--\n-- Based on code in \"Simplex noise demystified\", by Stefan Gustavson\n-- www.itn"
  },
  {
    "path": "modules/utils.lua",
    "chars": 5386,
    "preview": "--- Various utility functions\n-- @module utils\n\nlocal modules = (...): gsub('%.[^%.]+$', '') .. \".\"\nlocal vec2    = requ"
  },
  {
    "path": "modules/vec2.lua",
    "chars": 11504,
    "preview": "--- A 2 component vector.\n-- @module vec2\n\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal vec3    = require(mo"
  },
  {
    "path": "modules/vec3.lua",
    "chars": 12009,
    "preview": "--- A 3 component vector.\n-- @module vec3\n\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal precond = require(mo"
  },
  {
    "path": "spec/bound2_spec.lua",
    "chars": 5884,
    "preview": "local bound2      = require \"modules.bound2\"\nlocal vec2      = require \"modules.vec2\"\nlocal DBL_EPSILON = require(\"modul"
  },
  {
    "path": "spec/bound3_spec.lua",
    "chars": 7345,
    "preview": "local bound3      = require \"modules.bound3\"\nlocal vec3      = require \"modules.vec3\"\nlocal DBL_EPSILON = require(\"modul"
  },
  {
    "path": "spec/color_spec.lua",
    "chars": 4656,
    "preview": "local color = require \"modules.color\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\n\nlocal function asser"
  },
  {
    "path": "spec/intersect_spec.lua",
    "chars": 5771,
    "preview": "local intersect = require \"modules.intersect\"\nlocal vec3      = require \"modules.vec3\"\nlocal mat4      = require \"module"
  },
  {
    "path": "spec/mat4_spec.lua",
    "chars": 14688,
    "preview": "local mat4        = require \"modules.mat4\"\nlocal vec3        = require \"modules.vec3\"\nlocal quat        = require \"modul"
  },
  {
    "path": "spec/mesh_spec.lua",
    "chars": 210,
    "preview": "local mesh = require \"modules.mesh\"\n\ndescribe(\"mesh:\", function()\nend)\n\n--[[\naverage(vertices)\nnormal(triangle)\nplane_fr"
  },
  {
    "path": "spec/octree_spec.lua",
    "chars": 1204,
    "preview": "local octree = require \"modules.octree\"\n\ndescribe(\"octree:\", function()\nend)\n\n--[[\nlocal function new(initialWorldSize, "
  },
  {
    "path": "spec/quat_spec.lua",
    "chars": 9818,
    "preview": "local quat  = require \"modules.quat\"\nlocal vec3  = require \"modules.vec3\"\nlocal utils = require \"modules.utils\"\nlocal co"
  },
  {
    "path": "spec/utils_spec.lua",
    "chars": 1669,
    "preview": "local vec3      = require \"modules.vec3\"\nlocal utils     = require \"modules.utils\"\nlocal constants = require \"modules.co"
  },
  {
    "path": "spec/vec2_spec.lua",
    "chars": 7753,
    "preview": "local vec2        = require \"modules.vec2\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\nlocal abs, sqrt "
  },
  {
    "path": "spec/vec3_spec.lua",
    "chars": 6576,
    "preview": "local vec3        = require \"modules.vec3\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\nlocal abs, sqrt "
  }
]

About this extraction

This page contains the full source code of the excessive/cpml GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (232.8 KB), approximately 83.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!