[
  {
    "path": ".coveralls.yml",
    "content": "repo_token: WcsY9jsU97Zt0ZIbGHJftGkC8DsD16FVl"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n[*.lua]\nindent_style = tab\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "# Based on https://gist.github.com/domenic/ec8b0fc8ab45f39403dd\nname: Documentation\n\non:\n  pull_request: # Build on pull requests to ensure they don't break docs.\n    branches:\n    - master\n  push:         # We'll only push new docs when master is updated (see below).\n    branches:\n    - master\n\njobs:\n  build:\n    name: Build Docs\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - name: Setup Lua\n      uses: leafo/gh-actions-lua@v8\n      with:\n        luaVersion: 5.4\n    - name: Setup Lua Rocks\n      uses: leafo/gh-actions-luarocks@v4\n    - name: Setup and run ldoc\n      run: bash ./doc/install_and_build_docs\n    - name: Deploy\n      if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}\n      uses: peaceiris/actions-gh-pages@v3\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        publish_dir: ./doc/out\n"
  },
  {
    "path": ".github/workflows/runtest.yml",
    "content": "name: Validate Code\n\non:\n  pull_request:\n    branches:\n    - master\n    - refactor\n  push:\n    branches:\n    - master\n    - refactor\n\njobs:\n  test:\n    name: Run Tests\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        luaVersion: [\"5.1.5\", \"luajit-2.0.5\", \"luajit-2.1.0-beta3\"]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Setup Lua\n      uses: leafo/gh-actions-lua@v8.0.0\n      with:\n        luaVersion: ${{ matrix.luaVersion }}\n    - name: Setup Lua Rocks\n      uses: leafo/gh-actions-luarocks@v4\n    - name: Install dependencies\n      run: |\n        luarocks --local install busted\n        luarocks --local install luacov\n        luarocks --local install luacov-coveralls\n    - name: Run busted\n      run: ~/.luarocks/bin/busted --verbose --coverage spec\n    - name: Upload coverage\n      continue-on-error: true # don't know why coveralls isn't uploading. For now, let this fail.\n      run: |\n        # ignore dotfile directories created by lua setup\n        ~/.luarocks/bin/luacov-coveralls --exclude '^%.%a+$' --repo-token WcsY9jsU97Zt0ZIbGHJftGkC8DsD16FVl\n    # - name: Run luacheck\n    #   run: luacheck --std max+busted *.lua spec\n\n"
  },
  {
    "path": ".gitignore",
    "content": "# LDoc generated files.\ndoc/out\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# Licenses\n\nCPML is Copyright (c) 2016 Colby Klein <shakesoda@gmail.com>.\n\nCPML is Copyright (c) 2016 Landon Manning <lmanning17@gmail.com>.\n\nCode in vec3.lua is derived from hump.vector. (c) 2010-2013 Matthias Richter. MIT.\n\nPortions of mat4.lua are from LuaMatrix, (c) 2010 Michael Lutz. MIT.\n\nCode in simplex.lua is (c) 2011 Stefan Gustavson. MIT.\n\nCode in bound2.lua and bound3.lua are (c) 2018 Andi McClure. MIT.\n\nCode in quat.lua is from Andrew Stacey and covered under the CC0 license.\n\nCode in octree.lua is derived from UnityOctree. (c) 2014 Nition. BSD-2-Clause.\n\n# The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n# The BSD License (BSD-2-Clause)\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "Cirno's Perfect Math Library\n====\n\n![Build Status](https://github.com/excessive/cpml/actions/workflows/runtest.yml/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/github/excessive/cpml/badge.svg?branch=master)](https://coveralls.io/github/excessive/cpml?branch=master)\n\nVarious useful bits of game math. 3D line intersections, ray casting, 2d/3d vectors, 4x4 matrices, quaternions, etc.\n\nIntended to be used with LuaJIT and LÖVE (this is the backbone of LÖVE3D).\n\nOnline documentation can be found [here](http://excessive.github.io/cpml/) or you can generate them yourself using `ldoc -c doc/config.ld -o index .`\n\n# Installation\nClone 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.\n\n# Versions\n\nThis 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\".\n"
  },
  {
    "path": "cpml-scm-1.rockspec",
    "content": "package = \"cpml\"\nversion = \"scm-1\"\nsource = {\n   url = \"git://github.com/excessive/cpml.git\"\n}\ndescription = {\n   summary = \"Cirno's Perfect Math Library\",\n   detailed = \"Various useful bits of game math. 3D line intersections, ray casting, vectors, matrices, quaternions, etc.\",\n   homepage = \"http://github.com/excessive/cpml.git\",\n   license = \"MIT\"\n}\ndependencies = {\n   \"lua ~> 5.1\"\n}\nbuild = {\n   type = \"builtin\",\n   modules = {\n      [\"cpml\"] = \"init.lua\",\n      [\"cpml.modules.color\"] = \"modules/color.lua\",\n      [\"cpml.modules.constants\"] = \"modules/constants.lua\",\n      [\"cpml.modules.intersect\"] = \"modules/intersect.lua\",\n      [\"cpml.modules.mat4\"] = \"modules/mat4.lua\",\n      [\"cpml.modules.mesh\"] = \"modules/mesh.lua\",\n      [\"cpml.modules.octree\"] = \"modules/octree.lua\",\n      [\"cpml.modules.quat\"] = \"modules/quat.lua\",\n      [\"cpml.modules.simplex\"] = \"modules/simplex.lua\",\n      [\"cpml.modules.utils\"] = \"modules/utils.lua\",\n      [\"cpml.modules.vec2\"] = \"modules/vec2.lua\",\n      [\"cpml.modules.vec3\"] = \"modules/vec3.lua\",\n   }\n}\n"
  },
  {
    "path": "doc/config.ld",
    "content": "project=\"CPML\"\ntitle=\"CPML documentation\"\ndescription=\"A math library with (hopefully) everything you need for 2D/3D games\"\nformat=\"markdown\"\nbacktick_references=false\nfile = {\n   \"../init.lua\",\n   \"../modules\"\n}\ndir='./out'\nreadme='../README.md'\nstyle='!new'\n"
  },
  {
    "path": "doc/install_and_build_docs",
    "content": "#! /bin/sh\n\n# on github, leafo/gh-actions-lua leafo/gh-actions-luarocks setup luarocks for us.\n#~ sudo apt-get install lua5.3 liblua5.3-dev luarocks\n\n# github ldoc is far ahead of the released version.\necho ldoc version:\ngit ls-remote https://github.com/lunarmodules/LDoc master\nluarocks --local install https://raw.githubusercontent.com/lunarmodules/LDoc/master/ldoc-scm-3.rockspec\n\necho\ncd ./doc\n~/.luarocks/bin/ldoc .\n"
  },
  {
    "path": "init.lua",
    "content": "--[[\n-------------------------------------------------------------------------------\n-- @author Colby Klein\n-- @author Landon Manning\n-- @copyright 2016\n-- @license MIT/X11\n-------------------------------------------------------------------------------\n                  .'@@@@@@@@@@@@@@#:\n              ,@@@@#;            .'@@@@+\n           ,@@@'                      .@@@#\n         +@@+            ....            .@@@\n       ;@@;         '@@@@@@@@@@@@.          @@@\n      @@#         @@@@@@@@++@@@@@@@;         `@@;\n    .@@`         @@@@@#        #@@@@@          @@@\n   `@@          @@@@@` Cirno's  `@@@@#          +@@\n   @@          `@@@@@  Perfect   @@@@@           @@+\n  @@+          ;@@@@+   Math     +@@@@+           @@\n  @@           `@@@@@  Library   @@@@@@           #@'\n `@@            @@@@@@          @@@@@@@           `@@\n :@@             #@@@@@@.    .@@@@@@@@@            @@\n .@@               #@@@@@@@@@@@@;;@@@@@            @@\n  @@                  .;+@@#'.   ;@@@@@           :@@\n  @@`                            +@@@@+           @@.\n  ,@@                            @@@@@           .@@\n   @@#          ;;;;;.          `@@@@@           @@\n    @@+         .@@@@@          @@@@@           @@`\n     #@@         '@@@@@#`    ;@@@@@@          ;@@\n      .@@'         @@@@@@@@@@@@@@@           @@#\n        +@@'          '@@@@@@@;            @@@\n          '@@@`                         '@@@\n             #@@@;                  .@@@@:\n                :@@@@@@@++;;;+#@@@@@@+`\n                      .;'+++++;.\n--]]\nlocal modules = (...) and (...):gsub('%.init$', '') .. \".modules.\" or \"\"\n\nlocal cpml = {\n\t_LICENSE = \"CPML is distributed under the terms of the MIT license. See LICENSE.md.\",\n\t_URL = \"https://github.com/excessive/cpml\",\n\t_VERSION = \"1.2.9\",\n\t_DESCRIPTION = \"Cirno's Perfect Math Library: Just about everything you need for 3D games. Hopefully.\"\n}\n\nlocal files = {\n\t\"bvh\",\n\t\"color\",\n\t\"constants\",\n\t\"intersect\",\n\t\"mat4\",\n\t\"mesh\",\n\t\"octree\",\n\t\"quat\",\n\t\"simplex\",\n\t\"utils\",\n\t\"vec2\",\n\t\"vec3\",\n\t\"bound2\",\n\t\"bound3\",\n}\n\nfor _, file in ipairs(files) do\n\tcpml[file] = require(modules .. file)\nend\n\nreturn cpml\n"
  },
  {
    "path": "modules/_private_precond.lua",
    "content": "-- Preconditions for cpml functions.\nlocal precond = {}\n\n\nfunction precond.typeof(t, expected, msg)\n\tif type(t) ~= expected then\n\t\terror((\"%s: %s (<%s> expected)\"):format(msg, type(t), expected), 3)\n\tend\nend\n\nfunction precond.assert(cond, msg, ...)\n\tif not cond then\n\t\terror(msg:format(...), 3)\n\tend\nend\n\nreturn precond\n"
  },
  {
    "path": "modules/_private_utils.lua",
    "content": "-- Functions exported by utils.lua but needed by vec2 or vec3 (which utils.lua requires)\n\nlocal private = {}\nlocal floor   = math.floor\nlocal ceil    = math.ceil\n\nfunction private.round(value, precision)\n\tif precision then return private.round(value / precision) * precision end\n\treturn value >= 0 and floor(value+0.5) or ceil(value-0.5)\nend\n\nfunction private.is_nan(a)\n\treturn a ~= a\nend\n\nreturn private\n"
  },
  {
    "path": "modules/bound2.lua",
    "content": "--- A 2 component bounding box.\r\n-- @module bound2\r\n\r\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\r\nlocal vec2    = require(modules .. \"vec2\")\r\n\r\nlocal bound2    = {}\r\nlocal bound2_mt = {}\r\n\r\n-- Private constructor.\r\nlocal function new(min, max)\r\n\treturn setmetatable({\r\n\t\tmin=min, -- min: vec2, minimum value for each component \r\n\t\tmax=max, -- max: vec2, maximum value for each component \r\n\t}, bound2_mt)\r\nend\r\n\r\n-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.\r\nlocal status, ffi\r\nif type(jit) == \"table\" and jit.status() then\r\n\tstatus, ffi = pcall(require, \"ffi\")\r\n\tif status then\r\n\t\tffi.cdef \"typedef struct { cpml_vec2 min, max; } cpml_bound2;\"\r\n\t\tnew = ffi.typeof(\"cpml_bound2\")\r\n\tend\r\nend\r\n\r\nbound2.zero = new(vec2.zero, vec2.zero)\r\n\r\n--- The public constructor.\r\n-- @param min Can be of two types: </br>\r\n-- vec2 min, minimum value for each component\r\n-- nil Create bound at single point 0,0\r\n-- @tparam vec2 max, maximum value for each component\r\n-- @treturn bound2 out\r\nfunction bound2.new(min, max)\r\n\tif min and max then\r\n\t\treturn new(min:clone(), max:clone())\r\n\telseif min or max then\r\n\t\terror(\"Unexpected nil argument to bound2.new\")\r\n\telse\r\n\t\treturn new(vec2.zero, vec2.zero)\r\n\tend\r\nend\r\n\r\n--- Clone a bound.\r\n-- @tparam bound2 a bound to be cloned\r\n-- @treturn bound2 out\r\nfunction bound2.clone(a)\r\n\treturn new(a.min, a.max)\r\nend\r\n\r\n--- Construct a bound covering one or two points \r\n-- @tparam vec2 a Any vector\r\n-- @tparam vec2 b Any second vector (optional)\r\n-- @treturn vec2 Minimum bound containing the given points\r\nfunction bound2.at(a, b) -- \"bounded by\". b may be nil\r\n\tif b then\r\n\t\treturn bound2.new(a,b):check()\r\n\telse\r\n\t\treturn bound2.zero:with_center(a)\r\n\tend\r\nend\r\n\r\n--- Extend bound to include point\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 point to include\r\n-- @treturn bound2 Bound covering current min, current max and new point\r\nfunction bound2.extend(a, center)\r\n\treturn bound2.new(a.min:component_min(center), a.max:component_max(center))\r\nend\r\n\r\n--- Extend bound to entirety of other bound\r\n-- @tparam bound2 a bound\r\n-- @tparam bound2 bound to cover\r\n-- @treturn bound2 Bound covering current min and max of each bound in the pair\r\nfunction bound2.extend_bound(a, b)\r\n\treturn a:extend(b.min):extend(b.max)\r\nend\r\n\r\n--- Get size of bounding box as a vector \r\n-- @tparam bound2 a bound\r\n-- @treturn vec2 Vector spanning min to max points\r\nfunction bound2.size(a)\r\n\treturn a.max - a.min\r\nend\r\n\r\n--- Resize bounding box from minimum corner\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 new size\r\n-- @treturn bound2 resized bound\r\nfunction bound2.with_size(a, size)\r\n\treturn bound2.new(a.min, a.min + size)\r\nend\r\n\r\n--- Get half-size of bounding box as a vector. A more correct term for this is probably \"apothem\"\r\n-- @tparam bound2 a bound\r\n-- @treturn vec2 Vector spanning center to max point\r\nfunction bound2.radius(a)\r\n\treturn a:size()/2\r\nend\r\n\r\n--- Get center of bounding box\r\n-- @tparam bound2 a bound\r\n-- @treturn bound2 Point in center of bound\r\nfunction bound2.center(a)\r\n\treturn (a.min + a.max)/2\r\nend\r\n\r\n--- Move bounding box to new center\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 new center\r\n-- @treturn bound2 Bound with same size as input but different center\r\nfunction bound2.with_center(a, center)\r\n\treturn bound2.offset(a, center - a:center())\r\nend\r\n\r\n--- Resize bounding box from center\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 new size\r\n-- @treturn bound2 resized bound\r\nfunction bound2.with_size_centered(a, size)\r\n\tlocal center = a:center()\r\n\tlocal rad = size/2\r\n\treturn bound2.new(center - rad, center + rad)\r\nend\r\n\r\n--- Convert possibly-invalid bounding box to valid one\r\n-- @tparam bound2 a bound\r\n-- @treturn bound2 bound with all components corrected for min-max property\r\nfunction bound2.check(a)\r\n\tif a.min.x > a.max.x or a.min.y > a.max.y then\r\n\t\treturn bound2.new(vec2.component_min(a.min, a.max), vec2.component_max(a.min, a.max))\r\n\tend\r\n\treturn a\r\nend\r\n\r\n--- Shrink bounding box with fixed margin\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 a margin\r\n-- @treturn bound2 bound with margin subtracted from all edges. May not be valid, consider calling check()\r\nfunction bound2.inset(a, v)\r\n\treturn bound2.new(a.min + v, a.max - v)\r\nend\r\n\r\n--- Expand bounding box with fixed margin\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 a margin\r\n-- @treturn bound2 bound with margin added to all edges. May not be valid, consider calling check()\r\nfunction bound2.outset(a, v)\r\n\treturn bound2.new(a.min - v, a.max + v)\r\nend\r\n\r\n--- Offset bounding box\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 offset\r\n-- @treturn bound2 bound with same size, but position moved by offset\r\nfunction bound2.offset(a, v)\r\n\treturn bound2.new(a.min + v, a.max + v)\r\nend\r\n\r\n--- Test if point in bound\r\n-- @tparam bound2 a bound\r\n-- @tparam vec2 point to test\r\n-- @treturn boolean true if point in bounding box\r\nfunction bound2.contains(a, v)\r\n\treturn a.min.x <= v.x and a.min.y <= v.y\r\n\t   and a.max.x >= v.x and a.max.y >= v.y\r\nend\r\n\r\n-- Round all components of all vectors to nearest int (or other precision).\r\n-- @tparam vec3 a bound to round.\r\n-- @tparam precision Digits after the decimal (round number if unspecified)\r\n-- @treturn vec3 Rounded bound\r\nfunction bound2.round(a, precision)\r\n\treturn bound2.new(a.min:round(precision), a.max:round(precision))\r\nend\r\n\r\n--- Return a formatted string.\r\n-- @tparam bound2 a bound to be turned into a string\r\n-- @treturn string formatted\r\nfunction bound2.to_string(a)\r\n\treturn string.format(\"(%s-%s)\", a.min, a.max)\r\nend\r\n\r\nbound2_mt.__index    = bound2\r\nbound2_mt.__tostring = bound2.to_string\r\n\r\nfunction bound2_mt.__call(_, a, b)\r\n\treturn bound2.new(a, b)\r\nend\r\n\r\nif status then\r\n\txpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded\r\n\t\tffi.metatype(new, bound2_mt)\r\n\tend, function() end)\r\nend\r\n\r\nreturn setmetatable({}, bound2_mt)\r\n"
  },
  {
    "path": "modules/bound3.lua",
    "content": "--- A 3-component axis-aligned bounding box.\r\n-- @module bound3\r\n\r\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\r\nlocal vec3    = require(modules .. \"vec3\")\r\n\r\nlocal bound3    = {}\r\nlocal bound3_mt = {}\r\n\r\n-- Private constructor.\r\nlocal function new(min, max)\r\n\treturn setmetatable({\r\n\t\tmin=min, -- min: vec3, minimum value for each component \r\n\t\tmax=max  -- max: vec3, maximum value for each component\r\n\t}, bound3_mt)\r\nend\r\n\r\n-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.\r\nlocal status, ffi\r\nif type(jit) == \"table\" and jit.status() then\r\n\tstatus, ffi = pcall(require, \"ffi\")\r\n\tif status then\r\n\t\tffi.cdef \"typedef struct { cpml_vec3 min, max; } cpml_bound3;\"\r\n\t\tnew = ffi.typeof(\"cpml_bound3\")\r\n\tend\r\nend\r\n\r\nbound3.zero = new(vec3.zero, vec3.zero)\r\n\r\n--- The public constructor.\r\n-- @param min Can be of two types: </br>\r\n-- vec3 min, minimum value for each component\r\n-- nil Create bound at single point 0,0,0\r\n-- @tparam vec3 max, maximum value for each component\r\n-- @treturn bound3 out\r\nfunction bound3.new(min, max)\r\n\tif min and max then\r\n\t\treturn new(min:clone(), max:clone())\r\n\telseif min or max then\r\n\t\terror(\"Unexpected nil argument to bound3.new\")\r\n\telse\r\n\t\treturn new(vec3.zero, vec3.zero)\r\n\tend\r\nend\r\n\r\n--- Clone a bound.\r\n-- @tparam bound3 a bound to be cloned\r\n-- @treturn bound3 out\r\nfunction bound3.clone(a)\r\n\treturn new(a.min, a.max)\r\nend\r\n\r\n--- Construct a bound covering one or two points \r\n-- @tparam vec3 a Any vector\r\n-- @tparam vec3 b Any second vector (optional)\r\n-- @treturn vec3 Minimum bound containing the given points\r\nfunction bound3.at(a, b) -- \"bounded by\". b may be nil\r\n\tif b then\r\n\t\treturn bound3.new(a,b):check()\r\n\telse\r\n\t\treturn bound3.zero:with_center(a)\r\n\tend\r\nend\r\n\r\n--- Extend bound to include point\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 point to include\r\n-- @treturn bound3 Bound covering current min, current max and new point\r\nfunction bound3.extend(a, center)\r\n\treturn bound3.new(a.min:component_min(center), a.max:component_max(center))\r\nend\r\n\r\n--- Extend bound to entirety of other bound\r\n-- @tparam bound3 a bound\r\n-- @tparam bound3 bound to cover\r\n-- @treturn bound3 Bound covering current min and max of each bound in the pair\r\nfunction bound3.extend_bound(a, b)\r\n\treturn a:extend(b.min):extend(b.max)\r\nend\r\n\r\n--- Get size of bounding box as a vector \r\n-- @tparam bound3 a bound\r\n-- @treturn vec3 Vector spanning min to max points\r\nfunction bound3.size(a)\r\n\treturn a.max - a.min\r\nend\r\n\r\n--- Resize bounding box from minimum corner\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 new size\r\n-- @treturn bound3 resized bound\r\nfunction bound3.with_size(a, size)\r\n\treturn bound3.new(a.min, a.min + size)\r\nend\r\n\r\n--- Get half-size of bounding box as a vector. A more correct term for this is probably \"apothem\"\r\n-- @tparam bound3 a bound\r\n-- @treturn vec3 Vector spanning center to max point\r\nfunction bound3.radius(a)\r\n\treturn a:size()/2\r\nend\r\n\r\n--- Get center of bounding box\r\n-- @tparam bound3 a bound\r\n-- @treturn bound3 Point in center of bound\r\nfunction bound3.center(a)\r\n\treturn (a.min + a.max)/2\r\nend\r\n\r\n--- Move bounding box to new center\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 new center\r\n-- @treturn bound3 Bound with same size as input but different center\r\nfunction bound3.with_center(a, center)\r\n\treturn bound3.offset(a, center - a:center())\r\nend\r\n\r\n--- Resize bounding box from center\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 new size\r\n-- @treturn bound3 resized bound\r\nfunction bound3.with_size_centered(a, size)\r\n\tlocal center = a:center()\r\n\tlocal rad = size/2\r\n\treturn bound3.new(center - rad, center + rad)\r\nend\r\n\r\n--- Convert possibly-invalid bounding box to valid one\r\n-- @tparam bound3 a bound\r\n-- @treturn bound3 bound with all components corrected for min-max property\r\nfunction bound3.check(a)\r\n\tif a.min.x > a.max.x or a.min.y > a.max.y or a.min.z > a.max.z then\r\n\t\treturn bound3.new(vec3.component_min(a.min, a.max), vec3.component_max(a.min, a.max))\r\n\tend\r\n\treturn a\r\nend\r\n\r\n--- Shrink bounding box with fixed margin\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 a margin\r\n-- @treturn bound3 bound with margin subtracted from all edges. May not be valid, consider calling check()\r\nfunction bound3.inset(a, v)\r\n\treturn bound3.new(a.min + v, a.max - v)\r\nend\r\n\r\n--- Expand bounding box with fixed margin\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 a margin\r\n-- @treturn bound3 bound with margin added to all edges. May not be valid, consider calling check()\r\nfunction bound3.outset(a, v)\r\n\treturn bound3.new(a.min - v, a.max + v)\r\nend\r\n\r\n--- Offset bounding box\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 offset\r\n-- @treturn bound3 bound with same size, but position moved by offset\r\nfunction bound3.offset(a, v)\r\n\treturn bound3.new(a.min + v, a.max + v)\r\nend\r\n\r\n--- Test if point in bound\r\n-- @tparam bound3 a bound\r\n-- @tparam vec3 point to test\r\n-- @treturn boolean true if point in bounding box\r\nfunction bound3.contains(a, v)\r\n\treturn a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z\r\n\t   and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z\r\nend\r\n\r\n-- Round all components of all vectors to nearest int (or other precision).\r\n-- @tparam vec3 a bound to round.\r\n-- @tparam precision Digits after the decimal (round number if unspecified)\r\n-- @treturn vec3 Rounded bound\r\nfunction bound3.round(a, precision)\r\n\treturn bound3.new(a.min:round(precision), a.max:round(precision))\r\nend\r\n\r\n--- Return a formatted string.\r\n-- @tparam bound3 a bound to be turned into a string\r\n-- @treturn string formatted\r\nfunction bound3.to_string(a)\r\n\treturn string.format(\"(%s-%s)\", a.min, a.max)\r\nend\r\n\r\nbound3_mt.__index    = bound3\r\nbound3_mt.__tostring = bound3.to_string\r\n\r\nfunction bound3_mt.__call(_, a, b)\r\n\treturn bound3.new(a, b)\r\nend\r\n\r\nif status then\r\n\txpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded\r\n\t\tffi.metatype(new, bound3_mt)\r\n\tend, function() end)\r\nend\r\n\r\nreturn setmetatable({}, bound3_mt)\r\n"
  },
  {
    "path": "modules/bvh.lua",
    "content": "-- https://github.com/benraziel/bvh-tree\n\n--- BVH Tree\n-- @module bvh\n\nlocal modules   = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal intersect = require(modules .. \"intersect\")\nlocal vec3      = require(modules .. \"vec3\")\nlocal EPSILON   = 1e-6\nlocal BVH       = {}\nlocal BVHNode   = {}\nlocal Node\n\nBVH.__index     = BVH\nBVHNode.__index = BVHNode\n\nlocal function new(triangles, maxTrianglesPerNode)\n\tlocal tree = setmetatable({}, BVH)\n\tlocal trianglesArray = {}\n\n\tfor _, triangle in ipairs(triangles) do\n\t\tlocal p1 = triangle[1]\n\t\tlocal p2 = triangle[2]\n\t\tlocal p3 = triangle[3]\n\n\t\ttable.insert(trianglesArray, p1.x or p1[1])\n\t\ttable.insert(trianglesArray, p1.y or p1[2])\n\t\ttable.insert(trianglesArray, p1.z or p1[3])\n\n\t\ttable.insert(trianglesArray, p2.x or p2[1])\n\t\ttable.insert(trianglesArray, p2.y or p2[2])\n\t\ttable.insert(trianglesArray, p2.z or p2[3])\n\n\t\ttable.insert(trianglesArray, p3.x or p3[1])\n\t\ttable.insert(trianglesArray, p3.y or p3[2])\n\t\ttable.insert(trianglesArray, p3.z or p3[3])\n\tend\n\n\ttree._trianglesArray      = trianglesArray\n\ttree._maxTrianglesPerNode = maxTrianglesPerNode or 10\n\ttree._bboxArray           = tree.calcBoundingBoxes(trianglesArray)\n\n\t-- clone a helper array\n\ttree._bboxHelper = {}\n\tfor _, bbox in ipairs(tree._bboxArray) do\n\t\ttable.insert(tree._bboxHelper, bbox)\n\tend\n\n\t-- create the root node, add all the triangles to it\n\tlocal triangleCount = #triangles\n\tlocal extents = tree:calcExtents(1, triangleCount, EPSILON)\n\ttree._rootNode = Node(extents[1], extents[2], 1, triangleCount, 1)\n\n\ttree._nodes_to_split = { tree._rootNode }\n\twhile #tree._nodes_to_split > 0 do\n\t\tlocal node = table.remove(tree._nodes_to_split)\n\t\ttree:splitNode(node)\n\tend\n\treturn tree\nend\n\nfunction BVH:intersectAABB(aabb)\n\tlocal nodesToIntersect             = { self._rootNode }\n\tlocal trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)\n\tlocal intersectingTriangles        = {}\n\n\t-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the box.\n\t-- note: these triangles may not intersect the box themselves\n\twhile #nodesToIntersect > 0 do\n\t\tlocal node = table.remove(nodesToIntersect)\n\n\t\tlocal node_aabb = {\n\t\t\tmin = node._extentsMin,\n\t\t\tmax = node._extentsMax\n\t\t}\n\n\t\tif intersect.aabb_aabb(aabb, node_aabb) then\n\t\t\tif node._node0 then\n\t\t\t\ttable.insert(nodesToIntersect, node._node0)\n\t\t\tend\n\n\t\t\tif node._node1 then\n\t\t\t\ttable.insert(nodesToIntersect, node._node1)\n\t\t\tend\n\n\t\t\tfor i=node._startIndex, node._endIndex do\n\t\t\t\ttable.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])\n\t\t\tend\n\t\tend\n\tend\n\n\t-- insert all node triangles, don't bother being more specific yet.\n\tlocal triangle = { vec3(), vec3(), vec3() }\n\n\tfor i=1, #trianglesInIntersectingNodes do\n\t\tlocal triIndex = trianglesInIntersectingNodes[i]\n\n\t\t-- print(triIndex, #self._trianglesArray)\n\t\ttriangle[1].x = self._trianglesArray[1+(triIndex-1)*9]\n\t\ttriangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]\n\t\ttriangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]\n\t\ttriangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]\n\t\ttriangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]\n\t\ttriangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]\n\t\ttriangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]\n\t\ttriangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]\n\t\ttriangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]\n\n\t\ttable.insert(intersectingTriangles, {\n\t\t\ttriangle             = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },\n\t\t\ttriangleIndex        = triIndex\n\t\t})\n\tend\n\n\treturn intersectingTriangles\nend\n\nfunction BVH:intersectRay(rayOrigin, rayDirection, backfaceCulling)\n\tlocal nodesToIntersect             = { self._rootNode }\n\tlocal trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)\n\tlocal intersectingTriangles        = {}\n\n\tlocal invRayDirection = vec3(\n\t\t1 / rayDirection.x,\n\t\t1 / rayDirection.y,\n\t\t1 / rayDirection.z\n\t)\n\n\t-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the ray.\n\t-- note: these triangles may not intersect the ray themselves\n\twhile #nodesToIntersect > 0 do\n\t\tlocal node = table.remove(nodesToIntersect)\n\n\t\tif BVH.intersectNodeBox(rayOrigin, invRayDirection, node) then\n\t\t\tif node._node0 then\n\t\t\t\ttable.insert(nodesToIntersect, node._node0)\n\t\t\tend\n\n\t\t\tif node._node1 then\n\t\t\t\ttable.insert(nodesToIntersect, node._node1)\n\t\t\tend\n\n\t\t\tfor i=node._startIndex, node._endIndex do\n\t\t\t\ttable.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])\n\t\t\tend\n\t\tend\n\tend\n\n\t-- go over the list of candidate triangles, and check each of them using ray triangle intersection\n\tlocal triangle = { vec3(), vec3(), vec3() }\n\tlocal ray      = {\n\t\tposition  = vec3(rayOrigin.x,    rayOrigin.y,    rayOrigin.z),\n\t\tdirection = vec3(rayDirection.x, rayDirection.y, rayDirection.z)\n\t}\n\n\tfor i=1, #trianglesInIntersectingNodes do\n\t\tlocal triIndex = trianglesInIntersectingNodes[i]\n\n\t\t-- print(triIndex, #self._trianglesArray)\n\t\ttriangle[1].x = self._trianglesArray[1+(triIndex-1)*9]\n\t\ttriangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]\n\t\ttriangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]\n\t\ttriangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]\n\t\ttriangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]\n\t\ttriangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]\n\t\ttriangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]\n\t\ttriangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]\n\t\ttriangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]\n\n\t\tlocal intersectionPoint, intersectionDistance = intersect.ray_triangle(ray, triangle, backfaceCulling)\n\n\t\tif intersectionPoint then\n\t\t\ttable.insert(intersectingTriangles, {\n\t\t\t\ttriangle             = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },\n\t\t\t\ttriangleIndex        = triIndex,\n\t\t\t\tintersectionPoint    = intersectionPoint,\n\t\t\t\tintersectionDistance = intersectionDistance\n\t\t\t})\n\t\tend\n\tend\n\n\treturn intersectingTriangles\nend\n\nfunction BVH.calcBoundingBoxes(trianglesArray)\n\tlocal p1x, p1y, p1z\n\tlocal p2x, p2y, p2z\n\tlocal p3x, p3y, p3z\n\tlocal minX, minY, minZ\n\tlocal maxX, maxY, maxZ\n\n\tlocal bboxArray = {}\n\n\tfor i=1, #trianglesArray / 9 do\n\t\tp1x = trianglesArray[1+(i-1)*9]\n\t\tp1y = trianglesArray[1+(i-1)*9+1]\n\t\tp1z = trianglesArray[1+(i-1)*9+2]\n\t\tp2x = trianglesArray[1+(i-1)*9+3]\n\t\tp2y = trianglesArray[1+(i-1)*9+4]\n\t\tp2z = trianglesArray[1+(i-1)*9+5]\n\t\tp3x = trianglesArray[1+(i-1)*9+6]\n\t\tp3y = trianglesArray[1+(i-1)*9+7]\n\t\tp3z = trianglesArray[1+(i-1)*9+8]\n\n\t\tminX = math.min(p1x, p2x, p3x)\n\t\tminY = math.min(p1y, p2y, p3y)\n\t\tminZ = math.min(p1z, p2z, p3z)\n\t\tmaxX = math.max(p1x, p2x, p3x)\n\t\tmaxY = math.max(p1y, p2y, p3y)\n\t\tmaxZ = math.max(p1z, p2z, p3z)\n\n\t\tBVH.setBox(bboxArray, i, i, minX, minY, minZ, maxX, maxY, maxZ)\n\tend\n\n\treturn bboxArray\nend\n\nfunction BVH:calcExtents(startIndex, endIndex, expandBy)\n\texpandBy = expandBy or 0\n\n\tif startIndex > endIndex then\n\t\treturn { vec3(), vec3() }\n\tend\n\n\tlocal minX =  math.huge\n\tlocal minY =  math.huge\n\tlocal minZ =  math.huge\n\tlocal maxX = -math.huge\n\tlocal maxY = -math.huge\n\tlocal maxZ = -math.huge\n\n\tfor i=startIndex, endIndex do\n\t\tminX = math.min(self._bboxArray[1+(i-1)*7+1], minX)\n\t\tminY = math.min(self._bboxArray[1+(i-1)*7+2], minY)\n\t\tminZ = math.min(self._bboxArray[1+(i-1)*7+3], minZ)\n\t\tmaxX = math.max(self._bboxArray[1+(i-1)*7+4], maxX)\n\t\tmaxY = math.max(self._bboxArray[1+(i-1)*7+5], maxY)\n\t\tmaxZ = math.max(self._bboxArray[1+(i-1)*7+6], maxZ)\n\tend\n\n\treturn {\n\t\tvec3(minX - expandBy, minY - expandBy, minZ - expandBy),\n\t\tvec3(maxX + expandBy, maxY + expandBy, maxZ + expandBy)\n\t}\nend\n\nfunction BVH:splitNode(node)\n\tlocal num_elements = node:elementCount()\n\tif (num_elements <= self._maxTrianglesPerNode) or (num_elements <= 0) then\n\t\treturn\n\tend\n\n\tlocal startIndex = node._startIndex\n\tlocal endIndex   = node._endIndex\n\n\tlocal leftNode  = { {},{},{} }\n\tlocal rightNode = { {},{},{} }\n\tlocal extentCenters = { node:centerX(), node:centerY(), node:centerZ() }\n\n\tlocal extentsLength = {\n\t\tnode._extentsMax.x - node._extentsMin.x,\n\t\tnode._extentsMax.y - node._extentsMin.y,\n\t\tnode._extentsMax.z - node._extentsMin.z\n\t}\n\n\tlocal objectCenter = {}\n\tfor i=startIndex, endIndex do\n\t\tobjectCenter[1] = (self._bboxArray[1+(i-1)*7+1] + self._bboxArray[1+(i-1)*7+4]) * 0.5 -- center = (min + max) / 2\n\t\tobjectCenter[2] = (self._bboxArray[1+(i-1)*7+2] + self._bboxArray[1+(i-1)*7+5]) * 0.5 -- center = (min + max) / 2\n\t\tobjectCenter[3] = (self._bboxArray[1+(i-1)*7+3] + self._bboxArray[1+(i-1)*7+6]) * 0.5 -- center = (min + max) / 2\n\n\t\tfor j=1, 3 do\n\t\t\tif objectCenter[j] < extentCenters[j] then\n\t\t\t\ttable.insert(leftNode[j], i)\n\t\t\telse\n\t\t\t\ttable.insert(rightNode[j], i)\n\t\t\tend\n\t\tend\n\tend\n\n\t-- check if we couldn't split the node by any of the axes (x, y or z). halt\n\t-- here, dont try to split any more (cause it will always fail, and we'll\n\t-- enter an infinite loop\n\tlocal splitFailed = {\n\t\t#leftNode[1] == 0 or #rightNode[1] == 0,\n\t\t#leftNode[2] == 0 or #rightNode[2] == 0,\n\t\t#leftNode[3] == 0 or #rightNode[3] == 0\n\t}\n\n\tif splitFailed[1] and splitFailed[2] and splitFailed[3] then\n\t\treturn\n\tend\n\n\t-- choose the longest split axis. if we can't split by it, choose next best one.\n\tlocal splitOrder = { 1, 2, 3 }\n\ttable.sort(splitOrder, function(a, b)\n\t\treturn extentsLength[a] > extentsLength[b]\n\tend)\n\n\tlocal leftElements\n\tlocal rightElements\n\n\tfor i=1, 3 do\n\t\tlocal candidateIndex = splitOrder[i]\n\t\tif not splitFailed[candidateIndex] then\n\t\t\tleftElements  = leftNode[candidateIndex]\n\t\t\trightElements = rightNode[candidateIndex]\n\t\t\tbreak\n\t\tend\n\tend\n\n\t-- sort the elements in range (startIndex, endIndex) according to which node they should be at\n\tlocal node0Start = startIndex\n\tlocal node1Start = node0Start + #leftElements\n\tlocal node0End = node1Start - 1\n\tlocal node1End = endIndex\n\tlocal currElement\n\n\tlocal helperPos = node._startIndex\n\tlocal concatenatedElements = {}\n\n\tfor _, element in ipairs(leftElements) do\n\t\ttable.insert(concatenatedElements, element)\n\tend\n\n\tfor _, element in ipairs(rightElements) do\n\t\ttable.insert(concatenatedElements, element)\n\tend\n\n\t-- print(#leftElements, #rightElements, #concatenatedElements)\n\n\tfor i=1, #concatenatedElements do\n\t\tcurrElement = concatenatedElements[i]\n\t\tBVH.copyBox(self._bboxArray, currElement, self._bboxHelper, helperPos)\n\t\thelperPos = helperPos + 1\n\tend\n\n\t-- copy results back to main array\n\tfor i=1+(node._startIndex-1)*7, node._endIndex*7 do\n\t\tself._bboxArray[i] = self._bboxHelper[i]\n\tend\n\n\t-- create 2 new nodes for the node we just split, and add links to them from the parent node\n\tlocal node0Extents = self:calcExtents(node0Start, node0End, EPSILON)\n\tlocal node1Extents = self:calcExtents(node1Start, node1End, EPSILON)\n\n\tlocal node0 = Node(node0Extents[1], node0Extents[2], node0Start, node0End, node._level + 1)\n\tlocal node1 = Node(node1Extents[1], node1Extents[2], node1Start, node1End, node._level + 1)\n\n\tnode._node0 = node0\n\tnode._node1 = node1\n\tnode:clearShapes()\n\n\t-- add new nodes to the split queue\n\ttable.insert(self._nodes_to_split, node0)\n\ttable.insert(self._nodes_to_split, node1)\nend\n\nfunction BVH._calcTValues(minVal, maxVal, rayOriginCoord, invdir)\n\tlocal res = { min=0, max=0 }\n\n\tif invdir >= 0 then\n\t\tres.min = ( minVal - rayOriginCoord ) * invdir\n\t\tres.max = ( maxVal - rayOriginCoord ) * invdir\n\telse\n\t\tres.min = ( maxVal - rayOriginCoord ) * invdir\n\t\tres.max = ( minVal - rayOriginCoord ) * invdir\n\tend\n\n\treturn res\nend\n\nfunction BVH.intersectNodeBox(rayOrigin, invRayDirection, node)\n\tlocal t  = BVH._calcTValues(node._extentsMin.x, node._extentsMax.x, rayOrigin.x, invRayDirection.x)\n\tlocal ty = BVH._calcTValues(node._extentsMin.y, node._extentsMax.y, rayOrigin.y, invRayDirection.y)\n\n\tif t.min > ty.max or ty.min > t.max then\n\t\treturn false\n\tend\n\n\t-- These lines also handle the case where tmin or tmax is NaN\n\t-- (result of 0 * Infinity). x !== x returns true if x is NaN\n\tif ty.min > t.min or t.min ~= t.min then\n\t\tt.min = ty.min\n\tend\n\n\tif ty.max < t.max or t.max ~= t.max then\n\t\tt.max = ty.max\n\tend\n\n\tlocal tz = BVH._calcTValues(node._extentsMin.z, node._extentsMax.z, rayOrigin.z, invRayDirection.z)\n\n\tif t.min > tz.max or tz.min > t.max then\n\t\treturn false\n\tend\n\n\tif tz.min > t.min or t.min ~= t.min then\n\t\tt.min = tz.min\n\tend\n\n\tif tz.max < t.max or t.max ~= t.max then\n\t\tt.max = tz.max\n\tend\n\n\t--return point closest to the ray (positive side)\n\tif t.max < 0 then\n\t\treturn false\n\tend\n\n\treturn true\nend\n\nfunction BVH.setBox(bboxArray, pos, triangleId, minX, minY, minZ, maxX, maxY, maxZ)\n\tbboxArray[1+(pos-1)*7]   = triangleId\n\tbboxArray[1+(pos-1)*7+1] = minX\n\tbboxArray[1+(pos-1)*7+2] = minY\n\tbboxArray[1+(pos-1)*7+3] = minZ\n\tbboxArray[1+(pos-1)*7+4] = maxX\n\tbboxArray[1+(pos-1)*7+5] = maxY\n\tbboxArray[1+(pos-1)*7+6] = maxZ\nend\n\nfunction BVH.copyBox(sourceArray, sourcePos, destArray, destPos)\n\tdestArray[1+(destPos-1)*7]   = sourceArray[1+(sourcePos-1)*7]\n\tdestArray[1+(destPos-1)*7+1] = sourceArray[1+(sourcePos-1)*7+1]\n\tdestArray[1+(destPos-1)*7+2] = sourceArray[1+(sourcePos-1)*7+2]\n\tdestArray[1+(destPos-1)*7+3] = sourceArray[1+(sourcePos-1)*7+3]\n\tdestArray[1+(destPos-1)*7+4] = sourceArray[1+(sourcePos-1)*7+4]\n\tdestArray[1+(destPos-1)*7+5] = sourceArray[1+(sourcePos-1)*7+5]\n\tdestArray[1+(destPos-1)*7+6] = sourceArray[1+(sourcePos-1)*7+6]\nend\n\nfunction BVH.getBox(bboxArray, pos, outputBox)\n\toutputBox.triangleId = bboxArray[1+(pos-1)*7]\n\toutputBox.minX       = bboxArray[1+(pos-1)*7+1]\n\toutputBox.minY       = bboxArray[1+(pos-1)*7+2]\n\toutputBox.minZ       = bboxArray[1+(pos-1)*7+3]\n\toutputBox.maxX       = bboxArray[1+(pos-1)*7+4]\n\toutputBox.maxY       = bboxArray[1+(pos-1)*7+5]\n\toutputBox.maxZ       = bboxArray[1+(pos-1)*7+6]\nend\n\nlocal function new_node(extentsMin, extentsMax, startIndex, endIndex, level)\n\treturn setmetatable({\n\t\t_extentsMin = extentsMin,\n\t\t_extentsMax = extentsMax,\n\t\t_startIndex = startIndex,\n\t\t_endIndex   = endIndex,\n\t\t_level      = level\n\t\t--_node0    = nil\n\t\t--_node1    = nil\n\t}, BVHNode)\nend\n\nfunction BVHNode:elementCount()\n\treturn (self._endIndex + 1) - self._startIndex\nend\n\nfunction BVHNode:centerX()\n\treturn (self._extentsMin.x + self._extentsMax.x) * 0.5\nend\n\nfunction BVHNode:centerY()\n\treturn (self._extentsMin.y + self._extentsMax.y) * 0.5\nend\n\nfunction BVHNode:centerZ()\n\treturn (self._extentsMin.z + self._extentsMax.z) * 0.5\nend\n\nfunction BVHNode:clearShapes()\n\tself._startIndex =  0\n\tself._endIndex   = -1\nend\n\nfunction BVHNode.ngSphereRadius(extentsMin, extentsMax)\n\tlocal centerX = (extentsMin.x + extentsMax.x) * 0.5\n\tlocal centerY = (extentsMin.y + extentsMax.y) * 0.5\n\tlocal centerZ = (extentsMin.z + extentsMax.z) * 0.5\n\n\tlocal extentsMinDistSqr =\n\t\t(centerX - extentsMin.x) * (centerX - extentsMin.x) +\n\t\t(centerY - extentsMin.y) * (centerY - extentsMin.y) +\n\t\t(centerZ - extentsMin.z) * (centerZ - extentsMin.z)\n\n\tlocal extentsMaxDistSqr =\n\t\t(centerX - extentsMax.x) * (centerX - extentsMax.x) +\n\t\t(centerY - extentsMax.y) * (centerY - extentsMax.y) +\n\t\t(centerZ - extentsMax.z) * (centerZ - extentsMax.z)\n\n\treturn math.sqrt(math.max(extentsMinDistSqr, extentsMaxDistSqr))\nend\n\n--[[\n\n--- Draws node boundaries visually for debugging.\n-- @param cube Cube model to draw\n-- @param depth Used for recurcive calls to self method\nfunction OctreeNode:draw_bounds(cube, depth)\n\tdepth = depth or 0\n\tlocal tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically\n\n\tlove.graphics.setColor(tint * 255, 0, (1 - tint) * 255)\n\tlocal m = mat4()\n\t\t:translate(self.center)\n\t\t:scale(vec3(self.adjLength, self.adjLength, self.adjLength))\n\n\tlove.graphics.updateMatrix(\"transform\", m)\n\tlove.graphics.setWireframe(true)\n\tlove.graphics.draw(cube)\n\tlove.graphics.setWireframe(false)\n\n\tfor _, child in ipairs(self.children) do\n\t\tchild:draw_bounds(cube, depth + 1)\n\tend\n\n\tlove.graphics.setColor(255, 255, 255)\nend\n\n--- Draws the bounds of all objects in the tree visually for debugging.\n-- @param cube Cube model to draw\n-- @param filter a function returning true or false to determine visibility.\nfunction OctreeNode:draw_objects(cube, filter)\n\tlocal tint = self.baseLength / 20\n\tlove.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)\n\n\tfor _, object in ipairs(self.objects) do\n\t\tif filter and filter(object.data) or not filter then\n\t\t\tlocal m = mat4()\n\t\t\t\t:translate(object.bounds.center)\n\t\t\t\t:scale(object.bounds.size)\n\n\t\t\tlove.graphics.updateMatrix(\"transform\", m)\n\t\t\tlove.graphics.draw(cube)\n\t\tend\n\tend\n\n\tfor _, child in ipairs(self.children) do\n\t\tchild:draw_objects(cube, filter)\n\tend\n\n\tlove.graphics.setColor(255, 255, 255)\nend\n\n--]]\n\nNode = setmetatable({\n\tnew = new_node\n}, {\n\t__call = function(_, ...) return new_node(...) end\n})\n\nreturn setmetatable({\n\tnew = new\n}, {\n\t__call = function(_, ...) return new(...) end\n})\n"
  },
  {
    "path": "modules/color.lua",
    "content": "--- Color utilities\n-- @module color\n\nlocal modules  = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal constants = require(modules .. \"constants\")\nlocal utils    = require(modules .. \"utils\")\nlocal precond  = require(modules .. \"_private_precond\")\nlocal color    = {}\nlocal color_mt = {}\n\nlocal function new(r, g, b, a)\n\tlocal c = { r, g, b, a }\n\tc._c = c\n\treturn setmetatable(c, color_mt)\nend\n\n-- HSV utilities (adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html)\n-- hsv_to_color(hsv)\n-- Converts a set of HSV values to a color. hsv is a table.\n-- See also: hsv(h, s, v)\nlocal function hsv_to_color(hsv)\n\tlocal i\n\tlocal f, q, p, t\n\tlocal h, s, v\n\tlocal a = hsv[4] or 1\n\ts = hsv[2]\n\tv = hsv[3]\n\n\tif s == 0 then\n\t\treturn new(v, v, v, a)\n\tend\n\n\th = hsv[1] * 6 -- sector 0 to 5\n\n\ti = math.floor(h)\n\tf = h - i -- factorial part of h\n\tp = v * (1-s)\n\tq = v * (1-s*f)\n\tt = v * (1-s*(1-f))\n\n\tif     i == 0 then return new(v, t, p, a)\n\telseif i == 1 then return new(q, v, p, a)\n\telseif i == 2 then return new(p, v, t, a)\n\telseif i == 3 then return new(p, q, v, a)\n\telseif i == 4 then return new(t, p, v, a)\n\telse               return new(v, p, q, a)\n\tend\nend\n\n-- color_to_hsv(c)\n-- Takes in a normal color and returns a table with the HSV values.\nlocal function color_to_hsv(c)\n\tlocal r = c[1]\n\tlocal g = c[2]\n\tlocal b = c[3]\n\tlocal a = c[4] or 1\n\tlocal h, s, v\n\n\tlocal min = math.min(r, g, b)\n\tlocal max = math.max(r, g, b)\n\tv = max\n\n\tlocal delta = max - min\n\n\t-- black, nothing else is really possible here.\n\tif min == 0 and max == 0 then\n\t\treturn { 0, 0, 0, a }\n\tend\n\n\tif max ~= 0 then\n\t\ts = delta / max\n\telse\n\t\t-- r = g = b = 0 s = 0, v is undefined\n\t\ts = 0\n\t\th = -1\n\t\treturn { h, s, v, 1 }\n\tend\n\n\t-- Prevent division by zero.\n\tif delta == 0 then\n\t\tdelta = constants.DBL_EPSILON\n\tend\n\n\tif r == max then\n\t\th = ( g - b ) / delta     -- yellow/magenta\n\telseif g == max then\n\t\th = 2 + ( b - r ) / delta -- cyan/yellow\n\telse\n\t\th = 4 + ( r - g ) / delta -- magenta/cyan\n\tend\n\n\th = h / 6 -- normalize from segment 0..5\n\n\tif h < 0 then\n\t\th = h + 1\n\tend\n\n\treturn { h, s, v, a }\nend\n\n--- The public constructor.\n-- @param x Can be of three types: </br>\n-- number red component 0-1\n-- table {r, g, b, a}\n-- nil for {0,0,0,0}\n-- @tparam number g Green component 0-1\n-- @tparam number b Blue component 0-1\n-- @tparam number a Alpha component 0-1\n-- @treturn color out\nfunction color.new(r, g, b, a)\n\t-- number, number, number, number\n\tif r and g and b and a then\n\t\tprecond.typeof(r, \"number\", \"new: Wrong argument type for r\")\n\t\tprecond.typeof(g, \"number\", \"new: Wrong argument type for g\")\n\t\tprecond.typeof(b, \"number\", \"new: Wrong argument type for b\")\n\t\tprecond.typeof(a, \"number\", \"new: Wrong argument type for a\")\n\n\t\treturn new(r, g, b, a)\n\n\t-- {r, g, b, a}\n\telseif type(r) == \"table\" then\n\t\tlocal rr, gg, bb, aa = r[1], r[2], r[3], r[4]\n\t\tprecond.typeof(rr, \"number\", \"new: Wrong argument type for r\")\n\t\tprecond.typeof(gg, \"number\", \"new: Wrong argument type for g\")\n\t\tprecond.typeof(bb, \"number\", \"new: Wrong argument type for b\")\n\t\tprecond.typeof(aa, \"number\", \"new: Wrong argument type for a\")\n\n\t\treturn new(rr, gg, bb, aa)\n\tend\n\n\treturn new(0, 0, 0, 0)\nend\n\n--- Convert hue,saturation,value table to color object.\n-- @tparam table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}\n-- @treturn color out\ncolor.hsv_to_color_table = hsv_to_color\n\n--- Convert color to hue,saturation,value table\n-- @tparam color in\n-- @treturn table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}\ncolor.color_to_hsv_table = color_to_hsv\n\n--- Convert hue,saturation,value to color object.\n-- @tparam number h hue 0-1\n-- @tparam number s saturation 0-1\n-- @tparam number v value 0-1\n-- @treturn color out\nfunction color.from_hsv(h, s, v)\n\treturn hsv_to_color { h, s, v }\nend\n\n--- Convert hue,saturation,value to color object.\n-- @tparam number h hue 0-1\n-- @tparam number s saturation 0-1\n-- @tparam number v value 0-1\n-- @tparam number a alpha 0-1\n-- @treturn color out\nfunction color.from_hsva(h, s, v, a)\n\treturn hsv_to_color { h, s, v, a }\nend\n\n--- Invert a color.\n-- @tparam color to invert\n-- @treturn color out\nfunction color.invert(c)\n\treturn new(1 - c[1], 1 - c[2], 1 - c[3], c[4])\nend\n\n--- Lighten a color by a component-wise fixed amount (alpha unchanged)\n-- @tparam color to lighten\n-- @tparam number amount to increase each component by, 0-1 scale\n-- @treturn color out\nfunction color.lighten(c, v)\n\treturn new(\n\t\tutils.clamp(c[1] + v, 0, 1),\n\t\tutils.clamp(c[2] + v, 0, 1),\n\t\tutils.clamp(c[3] + v, 0, 1),\n\t\tc[4]\n\t)\nend\n\n--- Interpolate between two colors.\n-- @tparam color at start\n-- @tparam color at end\n-- @tparam number s in 0-1 progress between the two colors\n-- @treturn color out\nfunction color.lerp(a, b, s)\n\treturn a + s * (b - a)\nend\n\n--- Unpack a color into individual components in 0-1.\n-- @tparam color to unpack\n-- @treturn number r in 0-1\n-- @treturn number g in 0-1\n-- @treturn number b in 0-1\n-- @treturn number a in 0-1\nfunction color.unpack(c)\n\treturn c[1], c[2], c[3], c[4]\nend\n\n--- Unpack a color into individual components in 0-255.\n-- @tparam color to unpack\n-- @treturn number r in 0-255\n-- @treturn number g in 0-255\n-- @treturn number b in 0-255\n-- @treturn number a in 0-255\nfunction color.as_255(c)\n\treturn c[1] * 255, c[2] * 255, c[3] * 255, c[4] * 255\nend\n\n--- Darken a color by a component-wise fixed amount (alpha unchanged)\n-- @tparam color to darken\n-- @tparam number amount to decrease each component by, 0-1 scale\n-- @treturn color out\nfunction color.darken(c, v)\n\treturn new(\n\t\tutils.clamp(c[1] - v, 0, 1),\n\t\tutils.clamp(c[2] - v, 0, 1),\n\t\tutils.clamp(c[3] - v, 0, 1),\n\t\tc[4]\n\t)\nend\n\n--- Multiply a color's components by a value (alpha unchanged)\n-- @tparam color to multiply\n-- @tparam number to multiply each component by\n-- @treturn color out\nfunction color.multiply(c, v)\n\tlocal t = color.new()\n\tfor i = 1, 3 do\n\t\tt[i] = c[i] * v\n\tend\n\n\tt[4] = c[4]\n\treturn t\nend\n\n-- directly set alpha channel\n-- @tparam color to alter\n-- @tparam number new alpha 0-1\n-- @treturn color out\nfunction color.alpha(c, v)\n\tlocal t = color.new()\n\tfor i = 1, 3 do\n\t\tt[i] = c[i]\n\tend\n\n\tt[4] = v\n\treturn t\nend\n\n--- Multiply a color's alpha by a value\n-- @tparam color to multiply\n-- @tparam number to multiply alpha by\n-- @treturn color out\nfunction color.opacity(c, v)\n\tlocal t = color.new()\n\tfor i = 1, 3 do\n\t\tt[i] = c[i]\n\tend\n\n\tt[4] = c[4] * v\n\treturn t\nend\n\n--- Set a color's hue (saturation, value, alpha unchanged)\n-- @tparam color to alter\n-- @tparam hue to set 0-1\n-- @treturn color out\nfunction color.hue(col, hue)\n\tlocal c = color_to_hsv(col)\n\tc[1] = (hue + 1) % 1\n\treturn hsv_to_color(c)\nend\n\n--- Set a color's saturation (hue, value, alpha unchanged)\n-- @tparam color to alter\n-- @tparam saturation to set 0-1\n-- @treturn color out\nfunction color.saturation(col, percent)\n\tlocal c = color_to_hsv(col)\n\tc[2] = utils.clamp(percent, 0, 1)\n\treturn hsv_to_color(c)\nend\n\n--- Set a color's value (saturation, hue, alpha unchanged)\n-- @tparam color to alter\n-- @tparam value to set 0-1\n-- @treturn color out\nfunction color.value(col, percent)\n\tlocal c = color_to_hsv(col)\n\tc[3] = utils.clamp(percent, 0, 1)\n\treturn hsv_to_color(c)\nend\n\n-- https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ\nfunction color.gamma_to_linear(r, g, b, a)\n\tlocal function convert(c)\n\t\tif c > 1.0 then\n\t\t\treturn 1.0\n\t\telseif c < 0.0 then\n\t\t\treturn 0.0\n\t\telseif c <= 0.04045 then\n\t\t\treturn c / 12.92\n\t\telse\n\t\t\treturn math.pow((c + 0.055) / 1.055, 2.4)\n\t\tend\n\tend\n\n\tif type(r) == \"table\" then\n\t\tlocal c = {}\n\t\tfor i = 1, 3 do\n\t\t\tc[i] = convert(r[i])\n\t\tend\n\n\t\tc[4] = r[4]\n\t\treturn c\n\telse\n\t\treturn convert(r), convert(g), convert(b), a or 1\n\tend\nend\n\n-- https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB\nfunction color.linear_to_gamma(r, g, b, a)\n\tlocal function convert(c)\n\t\tif c > 1.0 then\n\t\t\treturn 1.0\n\t\telseif c < 0.0 then\n\t\t\treturn 0.0\n\t\telseif c < 0.0031308 then\n\t\t\treturn c * 12.92\n\t\telse\n\t\t\treturn 1.055 * math.pow(c, 0.41666) - 0.055\n\t\tend\n\tend\n\n\tif type(r) == \"table\" then\n\t\tlocal c = {}\n\t\tfor i = 1, 3 do\n\t\t\tc[i] = convert(r[i])\n\t\tend\n\n\t\tc[4] = r[4]\n\t\treturn c\n\telse\n\t\treturn convert(r), convert(g), convert(b), a or 1\n\tend\nend\n\n--- Check if color is valid\n-- @tparam color to test\n-- @treturn boolean is color\nfunction color.is_color(a)\n\tif type(a) ~= \"table\" then\n\t\treturn false\n\tend\n\n\tfor i = 1, 4 do\n\t\tif type(a[i]) ~= \"number\" then\n\t\t\treturn false\n\t\tend\n\tend\n\n\treturn true\nend\n\n--- Return a formatted string.\n-- @tparam color a color to be turned into a string\n-- @treturn string formatted\nfunction color.to_string(a)\n\treturn string.format(\"[ %3.0f, %3.0f, %3.0f, %3.0f ]\", a[1], a[2], a[3], a[4])\nend\n\ncolor_mt.__index = color\ncolor_mt.__tostring = color.to_string\n\nfunction color_mt.__call(_, r, g, b, a)\n\treturn color.new(r, g, b, a)\nend\n\nfunction color_mt.__add(a, b)\n\treturn new(a[1] + b[1], a[2] + b[2], a[3] + b[3], a[4] + b[4])\nend\n\nfunction color_mt.__sub(a, b)\n\treturn new(a[1] - b[1], a[2] - b[2], a[3] - b[3], a[4] - b[4])\nend\n\nfunction color_mt.__mul(a, b)\n\tif type(a) == \"number\" then\n\t\treturn new(a * b[1], a * b[2], a * b[3], a * b[4])\n\telseif type(b) == \"number\" then\n\t\treturn new(b * a[1], b * a[2], b * a[3], b * a[4])\n\telse\n\t\treturn new(a[1] * b[1], a[2] * b[2], a[3] * b[3], a[4] * b[4])\n\tend\nend\n\nreturn setmetatable({}, color_mt)\n"
  },
  {
    "path": "modules/constants.lua",
    "content": "--- Various useful constants\n-- @module constants\n\n--- Constants\n-- @table constants\n-- @field FLT_EPSILON Floating point precision breaks down\n-- @field DBL_EPSILON Double-precise floating point precision breaks down\n-- @field DOT_THRESHOLD Close enough to 1 for interpolations to occur\nlocal constants = {}\n\n-- same as C's FLT_EPSILON\nconstants.FLT_EPSILON = 1.19209290e-07\n\n-- same as C's DBL_EPSILON\nconstants.DBL_EPSILON = 2.2204460492503131e-16\n\n-- used for quaternion.slerp\nconstants.DOT_THRESHOLD = 0.9995\n\nreturn constants\n"
  },
  {
    "path": "modules/intersect.lua",
    "content": "--- Various geometric intersections\n-- @module intersect\n\nlocal modules     = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal constants   = require(modules .. \"constants\")\nlocal mat4        = require(modules .. \"mat4\")\nlocal vec3        = require(modules .. \"vec3\")\nlocal utils       = require(modules .. \"utils\")\nlocal DBL_EPSILON = constants.DBL_EPSILON\nlocal sqrt        = math.sqrt\nlocal abs         = math.abs\nlocal min         = math.min\nlocal max         = math.max\nlocal intersect   = {}\n\n-- Checks if a point belongs to the segment\n-- p is a vec3\n-- seg[1] is a vec3\n-- seg[2] is a vec3\n-- Returns a boolean\nfunction intersect.point_segment(p, seg)\n\tlocal min, max = vec3.component_sort(seg[1], seg[2])\n\tif min.x <= p.x and\n\t\tmin.y <= p.y and\n\t\tmin.z <= p.z and\n\t\tp.x <= max.x and\n\t\tp.y <= max.y and\n\t\tp.z <= max.z\n\tthen\n\t\treturn true\n\tend\n\treturn false\nend\n\n-- https://blogs.msdn.microsoft.com/rezanour/2011/08/07/barycentric-coordinates-and-point-in-triangle-tests/\n-- point       is a vec3\n-- triangle[1] is a vec3\n-- triangle[2] is a vec3\n-- triangle[3] is a vec3\nfunction intersect.point_triangle(point, triangle)\n\tlocal u = triangle[2] - triangle[1]\n\tlocal v = triangle[3] - triangle[1]\n\tlocal w = point       - triangle[1]\n\n\tlocal vw = v:cross(w)\n\tlocal vu = v:cross(u)\n\n\tif vw:dot(vu) < 0 then\n\t\treturn false\n\tend\n\n\tlocal uw = u:cross(w)\n\tlocal uv = u:cross(v)\n\n\tif uw:dot(uv) < 0 then\n\t\treturn false\n\tend\n\n\tlocal d = uv:len()\n\tlocal r = vw:len() / d\n\tlocal t = uw:len() / d\n\n\treturn r + t <= 1\nend\n\n-- point    is a vec3\n-- aabb.min is a vec3\n-- aabb.max is a vec3\nfunction intersect.point_aabb(point, aabb)\n\treturn\n\t\taabb.min.x <= point.x and\n\t\taabb.max.x >= point.x and\n\t\taabb.min.y <= point.y and\n\t\taabb.max.y >= point.y and\n\t\taabb.min.z <= point.z and\n\t\taabb.max.z >= point.z\nend\n\n-- point          is a vec3\n-- frustum.left   is a plane { a, b, c, d }\n-- frustum.right  is a plane { a, b, c, d }\n-- frustum.bottom is a plane { a, b, c, d }\n-- frustum.top    is a plane { a, b, c, d }\n-- frustum.near   is a plane { a, b, c, d }\n-- frustum.far    is a plane { a, b, c, d }\nfunction intersect.point_frustum(point, frustum)\n\tlocal x, y, z = point:unpack()\n\tlocal planes  = {\n\t\tfrustum.left,\n\t\tfrustum.right,\n\t\tfrustum.bottom,\n\t\tfrustum.top,\n\t\tfrustum.near,\n\t\tfrustum.far or false\n\t}\n\n\t-- Skip the last test for infinite projections, it'll never fail.\n\tif not planes[6] then\n\t\ttable.remove(planes)\n\tend\n\n\tlocal dot\n\tfor i = 1, #planes do\n\t\tdot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d\n\t\tif dot <= 0 then\n\t\t\treturn false\n\t\tend\n\tend\n\n\treturn true\nend\n\n-- http://www.lighthouse3d.com/tutorials/maths/ray-triangle-intersection/\n-- ray.position  is a vec3\n-- ray.direction is a vec3\n-- triangle[1]   is a vec3\n-- triangle[2]   is a vec3\n-- triangle[3]   is a vec3\n-- backface_cull is a boolean (optional)\nfunction intersect.ray_triangle(ray, triangle, backface_cull)\n\tlocal e1 = triangle[2] - triangle[1]\n\tlocal e2 = triangle[3] - triangle[1]\n\tlocal h  = ray.direction:cross(e2)\n\tlocal a  = h:dot(e1)\n\n\t-- if a is negative, ray hits the backface\n\tif backface_cull and a < 0 then\n\t\treturn false\n\tend\n\n\t-- if a is too close to 0, ray does not intersect triangle\n\tif abs(a) <= DBL_EPSILON then\n\t\treturn false\n\tend\n\n\tlocal f = 1 / a\n\tlocal s = ray.position - triangle[1]\n\tlocal u = s:dot(h) * f\n\n\t-- ray does not intersect triangle\n\tif u < 0 or u > 1 then\n\t\treturn false\n\tend\n\n\tlocal q = s:cross(e1)\n\tlocal v = ray.direction:dot(q) * f\n\n\t-- ray does not intersect triangle\n\tif v < 0 or u + v > 1 then\n\t\treturn false\n\tend\n\n\t-- at this stage we can compute t to find out where\n\t-- the intersection point is on the line\n\tlocal t = q:dot(e2) * f\n\n\t-- return position of intersection and distance from ray origin\n\tif t >= DBL_EPSILON then\n\t\treturn ray.position + ray.direction * t, t\n\tend\n\n\t-- ray does not intersect triangle\n\treturn false\nend\n\n-- https://gamedev.stackexchange.com/questions/96459/fast-ray-sphere-collision-code\n-- ray.position    is a vec3\n-- ray.direction   is a vec3\n-- sphere.position is a vec3\n-- sphere.radius   is a number\nfunction intersect.ray_sphere(ray, sphere)\n\tlocal offset = ray.position - sphere.position\n\tlocal b = offset:dot(ray.direction)\n\tlocal c = offset:dot(offset) - sphere.radius * sphere.radius\n\n\t-- ray's position outside sphere (c > 0)\n\t-- ray's direction pointing away from sphere (b > 0)\n\tif c > 0 and b > 0 then\n\t\treturn false\n\tend\n\n\tlocal discr = b * b - c\n\n\t-- negative discriminant\n\tif discr < 0 then\n\t\treturn false\n\tend\n\n\t-- Clamp t to 0\n\tlocal t = -b - sqrt(discr)\n\tt = t < 0 and 0 or t\n\n\t-- Return collision point and distance from ray origin\n\treturn ray.position + ray.direction * t, t\nend\n\n-- http://gamedev.stackexchange.com/a/18459\n-- ray.position  is a vec3\n-- ray.direction is a vec3\n-- aabb.min      is a vec3\n-- aabb.max      is a vec3\nfunction intersect.ray_aabb(ray, aabb)\n\tlocal dir     = ray.direction:normalize()\n\tlocal dirfrac = vec3(\n\t\t1 / dir.x,\n\t\t1 / dir.y,\n\t\t1 / dir.z\n\t)\n\n\tlocal t1 = (aabb.min.x - ray.position.x) * dirfrac.x\n\tlocal t2 = (aabb.max.x - ray.position.x) * dirfrac.x\n\tlocal t3 = (aabb.min.y - ray.position.y) * dirfrac.y\n\tlocal t4 = (aabb.max.y - ray.position.y) * dirfrac.y\n\tlocal t5 = (aabb.min.z - ray.position.z) * dirfrac.z\n\tlocal t6 = (aabb.max.z - ray.position.z) * dirfrac.z\n\n\tlocal tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6))\n\tlocal tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6))\n\n\t-- ray is intersecting AABB, but whole AABB is behind us\n\tif tmax < 0 then\n\t\treturn false\n\tend\n\n\t-- ray does not intersect AABB\n\tif tmin > tmax then\n\t\treturn false\n\tend\n\n\t-- Return collision point and distance from ray origin\n\treturn ray.position + ray.direction * tmin, tmin\nend\n\n-- http://stackoverflow.com/a/23976134/1190664\n-- ray.position   is a vec3\n-- ray.direction  is a vec3\n-- plane.position is a vec3\n-- plane.normal   is a vec3\nfunction intersect.ray_plane(ray, plane)\n\tlocal denom = plane.normal:dot(ray.direction)\n\n\t-- ray does not intersect plane\n\tif abs(denom) < DBL_EPSILON then\n\t\treturn false\n\tend\n\n\t-- distance of direction\n\tlocal d = plane.position - ray.position\n\tlocal t = d:dot(plane.normal) / denom\n\n\tif t < DBL_EPSILON then\n\t\treturn false\n\tend\n\n\t-- Return collision point and distance from ray origin\n\treturn ray.position + ray.direction * t, t\nend\n\nfunction intersect.ray_capsule(ray, capsule)\n\tlocal dist2, p1, p2 = intersect.closest_point_segment_segment(\n\t\tray.position,\n\t\tray.position + ray.direction * 1e10,\n\t\tcapsule.a,\n\t\tcapsule.b\n\t)\n\tif dist2 <= capsule.radius^2 then\n\t\treturn p1\n\tend\n\n\treturn false\nend\n\n-- https://web.archive.org/web/20120414063459/http://local.wasp.uwa.edu.au/~pbourke//geometry/lineline3d/\n-- a[1] is a vec3\n-- a[2] is a vec3\n-- b[1] is a vec3\n-- b[2] is a vec3\n-- e    is a number\nfunction intersect.line_line(a, b, e)\n\t-- new points\n\tlocal p13 = a[1] - b[1]\n\tlocal p43 = b[2] - b[1]\n\tlocal p21 = a[2] - a[1]\n\n\t-- if lengths are negative or too close to 0, lines do not intersect\n\tif p43:len2() < DBL_EPSILON or p21:len2() < DBL_EPSILON then\n\t\treturn false\n\tend\n\n\t-- dot products\n\tlocal d1343 = p13:dot(p43)\n\tlocal d4321 = p43:dot(p21)\n\tlocal d1321 = p13:dot(p21)\n\tlocal d4343 = p43:dot(p43)\n\tlocal d2121 = p21:dot(p21)\n\tlocal denom = d2121 * d4343 - d4321 * d4321\n\n\t-- if denom is too close to 0, lines do not intersect\n\tif abs(denom) < DBL_EPSILON then\n\t\treturn false\n\tend\n\n\tlocal numer = d1343 * d4321 - d1321 * d4343\n\tlocal mua   = numer / denom\n\tlocal mub   = (d1343 + d4321 * mua) / d4343\n\n\t-- return positions of intersection on each line\n\tlocal out1 = a[1] + p21 * mua\n\tlocal out2 = b[1] + p43 * mub\n\tlocal dist = out1:dist(out2)\n\n\t-- if distance of the shortest segment between lines is less than threshold\n\tif e and dist > e then\n\t\treturn false\n\tend\n\n\treturn { out1, out2 }, dist\nend\n\n-- a[1] is a vec3\n-- a[2] is a vec3\n-- b[1] is a vec3\n-- b[2] is a vec3\n-- e    is a number\nfunction intersect.segment_segment(a, b, e)\n\tlocal c, d = intersect.line_line(a, b, e)\n\tif c and\n\t\tintersect.point_segment(c[1], a) and\n\t\tintersect.point_segment(c[2], a) and\n\t\tintersect.point_segment(c[1], b) and\n\t\tintersect.point_segment(c[2], b) then\n\t\treturn c, d\n\tend\n\t-- segments do not intersect\n\treturn false\nend\n\n-- a.min is a vec3\n-- a.max is a vec3\n-- b.min is a vec3\n-- b.max is a vec3\nfunction intersect.aabb_aabb(a, b)\n\treturn\n\t\ta.min.x <= b.max.x and\n\t\ta.max.x >= b.min.x and\n\t\ta.min.y <= b.max.y and\n\t\ta.max.y >= b.min.y and\n\t\ta.min.z <= b.max.z and\n\t\ta.max.z >= b.min.z\nend\n\n-- aabb.position is a vec3\n-- aabb.extent   is a vec3 (half-size)\n-- obb.position  is a vec3\n-- obb.extent    is a vec3 (half-size)\n-- obb.rotation  is a mat4\nfunction intersect.aabb_obb(aabb, obb)\n\tlocal a   = aabb.extent\n\tlocal b   = obb.extent\n\tlocal T   = obb.position - aabb.position\n\tlocal rot = mat4():transpose(obb.rotation)\n\tlocal B   = {}\n\tlocal t\n\n\tfor i = 1, 3 do\n\t\tB[i] = {}\n\t\tfor j = 1, 3 do\n\t\t\tassert((i - 1) * 4 + j < 16 and (i - 1) * 4 + j > 0)\n\t\t\tB[i][j] = abs(rot[(i - 1) * 4 + j]) + 1e-6\n\t\tend\n\tend\n\n\tt = abs(T.x)\n\tif not (t <= (b.x + a.x * B[1][1] + b.y * B[1][2] + b.z * B[1][3])) then return false end\n\tt = abs(T.x * B[1][1] + T.y * B[2][1] + T.z * B[3][1])\n\tif not (t <= (b.x + a.x * B[1][1] + a.y * B[2][1] + a.z * B[3][1])) then return false end\n\tt = abs(T.y)\n\tif not (t <= (a.y + b.x * B[2][1] + b.y * B[2][2] + b.z * B[2][3])) then return false end\n\tt = abs(T.z)\n\tif not (t <= (a.z + b.x * B[3][1] + b.y * B[3][2] + b.z * B[3][3])) then return false end\n\tt = abs(T.x * B[1][2] + T.y * B[2][2] + T.z * B[3][2])\n\tif not (t <= (b.y + a.x * B[1][2] + a.y * B[2][2] + a.z * B[3][2])) then return false end\n\tt = abs(T.x * B[1][3] + T.y * B[2][3] + T.z * B[3][3])\n\tif not (t <= (b.z + a.x * B[1][3] + a.y * B[2][3] + a.z * B[3][3])) then return false end\n\tt = abs(T.z * B[2][1] - T.y * B[3][1])\n\tif 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\n\tt = abs(T.z * B[2][2] - T.y * B[3][2])\n\tif 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\n\tt = abs(T.z * B[2][3] - T.y * B[3][3])\n\tif 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\n\tt = abs(T.x * B[3][1] - T.z * B[1][1])\n\tif 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\n\tt = abs(T.x * B[3][2] - T.z * B[1][2])\n\tif 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\n\tt = abs(T.x * B[3][3] - T.z * B[1][3])\n\tif 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\n\tt = abs(T.y * B[1][1] - T.x * B[2][1])\n\tif 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\n\tt = abs(T.y * B[1][2] - T.x * B[2][2])\n\tif 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\n\tt = abs(T.y * B[1][3] - T.x * B[2][3])\n\tif 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\n\n\t-- https://gamedev.stackexchange.com/questions/24078/which-side-was-hit\n\t-- Minkowski Sum\n\tlocal wy = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.y - obb.position.y)\n\tlocal hx = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.x - obb.position.x)\n\n\tif wy.x > hx.x and wy.y > hx.y and wy.z > hx.z then\n\t\tif wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then\n\t\t\treturn vec3(obb.rotation * {  0, -1, 0, 1 })\n\t\telse\n\t\t\treturn vec3(obb.rotation * { -1,  0, 0, 1 })\n\t\tend\n\telse\n\t\tif wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then\n\t\t\treturn vec3(obb.rotation * { 1, 0, 0, 1 })\n\t\telse\n\t\t\treturn vec3(obb.rotation * { 0, 1, 0, 1 })\n\t\tend\n\tend\nend\n\n-- http://stackoverflow.com/a/4579069/1190664\n-- aabb.min        is a vec3\n-- aabb.max        is a vec3\n-- sphere.position is a vec3\n-- sphere.radius   is a number\nlocal axes = { \"x\", \"y\", \"z\" }\nfunction intersect.aabb_sphere(aabb, sphere)\n\tlocal dist2 = sphere.radius ^ 2\n\n\tfor _, axis in ipairs(axes) do\n\t\tlocal pos  = sphere.position[axis]\n\t\tlocal amin = aabb.min[axis]\n\t\tlocal amax = aabb.max[axis]\n\n\t\tif pos < amin then\n\t\t\tdist2 = dist2 - (pos - amin) ^ 2\n\t\telseif pos > amax then\n\t\t\tdist2 = dist2 - (pos - amax) ^ 2\n\t\tend\n\tend\n\n\treturn dist2 > 0\nend\n\n-- aabb.min       is a vec3\n-- aabb.max       is a vec3\n-- frustum.left   is a plane { a, b, c, d }\n-- frustum.right  is a plane { a, b, c, d }\n-- frustum.bottom is a plane { a, b, c, d }\n-- frustum.top    is a plane { a, b, c, d }\n-- frustum.near   is a plane { a, b, c, d }\n-- frustum.far    is a plane { a, b, c, d }\nfunction intersect.aabb_frustum(aabb, frustum)\n\t-- Indexed for the 'index trick' later\n\tlocal box = {\n\t\taabb.min,\n\t\taabb.max\n\t}\n\n\t-- We have 6 planes defining the frustum, 5 if infinite.\n\tlocal planes = {\n\t\tfrustum.left,\n\t\tfrustum.right,\n\t\tfrustum.bottom,\n\t\tfrustum.top,\n\t\tfrustum.near,\n\t\tfrustum.far or false\n\t}\n\n\t-- Skip the last test for infinite projections, it'll never fail.\n\tif not planes[6] then\n\t\ttable.remove(planes)\n\tend\n\n\tfor i = 1, #planes do\n\t\t-- This is the current plane\n\t\tlocal p = planes[i]\n\n\t\t-- p-vertex selection (with the index trick)\n\t\t-- According to the plane normal we can know the\n\t\t-- indices of the positive vertex\n\t\tlocal px = p.a > 0.0 and 2 or 1\n\t\tlocal py = p.b > 0.0 and 2 or 1\n\t\tlocal pz = p.c > 0.0 and 2 or 1\n\n\t\t-- project p-vertex on plane normal\n\t\t-- (How far is p-vertex from the origin)\n\t\tlocal dot = (p.a * box[px].x) + (p.b * box[py].y) + (p.c * box[pz].z)\n\n\t\t-- Doesn't intersect if it is behind the plane\n\t\tif dot < -p.d then\n\t\t\treturn false\n\t\tend\n\tend\n\n\treturn true\nend\n\n-- outer.min is a vec3\n-- outer.max is a vec3\n-- inner.min is a vec3\n-- inner.max is a vec3\nfunction intersect.encapsulate_aabb(outer, inner)\n\treturn\n\t\touter.min.x <= inner.min.x and\n\t\touter.max.x >= inner.max.x and\n\t\touter.min.y <= inner.min.y and\n\t\touter.max.y >= inner.max.y and\n\t\touter.min.z <= inner.min.z and\n\t\touter.max.z >= inner.max.z\nend\n\n-- a.position is a vec3\n-- a.radius   is a number\n-- b.position is a vec3\n-- b.radius   is a number\nfunction intersect.circle_circle(a, b)\n\treturn a.position:dist(b.position) <= a.radius + b.radius\nend\n\n-- a.position is a vec3\n-- a.radius   is a number\n-- b.position is a vec3\n-- b.radius   is a number\nfunction intersect.sphere_sphere(a, b)\n\treturn intersect.circle_circle(a, b)\nend\n\n-- http://realtimecollisiondetection.net/blog/?p=103\n-- sphere.position is a vec3\n-- sphere.radius   is a number\n-- triangle[1]     is a vec3\n-- triangle[2]     is a vec3\n-- triangle[3]     is a vec3\nfunction intersect.sphere_triangle(sphere, triangle)\n\t-- Sphere is centered at origin\n\tlocal A  = triangle[1] - sphere.position\n\tlocal B  = triangle[2] - sphere.position\n\tlocal C  = triangle[3] - sphere.position\n\n\t-- Compute normal of triangle plane\n\tlocal V  = (B - A):cross(C - A)\n\n\t-- Test if sphere lies outside triangle plane\n\tlocal rr = sphere.radius * sphere.radius\n\tlocal d  = A:dot(V)\n\tlocal e  = V:dot(V)\n\tlocal s1 = d * d > rr * e\n\n\t-- Test if sphere lies outside triangle vertices\n\tlocal aa = A:dot(A)\n\tlocal ab = A:dot(B)\n\tlocal ac = A:dot(C)\n\tlocal bb = B:dot(B)\n\tlocal bc = B:dot(C)\n\tlocal cc = C:dot(C)\n\n\tlocal s2 = (aa > rr) and (ab > aa) and (ac > aa)\n\tlocal s3 = (bb > rr) and (ab > bb) and (bc > bb)\n\tlocal s4 = (cc > rr) and (ac > cc) and (bc > cc)\n\n\t-- Test is sphere lies outside triangle edges\n\tlocal AB = B - A\n\tlocal BC = C - B\n\tlocal CA = A - C\n\n\tlocal d1 = ab - aa\n\tlocal d2 = bc - bb\n\tlocal d3 = ac - cc\n\n\tlocal e1 = AB:dot(AB)\n\tlocal e2 = BC:dot(BC)\n\tlocal e3 = CA:dot(CA)\n\n\tlocal Q1 = A * e1 - AB * d1\n\tlocal Q2 = B * e2 - BC * d2\n\tlocal Q3 = C * e3 - CA * d3\n\n\tlocal QC = C * e1 - Q1\n\tlocal QA = A * e2 - Q2\n\tlocal QB = B * e3 - Q3\n\n\tlocal s5 = (Q1:dot(Q1) > rr * e1 * e1) and (Q1:dot(QC) > 0)\n\tlocal s6 = (Q2:dot(Q2) > rr * e2 * e2) and (Q2:dot(QA) > 0)\n\tlocal s7 = (Q3:dot(Q3) > rr * e3 * e3) and (Q3:dot(QB) > 0)\n\n\t-- Return whether or not any of the tests passed\n\treturn s1 or s2 or s3 or s4 or s5 or s6 or s7\nend\n\n-- sphere.position is a vec3\n-- sphere.radius   is a number\n-- frustum.left    is a plane { a, b, c, d }\n-- frustum.right   is a plane { a, b, c, d }\n-- frustum.bottom  is a plane { a, b, c, d }\n-- frustum.top     is a plane { a, b, c, d }\n-- frustum.near    is a plane { a, b, c, d }\n-- frustum.far     is a plane { a, b, c, d }\nfunction intersect.sphere_frustum(sphere, frustum)\n\tlocal x, y, z = sphere.position:unpack()\n\tlocal planes  = {\n\t\tfrustum.left,\n\t\tfrustum.right,\n\t\tfrustum.bottom,\n\t\tfrustum.top,\n\t\tfrustum.near\n\t}\n\n\tif frustum.far then\n\t\ttable.insert(planes, frustum.far, 5)\n\tend\n\n\tlocal dot\n\tfor i = 1, #planes do\n\t\tdot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d\n\n\t\tif dot <= -sphere.radius then\n\t\t\treturn false\n\t\tend\n\tend\n\n\t-- dot + radius is the distance of the object from the near plane.\n\t-- make sure that the near plane is the last test!\n\treturn dot + sphere.radius\nend\n\nfunction intersect.capsule_capsule(c1, c2)\n\tlocal dist2, p1, p2 = intersect.closest_point_segment_segment(c1.a, c1.b, c2.a, c2.b)\n\tlocal radius = c1.radius + c2.radius\n\n\tif dist2 <= radius * radius then\n\t\treturn p1, p2\n\tend\n\n\treturn false\nend\n\nfunction intersect.closest_point_segment_segment(p1, p2, p3, p4)\n\tlocal s  -- Distance of intersection along segment 1\n\tlocal t  -- Distance of intersection along segment 2\n\tlocal c1 -- Collision point on segment 1\n\tlocal c2 -- Collision point on segment 2\n\n\tlocal d1 = p2 - p1 -- Direction of segment 1\n\tlocal d2 = p4 - p3 -- Direction of segment 2\n\tlocal r  = p1 - p3\n\tlocal a  = d1:dot(d1)\n\tlocal e  = d2:dot(d2)\n\tlocal f  = d2:dot(r)\n\n\t-- Check if both segments degenerate into points\n\tif a <= DBL_EPSILON and e <= DBL_EPSILON then\n\t\ts  = 0\n\t\tt  = 0\n\t\tc1 = p1\n\t\tc2 = p3\n\t\treturn (c1 - c2):dot(c1 - c2), s, t, c1, c2\n\tend\n\n\t-- Check if segment 1 degenerates into a point\n\tif a <= DBL_EPSILON then\n\t\ts = 0\n\t\tt = utils.clamp(f / e, 0, 1)\n\telse\n\t\tlocal c = d1:dot(r)\n\n\t\t-- Check is segment 2 degenerates into a point\n\t\tif e <= DBL_EPSILON then\n\t\t\tt = 0\n\t\t\ts = utils.clamp(-c / a, 0, 1)\n\t\telse\n\t\t\tlocal b     = d1:dot(d2)\n\t\t\tlocal denom = a * e - b * b\n\n\t\t\tif abs(denom) > 0 then\n\t\t\t\ts = utils.clamp((b * f - c * e) / denom, 0, 1)\n\t\t\telse\n\t\t\t\ts = 0\n\t\t\tend\n\n\t\t\tt = (b * s + f) / e\n\n\t\t\tif t < 0 then\n\t\t\t\tt = 0\n\t\t\t\ts = utils.clamp(-c / a, 0, 1)\n\t\t\telseif t > 1 then\n\t\t\t\tt = 1\n\t\t\t\ts = utils.clamp((b - c) / a, 0, 1)\n\t\t\tend\n\t\tend\n\tend\n\n\tc1 = p1 + d1 * s\n\tc2 = p3 + d2 * t\n\n\treturn (c1 - c2):dot(c1 - c2), c1, c2, s, t\nend\n\nreturn intersect\n"
  },
  {
    "path": "modules/mat4.lua",
    "content": "--- double 4x4, 1-based, column major matrices\n-- @module mat4\nlocal modules   = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal constants = require(modules .. \"constants\")\nlocal vec2      = require(modules .. \"vec2\")\nlocal vec3      = require(modules .. \"vec3\")\nlocal quat      = require(modules .. \"quat\")\nlocal utils     = require(modules .. \"utils\")\nlocal precond   = require(modules .. \"_private_precond\")\nlocal private   = require(modules .. \"_private_utils\")\nlocal sqrt      = math.sqrt\nlocal cos       = math.cos\nlocal sin       = math.sin\nlocal tan       = math.tan\nlocal rad       = math.rad\nlocal mat4      = {}\nlocal mat4_mt   = {}\n\n-- Private constructor.\nlocal function new(m)\n\tm = m or {\n\t\t0, 0, 0, 0,\n\t\t0, 0, 0, 0,\n\t\t0, 0, 0, 0,\n\t\t0, 0, 0, 0\n\t}\n\tm._m = m\n\treturn setmetatable(m, mat4_mt)\nend\n\n-- Convert matrix into identity\nlocal function identity(m)\n\tm[1],  m[2],  m[3],  m[4]  = 1, 0, 0, 0\n\tm[5],  m[6],  m[7],  m[8]  = 0, 1, 0, 0\n\tm[9],  m[10], m[11], m[12] = 0, 0, 1, 0\n\tm[13], m[14], m[15], m[16] = 0, 0, 0, 1\n\treturn m\nend\n\n-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.\nlocal status, ffi\nif type(jit) == \"table\" and jit.status() then\n\t--  status, ffi = pcall(require, \"ffi\")\n\tif status then\n\t\tffi.cdef \"typedef struct { double _m[16]; } cpml_mat4;\"\n\t\tnew = ffi.typeof(\"cpml_mat4\")\n\tend\nend\n\n-- Statically allocate a temporary variable used in some of our functions.\nlocal tmp = new()\nlocal tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }\nlocal tv4 = { 0, 0, 0, 0 }\n\n--- The public constructor.\n-- @param a Can be of four types: </br>\n-- table Length 16 (4x4 matrix)\n-- table Length 9 (3x3 matrix)\n-- table Length 4 (4 vec4s)\n-- nil\n-- @treturn mat4 out\nfunction mat4.new(a)\n\tlocal out = new()\n\n\t-- 4x4 matrix\n\tif type(a) == \"table\" and #a == 16 then\n\t\tfor i = 1, 16 do\n\t\t\tout[i] = tonumber(a[i])\n\t\tend\n\n\t-- 3x3 matrix\n\telseif type(a) == \"table\" and #a == 9 then\n\t\tout[1], out[2],  out[3]  = a[1], a[2], a[3]\n\t\tout[5], out[6],  out[7]  = a[4], a[5], a[6]\n\t\tout[9], out[10], out[11] = a[7], a[8], a[9]\n\t\tout[16] = 1\n\n\t-- 4 vec4s\n\telseif type(a) == \"table\" and type(a[1]) == \"table\" then\n\t\tlocal idx = 1\n\t\tfor i = 1, 4 do\n\t\t\tfor j = 1, 4 do\n\t\t\t\tout[idx] = a[i][j]\n\t\t\t\tidx = idx + 1\n\t\t\tend\n\t\tend\n\n\t-- nil\n\telse\n\t\tout[1]  = 1\n\t\tout[6]  = 1\n\t\tout[11] = 1\n\t\tout[16] = 1\n\tend\n\n\treturn out\nend\n\n--- Create an identity matrix.\n-- @tparam mat4 a Matrix to overwrite\n-- @treturn mat4 out\nfunction mat4.identity(a)\n\treturn identity(a or new())\nend\n\n--- Create a matrix from an angle/axis pair.\n-- @tparam number angle Angle of rotation\n-- @tparam vec3 axis Axis of rotation\n-- @treturn mat4 out\nfunction mat4.from_angle_axis(angle, axis)\n\tlocal l = axis:len()\n\tif l == 0 then\n\t\treturn new()\n\tend\n\n\tlocal x, y, z = axis.x / l, axis.y / l, axis.z / l\n\tlocal c = cos(angle)\n\tlocal s = sin(angle)\n\n\treturn new {\n\t\tx*x*(1-c)+c,   y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,\n\t\tx*y*(1-c)-z*s, y*y*(1-c)+c,   y*z*(1-c)+x*s, 0,\n\t\tx*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c,   0,\n\t\t0, 0, 0, 1\n\t}\nend\n\n--- Create a matrix from a quaternion.\n-- @tparam quat q Rotation quaternion\n-- @treturn mat4 out\nfunction mat4.from_quaternion(q)\n\treturn mat4.from_angle_axis(q:to_angle_axis())\nend\n\n--- Create a matrix from a direction/up pair.\n-- @tparam vec3 direction Vector direction\n-- @tparam vec3 up Up direction\n-- @treturn mat4 out\nfunction mat4.from_direction(direction, up)\n\tlocal forward = vec3.normalize(direction)\n\tlocal side = vec3.cross(forward, up):normalize()\n\tlocal new_up = vec3.cross(side, forward):normalize()\n\n\tlocal out = new()\n\tout[1]    = side.x\n\tout[5]    = side.y\n\tout[9]    = side.z\n\tout[2]    = new_up.x\n\tout[6]    = new_up.y\n\tout[10]   = new_up.z\n\tout[3]    = forward.x\n\tout[7]    = forward.y\n\tout[11]   = forward.z\n\tout[16]   = 1\n\n\treturn out\nend\n\n--- Create a matrix from a transform.\n-- @tparam vec3 trans Translation vector\n-- @tparam quat rot Rotation quaternion\n-- @tparam vec3 scale Scale vector\n-- @treturn mat4 out\nfunction mat4.from_transform(trans, rot, scale)\n\tlocal rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w\n\n\tlocal sm = new {\n\t\tscale.x, 0,       0,       0,\n\t\t0,       scale.y, 0,       0,\n\t\t0,       0,       scale.z, 0,\n\t\t0,       0,       0,       1,\n\t}\n\n\tlocal rm = new {\n\t\t1-2*(ry*ry+rz*rz), 2*(rx*ry-rz*rw), 2*(rx*rz+ry*rw), 0,\n\t\t2*(rx*ry+rz*rw), 1-2*(rx*rx+rz*rz), 2*(ry*rz-rx*rw), 0,\n\t\t2*(rx*rz-ry*rw), 2*(ry*rz+rx*rw), 1-2*(rx*rx+ry*ry), 0,\n\t\t0, 0, 0, 1\n\t}\n\n\tlocal rsm = rm * sm\n\n\trsm[13] = trans.x\n\trsm[14] = trans.y\n\trsm[15] = trans.z\n\n\treturn rsm\nend\n\n--- Create matrix from orthogonal.\n-- @tparam number left\n-- @tparam number right\n-- @tparam number top\n-- @tparam number bottom\n-- @tparam number near\n-- @tparam number far\n-- @treturn mat4 out\nfunction mat4.from_ortho(left, right, top, bottom, near, far)\n\tlocal out = new()\n\tout[1]    =  2 / (right - left)\n\tout[6]    =  2 / (top - bottom)\n\tout[11]   = -2 / (far - near)\n\tout[13]   = -((right + left) / (right - left))\n\tout[14]   = -((top + bottom) / (top - bottom))\n\tout[15]   = -((far + near) / (far - near))\n\tout[16]   =  1\n\n\treturn out\nend\n\n--- Create matrix from perspective.\n-- @tparam number fovy Field of view\n-- @tparam number aspect Aspect ratio\n-- @tparam number near Near plane\n-- @tparam number far Far plane\n-- @treturn mat4 out\nfunction mat4.from_perspective(fovy, aspect, near, far)\n\tassert(aspect ~= 0)\n\tassert(near   ~= far)\n\n\tlocal t   = tan(rad(fovy) / 2)\n\tlocal out = new()\n\tout[1]    =  1 / (t * aspect)\n\tout[6]    =  1 / t\n\tout[11]   = -(far + near) / (far - near)\n\tout[12]   = -1\n\tout[15]   = -(2 * far * near) / (far - near)\n\tout[16]   =  0\n\n\treturn out\nend\n\n-- Adapted from the Oculus SDK.\n--- Create matrix from HMD perspective.\n-- @tparam number tanHalfFov Tangent of half of the field of view\n-- @tparam number zNear Near plane\n-- @tparam number zFar Far plane\n-- @tparam boolean flipZ Z axis is flipped or not\n-- @tparam boolean farAtInfinity Far plane is infinite or not\n-- @treturn mat4 out\nfunction mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)\n\t-- CPML is right-handed and intended for GL, so these don't need to be arguments.\n\tlocal rightHanded = true\n\tlocal isOpenGL    = true\n\n\tlocal function CreateNDCScaleAndOffsetFromFov(tanHalfFov)\n\t\tlocal x_scale  = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)\n\t\tlocal x_offset =     (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5\n\t\tlocal y_scale  = 2 / (tanHalfFov.UpTan   + tanHalfFov.DownTan )\n\t\tlocal y_offset =     (tanHalfFov.UpTan   - tanHalfFov.DownTan ) * y_scale * 0.5\n\n\t\tlocal result = {\n\t\t\tScale  = vec2(x_scale, y_scale),\n\t\t\tOffset = vec2(x_offset, y_offset)\n\t\t}\n\n\t\t-- Hey - why is that Y.Offset negated?\n\t\t-- It's because a projection matrix transforms from world coords with Y=up,\n\t\t-- whereas this is from NDC which is Y=down.\n\t\t return result\n\tend\n\n\tif not flipZ and farAtInfinity then\n\t\tprint(\"Error: Cannot push Far Clip to Infinity when Z-order is not flipped\")\n\t\tfarAtInfinity = false\n\tend\n\n\t -- A projection matrix is very like a scaling from NDC, so we can start with that.\n\tlocal scaleAndOffset  = CreateNDCScaleAndOffsetFromFov(tanHalfFov)\n\tlocal handednessScale = rightHanded and -1.0 or 1.0\n\tlocal projection      = new()\n\n\t-- Produces X result, mapping clip edges to [-w,+w]\n\tprojection[1] = scaleAndOffset.Scale.x\n\tprojection[2] = 0\n\tprojection[3] = handednessScale * scaleAndOffset.Offset.x\n\tprojection[4] = 0\n\n\t-- Produces Y result, mapping clip edges to [-w,+w]\n\t-- Hey - why is that YOffset negated?\n\t-- It's because a projection matrix transforms from world coords with Y=up,\n\t-- whereas this is derived from an NDC scaling, which is Y=down.\n\tprojection[5] = 0\n\tprojection[6] = scaleAndOffset.Scale.y\n\tprojection[7] = handednessScale * -scaleAndOffset.Offset.y\n\tprojection[8] = 0\n\n\t-- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.\n\t-- We'll just use some defaults for now.\n\tprojection[9]  = 0\n\tprojection[10] = 0\n\n\tif farAtInfinity then\n\t\tif isOpenGL then\n\t\t\t-- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.\n\t\t\tprojection[11] = -handednessScale\n\t\t\tprojection[12] = 2.0 * zNear\n\t\telse\n\t\t\tprojection[11] = 0\n\t\t\tprojection[12] = zNear\n\t\tend\n\telse\n\t\tif isOpenGL then\n\t\t\t-- Clip range is [-w,+w], so 0 is at the middle of the range.\n\t\t\tprojection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)\n\t\t\tprojection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)\n\t\telse\n\t\t\t-- Clip range is [0,+w], so 0 is at the start of the range.\n\t\t\tprojection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)\n\t\t\tprojection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)\n\t\tend\n\tend\n\n\t-- Produces W result (= Z in)\n\tprojection[13] = 0\n\tprojection[14] = 0\n\tprojection[15] = handednessScale\n\tprojection[16] = 0\n\n\treturn projection:transpose(projection)\nend\n\n--- Clone a matrix.\n-- @tparam mat4 a Matrix to clone\n-- @treturn mat4 out\nfunction mat4.clone(a)\n\treturn new(a)\nend\n\nfunction mul_internal(out, a, b)\n\ttm4[1]  = b[1]  * a[1] + b[2]  * a[5] + b[3]  * a[9]  + b[4]  * a[13]\n\ttm4[2]  = b[1]  * a[2] + b[2]  * a[6] + b[3]  * a[10] + b[4]  * a[14]\n\ttm4[3]  = b[1]  * a[3] + b[2]  * a[7] + b[3]  * a[11] + b[4]  * a[15]\n\ttm4[4]  = b[1]  * a[4] + b[2]  * a[8] + b[3]  * a[12] + b[4]  * a[16]\n\ttm4[5]  = b[5]  * a[1] + b[6]  * a[5] + b[7]  * a[9]  + b[8]  * a[13]\n\ttm4[6]  = b[5]  * a[2] + b[6]  * a[6] + b[7]  * a[10] + b[8]  * a[14]\n\ttm4[7]  = b[5]  * a[3] + b[6]  * a[7] + b[7]  * a[11] + b[8]  * a[15]\n\ttm4[8]  = b[5]  * a[4] + b[6]  * a[8] + b[7]  * a[12] + b[8]  * a[16]\n\ttm4[9]  = b[9]  * a[1] + b[10] * a[5] + b[11] * a[9]  + b[12] * a[13]\n\ttm4[10] = b[9]  * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]\n\ttm4[11] = b[9]  * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]\n\ttm4[12] = b[9]  * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]\n\ttm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9]  + b[16] * a[13]\n\ttm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]\n\ttm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]\n\ttm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]\n\n\tfor i = 1, 16 do\n\t\tout[i] = tm4[i]\n\tend\nend\n\n--- Multiply N matrices.\n-- @tparam mat4 out Matrix to store the result\n-- @tparam mat4 or {mat4, ...} left hand operand(s)\n-- @tparam mat4 right hand operand if a is not table\n-- @treturn mat4 out multiplied matrix result\nfunction mat4.mul(out, a, b)\n\tif mat4.is_mat4(a) then\n\t\tmul_internal(out, a, b)\n\t\treturn out\n\tend\n\tif #a == 0 then\n\t\tidentity(out)\n\telseif #a == 1 then\n\t\t-- only one matrix, just copy\n\t\tfor i = 1, 16 do\n\t\t\tout[i] = a[1][i]\n\t\tend\n\telse\n\t\tlocal ma = a[1]\n\t\tlocal mb = a[2]\n\t\tfor i = 2, #a do\n\t\t\tmul_internal(out, ma, mb)\n\t\t\tma = out\n\t\tend\n\tend\n\treturn out\nend\n\n--- Multiply a matrix and a vec3, with perspective division.\n-- This function uses an implicit 1 for the fourth component.\n-- @tparam vec3 out vec3 to store the result\n-- @tparam mat4 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 out\nfunction mat4.mul_vec3_perspective(out, a, b)\n\tlocal v4x = b.x * a[1] + b.y * a[5] + b.z * a[9]  + a[13]\n\tlocal v4y = b.x * a[2] + b.y * a[6] + b.z * a[10] + a[14]\n\tlocal v4z = b.x * a[3] + b.y * a[7] + b.z * a[11] + a[15]\n\tlocal v4w = b.x * a[4] + b.y * a[8] + b.z * a[12] + a[16]\n\tlocal inv_w = 0\n\tif v4w ~= 0 then\n\t\tinv_w = utils.sign(v4w) / v4w\n\tend\n\tout.x = v4x * inv_w\n\tout.y = v4y * inv_w\n\tout.z = v4z * inv_w\n\treturn out\nend\n\n--- Multiply a matrix and a vec4.\n-- @tparam table out table to store the result\n-- @tparam mat4 a Left hand operand\n-- @tparam table b Right hand operand\n-- @treturn vec4 out\nfunction mat4.mul_vec4(out, a, b)\n\ttv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9]  + b[4] * a[13]\n\ttv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]\n\ttv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]\n\ttv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]\n\n\tfor i = 1, 4 do\n\t\tout[i] = tv4[i]\n\tend\n\n\treturn out\nend\n\n--- Invert a matrix.\n-- @tparam mat4 out Matrix to store the result\n-- @tparam mat4 a Matrix to invert\n-- @treturn mat4 out\nfunction mat4.invert(out, a)\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\ttm4[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]\n\n\tlocal det = a[1] * tm4[1] + a[2] * tm4[5] + a[3] * tm4[9] + a[4] * tm4[13]\n\n\tif det == 0 then return a end\n\n\tdet = 1 / det\n\n\tfor i = 1, 16 do\n\t\tout[i] = tm4[i] * det\n\tend\n\n\treturn out\nend\n\n--- Scale a matrix.\n-- @tparam mat4 out Matrix to store the result\n-- @tparam mat4 a Matrix to scale\n-- @tparam vec3 s Scalar\n-- @treturn mat4 out\nfunction mat4.scale(out, a, s)\n\tidentity(tmp)\n\ttmp[1]  = s.x\n\ttmp[6]  = s.y\n\ttmp[11] = s.z\n\n\treturn out:mul(tmp, a)\nend\n\n--- Rotate a matrix.\n-- @tparam mat4 out Matrix to store the result\n-- @tparam mat4 a Matrix to rotate\n-- @tparam number angle Angle to rotate by (in radians)\n-- @tparam vec3 axis Axis to rotate on\n-- @treturn mat4 out\nfunction mat4.rotate(out, a, angle, axis)\n\tif type(angle) == \"table\" or type(angle) == \"cdata\" then\n\t\tangle, axis = angle:to_angle_axis()\n\tend\n\n\tlocal l = axis:len()\n\n\tif l == 0 then\n\t\treturn a\n\tend\n\n\tlocal x, y, z = axis.x / l, axis.y / l, axis.z / l\n\tlocal c = cos(angle)\n\tlocal s = sin(angle)\n\n\tidentity(tmp)\n\ttmp[1]  = x * x * (1 - c) + c\n\ttmp[2]  = y * x * (1 - c) + z * s\n\ttmp[3]  = x * z * (1 - c) - y * s\n\ttmp[5]  = x * y * (1 - c) - z * s\n\ttmp[6]  = y * y * (1 - c) + c\n\ttmp[7]  = y * z * (1 - c) + x * s\n\ttmp[9]  = x * z * (1 - c) + y * s\n\ttmp[10] = y * z * (1 - c) - x * s\n\ttmp[11] = z * z * (1 - c) + c\n\n\treturn out:mul(tmp, a)\nend\n\n--- Translate a matrix.\n-- @tparam mat4 out Matrix to store the result\n-- @tparam mat4 a Matrix to translate\n-- @tparam vec3 t Translation vector\n-- @treturn mat4 out\nfunction mat4.translate(out, a, t)\n\tidentity(tmp)\n\ttmp[13] = t.x\n\ttmp[14] = t.y\n\ttmp[15] = t.z\n\n\treturn out:mul(tmp, a)\nend\n\n--- Shear a matrix.\n-- @tparam mat4 out Matrix to store the result\n-- @tparam mat4 a Matrix to translate\n-- @tparam number yx\n-- @tparam number zx\n-- @tparam number xy\n-- @tparam number zy\n-- @tparam number xz\n-- @tparam number yz\n-- @treturn mat4 out\nfunction mat4.shear(out, a, yx, zx, xy, zy, xz, yz)\n\tidentity(tmp)\n\ttmp[2]  = yx or 0\n\ttmp[3]  = zx or 0\n\ttmp[5]  = xy or 0\n\ttmp[7]  = zy or 0\n\ttmp[9]  = xz or 0\n\ttmp[10] = yz or 0\n\n\treturn out:mul(tmp, a)\nend\n\n--- Reflect a matrix across a plane.\n-- @tparam mat4 Matrix to store the result\n-- @tparam a Matrix to reflect\n-- @tparam vec3 position A point on the plane\n-- @tparam vec3 normal The (normalized!) normal vector of the plane\nfunction mat4.reflect(out, a, position, normal)\n\tlocal nx, ny, nz = normal:unpack()\n\tlocal d = -position:dot(normal)\n\ttmp[1] = 1 - 2 * nx ^ 2\n\ttmp[2] = 2 * nx * ny\n\ttmp[3] = -2 * nx * nz\n\ttmp[4] = 0\n\ttmp[5] = -2 * nx * ny\n\ttmp[6] = 1 - 2 * ny ^ 2\n\ttmp[7] = -2 * ny * nz\n\ttmp[8] = 0\n\ttmp[9] = -2 * nx * nz\n\ttmp[10] = -2 * ny * nz\n\ttmp[11] = 1 - 2 * nz ^ 2\n\ttmp[12] = 0\n\ttmp[13] = -2 * nx * d\n\ttmp[14] = -2 * ny * d\n\ttmp[15] = -2 * nz * d\n\ttmp[16] = 1\n\n\treturn out:mul(tmp, a)\nend\n\n--- Transform matrix to look at a point.\n-- @tparam mat4 out Matrix to store result\n-- @tparam vec3 eye Location of viewer's view plane\n-- @tparam vec3 center Location of object to view\n-- @tparam vec3 up Up direction\n-- @treturn mat4 out\nfunction mat4.look_at(out, eye, look_at, up)\n\tlocal z_axis = (eye - look_at):normalize()\n\tlocal x_axis = up:cross(z_axis):normalize()\n\tlocal y_axis = z_axis:cross(x_axis)\n\tout[1] = x_axis.x\n\tout[2] = y_axis.x\n\tout[3] = z_axis.x\n\tout[4] = 0\n\tout[5] = x_axis.y\n\tout[6] = y_axis.y\n\tout[7] = z_axis.y\n\tout[8] = 0\n\tout[9] = x_axis.z\n\tout[10] = y_axis.z\n\tout[11] = z_axis.z\n\tout[12] = 0\n\tout[13] = -out[  1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z\n\tout[14] = -out[  2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z\n\tout[15] = -out[  3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z\n\tout[16] = -out[  4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1\n\treturn out\nend\n\n--- Transform matrix to target a point.\n-- @tparam mat4 out Matrix to store result\n-- @tparam vec3 eye Location of viewer's view plane\n-- @tparam vec3 center Location of object to view\n-- @tparam vec3 up Up direction\n-- @treturn mat4 out\nfunction mat4.target(out, from, to, up)\n\tlocal z_axis = (from - to):normalize()\n\tlocal x_axis = up:cross(z_axis):normalize()\n\tlocal y_axis = z_axis:cross(x_axis)\n\tout[1] = x_axis.x\n\tout[2] = x_axis.y\n\tout[3] = x_axis.z\n\tout[4] = 0\n\tout[5] = y_axis.x\n\tout[6] = y_axis.y\n\tout[7] = y_axis.z\n\tout[8] = 0\n\tout[9] = z_axis.x\n\tout[10] = z_axis.y\n\tout[11] = z_axis.z\n\tout[12] = 0\n\tout[13] = from.x\n\tout[14] = from.y\n\tout[15] = from.z\n\tout[16] = 1\n\treturn out\nend\n\n--- Transpose a matrix.\n-- @tparam mat4 out Matrix to store the result\n-- @tparam mat4 a Matrix to transpose\n-- @treturn mat4 out\nfunction mat4.transpose(out, a)\n\ttm4[1]  = a[1]\n\ttm4[2]  = a[5]\n\ttm4[3]  = a[9]\n\ttm4[4]  = a[13]\n\ttm4[5]  = a[2]\n\ttm4[6]  = a[6]\n\ttm4[7]  = a[10]\n\ttm4[8]  = a[14]\n\ttm4[9]  = a[3]\n\ttm4[10] = a[7]\n\ttm4[11] = a[11]\n\ttm4[12] = a[15]\n\ttm4[13] = a[4]\n\ttm4[14] = a[8]\n\ttm4[15] = a[12]\n\ttm4[16] = a[16]\n\n\tfor i = 1, 16 do\n\t\tout[i] = tm4[i]\n\tend\n\n\treturn out\nend\n\n--- Project a point into screen space\n-- @tparam vec3 obj Object position in world space\n-- @tparam mat4 mvp Projection matrix\n-- @tparam table viewport XYWH of viewport\n-- @treturn vec3 win\nfunction mat4.project(obj, mvp, viewport)\n\tlocal point = mat4.mul_vec3_perspective(vec3(), mvp, obj)\n\tpoint.x = point.x * 0.5 + 0.5\n\tpoint.y = point.y * 0.5 + 0.5\n\tpoint.z = point.z * 0.5 + 0.5\n\tpoint.x = point.x * viewport[3] + viewport[1]\n\tpoint.y = point.y * viewport[4] + viewport[2]\n\treturn point\nend\n\n--- Unproject a point from screen space to world space.\n-- @tparam vec3 win Object position in screen space\n-- @tparam mat4 mvp Projection matrix\n-- @tparam table viewport XYWH of viewport\n-- @treturn vec3 obj\nfunction mat4.unproject(win, mvp, viewport)\n\tlocal point = vec3.clone(win)\n\n\t-- 0..n -> 0..1\n\tpoint.x = (point.x - viewport[1]) / viewport[3]\n\tpoint.y = (point.y - viewport[2]) / viewport[4]\n\n\t-- 0..1 -> -1..1\n\tpoint.x = point.x * 2 - 1\n\tpoint.y = point.y * 2 - 1\n\tpoint.z = point.z * 2 - 1\n\n\treturn mat4.mul_vec3_perspective(point, tmp:invert(mvp), point)\nend\n\n--- Return a boolean showing if a table is or is not a mat4.\n-- @tparam mat4 a Matrix to be tested\n-- @treturn boolean is_mat4\nfunction mat4.is_mat4(a)\n\tif type(a) == \"cdata\" then\n\t\treturn ffi.istype(\"cpml_mat4\", a)\n\tend\n\n\tif type(a) ~= \"table\" then\n\t\treturn false\n\tend\n\n\tfor i = 1, 16 do\n\t\tif type(a[i]) ~= \"number\" then\n\t\t\treturn false\n\t\tend\n\tend\n\n\treturn true\nend\n\n--- Return whether any component is NaN\n-- @tparam mat4 a Matrix to be tested\n-- @treturn boolean if any component is NaN\nfunction vec2.has_nan(a)\n\tfor i = 1, 16 do\n\t\tif private.is_nan(a[i]) then\n\t\t\treturn true\n\t\tend\n\tend\n\treturn false\nend\n\n--- Return a formatted string.\n-- @tparam mat4 a Matrix to be turned into a string\n-- @treturn string formatted\nfunction mat4.to_string(a)\n\tlocal str = \"[ \"\n\tfor i = 1, 16 do\n\t\tstr = str .. string.format(\"%+0.3f\", a[i])\n\t\tif i < 16 then\n\t\t\tstr = str .. \", \"\n\t\tend\n\tend\n\tstr = str .. \" ]\"\n\treturn str\nend\n\n--- Convert a matrix to row vec4s.\n-- @tparam mat4 a Matrix to be converted\n-- @treturn table vec4s\nfunction mat4.to_vec4s(a)\n\treturn {\n\t\t{ a[1],  a[2],  a[3],  a[4]  },\n\t\t{ a[5],  a[6],  a[7],  a[8]  },\n\t\t{ a[9],  a[10], a[11], a[12] },\n\t\t{ a[13], a[14], a[15], a[16] }\n\t}\nend\n\n--- Convert a matrix to col vec4s.\n-- @tparam mat4 a Matrix to be converted\n-- @treturn table vec4s\nfunction mat4.to_vec4s_cols(a)\n\treturn {\n\t\t{ a[1], a[5], a[9],  a[13] },\n\t\t{ a[2], a[6], a[10], a[14] },\n\t\t{ a[3], a[7], a[11], a[15] },\n\t\t{ a[4], a[8], a[12], a[16] }\n\t}\nend\n\n-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/\n--- Convert a matrix to a quaternion.\n-- @tparam mat4 a Matrix to be converted\n-- @treturn quat out\nfunction mat4.to_quat(a)\n\tidentity(tmp):transpose(a)\n\n\tlocal w     = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2\n\tlocal scale = w * 4\n\tlocal q     = quat.new(\n\t\ttmp[10] - tmp[7] / scale,\n\t\ttmp[3]  - tmp[9] / scale,\n\t\ttmp[5]  - tmp[2] / scale,\n\t\tw\n\t)\n\n\treturn q:normalize(q)\nend\n\n-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html\n--- Convert a matrix to a frustum.\n-- @tparam mat4 a Matrix to be converted (projection * view)\n-- @tparam boolean infinite Infinite removes the far plane\n-- @treturn frustum out\nfunction mat4.to_frustum(a, infinite)\n\tlocal t\n\tlocal frustum = {}\n\n\t-- Extract the LEFT plane\n\tfrustum.left   = {}\n\tfrustum.left.a = a[4]  + a[1]\n\tfrustum.left.b = a[8]  + a[5]\n\tfrustum.left.c = a[12] + a[9]\n\tfrustum.left.d = a[16] + a[13]\n\n\t-- Normalize the result\n\tt = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)\n\tfrustum.left.a = frustum.left.a / t\n\tfrustum.left.b = frustum.left.b / t\n\tfrustum.left.c = frustum.left.c / t\n\tfrustum.left.d = frustum.left.d / t\n\n\t-- Extract the RIGHT plane\n\tfrustum.right   = {}\n\tfrustum.right.a = a[4]  - a[1]\n\tfrustum.right.b = a[8]  - a[5]\n\tfrustum.right.c = a[12] - a[9]\n\tfrustum.right.d = a[16] - a[13]\n\n\t-- Normalize the result\n\tt = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)\n\tfrustum.right.a = frustum.right.a / t\n\tfrustum.right.b = frustum.right.b / t\n\tfrustum.right.c = frustum.right.c / t\n\tfrustum.right.d = frustum.right.d / t\n\n\t-- Extract the BOTTOM plane\n\tfrustum.bottom   = {}\n\tfrustum.bottom.a = a[4]  + a[2]\n\tfrustum.bottom.b = a[8]  + a[6]\n\tfrustum.bottom.c = a[12] + a[10]\n\tfrustum.bottom.d = a[16] + a[14]\n\n\t-- Normalize the result\n\tt = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)\n\tfrustum.bottom.a = frustum.bottom.a / t\n\tfrustum.bottom.b = frustum.bottom.b / t\n\tfrustum.bottom.c = frustum.bottom.c / t\n\tfrustum.bottom.d = frustum.bottom.d / t\n\n\t-- Extract the TOP plane\n\tfrustum.top   = {}\n\tfrustum.top.a = a[4]  - a[2]\n\tfrustum.top.b = a[8]  - a[6]\n\tfrustum.top.c = a[12] - a[10]\n\tfrustum.top.d = a[16] - a[14]\n\n\t-- Normalize the result\n\tt = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)\n\tfrustum.top.a = frustum.top.a / t\n\tfrustum.top.b = frustum.top.b / t\n\tfrustum.top.c = frustum.top.c / t\n\tfrustum.top.d = frustum.top.d / t\n\n\t-- Extract the NEAR plane\n\tfrustum.near   = {}\n\tfrustum.near.a = a[4]  + a[3]\n\tfrustum.near.b = a[8]  + a[7]\n\tfrustum.near.c = a[12] + a[11]\n\tfrustum.near.d = a[16] + a[15]\n\n\t-- Normalize the result\n\tt = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)\n\tfrustum.near.a = frustum.near.a / t\n\tfrustum.near.b = frustum.near.b / t\n\tfrustum.near.c = frustum.near.c / t\n\tfrustum.near.d = frustum.near.d / t\n\n\tif not infinite then\n\t\t-- Extract the FAR plane\n\t\tfrustum.far   = {}\n\t\tfrustum.far.a = a[4]  - a[3]\n\t\tfrustum.far.b = a[8]  - a[7]\n\t\tfrustum.far.c = a[12] - a[11]\n\t\tfrustum.far.d = a[16] - a[15]\n\n\t\t-- Normalize the result\n\t\tt = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)\n\t\tfrustum.far.a = frustum.far.a / t\n\t\tfrustum.far.b = frustum.far.b / t\n\t\tfrustum.far.c = frustum.far.c / t\n\t\tfrustum.far.d = frustum.far.d / t\n\tend\n\n\treturn frustum\nend\n\nfunction mat4_mt.__index(t, k)\n\tif type(t) == \"cdata\" then\n\t\tif type(k) == \"number\" then\n\t\t\treturn t._m[k-1]\n\t\tend\n\tend\n\n\treturn rawget(mat4, k)\nend\n\nfunction mat4_mt.__newindex(t, k, v)\n\tif type(t) == \"cdata\" then\n\t\tif type(k) == \"number\" then\n\t\t\tt._m[k-1] = v\n\t\tend\n\tend\nend\n\nmat4_mt.__tostring = mat4.to_string\n\nfunction mat4_mt.__call(_, a)\n\treturn mat4.new(a)\nend\n\nfunction mat4_mt.__unm(a)\n\treturn new():invert(a)\nend\n\nfunction mat4_mt.__eq(a, b)\n\tif not mat4.is_mat4(a) or not mat4.is_mat4(b) then\n\t\treturn false\n\tend\n\n\tfor i = 1, 16 do\n\t\tif not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then\n\t\t\treturn false\n\t\tend\n\tend\n\n\treturn true\nend\n\nfunction mat4_mt.__mul(a, b)\n\tprecond.assert(mat4.is_mat4(a), \"__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)\", type(a))\n\n\tif vec3.is_vec3(b) then\n\t\treturn mat4.mul_vec3_perspective(vec3(), a, b)\n\tend\n\n\tassert(mat4.is_mat4(b) or #b == 4, \"__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)\")\n\n\tif mat4.is_mat4(b) then\n\t\treturn new():mul(a, b)\n\tend\n\n\treturn mat4.mul_vec4({}, a, b)\nend\n\nif status then\n\txpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded\n\t\tffi.metatype(new, mat4_mt)\n\tend, function() end)\nend\n\nreturn setmetatable({}, mat4_mt)\n"
  },
  {
    "path": "modules/mesh.lua",
    "content": "--- Mesh utilities\n-- @module mesh\n\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal vec3    = require(modules .. \"vec3\")\nlocal mesh    = {}\n\n-- vertices is an arbitrary list of vec3s\nfunction mesh.average(vertices)\n\tlocal out = vec3()\n\tfor _, v in ipairs(vertices) do\n\t\tout = out + v\n\tend\n\treturn out / #vertices\nend\n\n-- triangle[1] is a vec3\n-- triangle[2] is a vec3\n-- triangle[3] is a vec3\nfunction mesh.normal(triangle)\n\tlocal ba = triangle[2] - triangle[1]\n\tlocal ca = triangle[3] - triangle[1]\n\treturn ba:cross(ca):normalize()\nend\n\n-- triangle[1] is a vec3\n-- triangle[2] is a vec3\n-- triangle[3] is a vec3\nfunction mesh.plane_from_triangle(triangle)\n\treturn {\n\t\torigin = triangle[1],\n\t\tnormal = mesh.normal(triangle)\n\t}\nend\n\n-- plane.origin is a vec3\n-- plane.normal is a vec3\n-- direction    is a vec3\nfunction mesh.is_front_facing(plane, direction)\n\treturn plane.normal:dot(direction) >= 0\nend\n\n-- point        is a vec3\n-- plane.origin is a vec3\n-- plane.normal is a vec3\n-- plane.dot    is a number\nfunction mesh.signed_distance(point, plane)\n\treturn point:dot(plane.normal) - plane.normal:dot(plane.origin)\nend\n\nreturn mesh\n"
  },
  {
    "path": "modules/octree.lua",
    "content": "-- https://github.com/Nition/UnityOctree\n-- https://github.com/Nition/UnityOctree/blob/master/LICENCE\n-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctree.cs\n-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctreeNode.cs\n\n--- Octree\n-- @module octree\n\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal intersect  = require(modules .. \"intersect\")\nlocal mat4       = require(modules .. \"mat4\")\nlocal utils      = require(modules .. \"utils\")\nlocal vec3       = require(modules .. \"vec3\")\nlocal Octree     = {}\nlocal OctreeNode = {}\nlocal Node\n\nOctree.__index     = Octree\nOctreeNode.__index = OctreeNode\n\n--== Octree ==--\n\n--- Constructor for the bounds octree.\n-- @param initialWorldSize Size of the sides of the initial node, in metres. The octree will never shrink smaller than this\n-- @param initialWorldPos Position of the centre of the initial node\n-- @param minNodeSize Nodes will stop splitting if the new nodes would be smaller than this (metres)\n-- @param looseness Clamped between 1 and 2. Values > 1 let nodes overlap\nlocal function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)\n\tlocal tree = setmetatable({}, Octree)\n\n\tif minNodeSize > initialWorldSize then\n\t\tprint(\"Minimum node size must be at least as big as the initial world size. Was: \" .. minNodeSize .. \" Adjusted to: \" .. initialWorldSize)\n\t\tminNodeSize = initialWorldSize\n\tend\n\n\t-- The total amount of objects currently in the tree\n\ttree.count = 0\n\n\t-- Size that the octree was on creation\n\ttree.initialSize = initialWorldSize\n\n\t-- Minimum side length that a node can be - essentially an alternative to having a max depth\n\ttree.minSize = minNodeSize\n\n\t-- Should be a value between 1 and 2. A multiplier for the base size of a node.\n\t-- 1.0 is a \"normal\" octree, while values > 1 have overlap\n\ttree.looseness = utils.clamp(looseness, 1, 2)\n\n\t-- Root node of the octree\n\ttree.rootNode = Node(tree.initialSize, tree.minSize, tree.looseness, initialWorldPos)\n\n\treturn tree\nend\n\n--- Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.\n-- @param xDir X direction of growth. 1 or -1\n-- @param yDir Y direction of growth. 1 or -1\n-- @param zDir Z direction of growth. 1 or -1\n-- @return Octant where the root node should be\nlocal function get_root_pos_index(xDir, yDir, zDir)\n\tlocal result = xDir > 0 and 1 or 0\n\tif yDir < 0 then return result + 4 end\n\tif zDir > 0 then return result + 2 end\nend\n\n--- Add an object.\n-- @param obj Object to add\n-- @param objBounds 3D bounding box around the object\nfunction Octree:add(obj, objBounds)\n\t-- Add object or expand the octree until it can be added\n\tlocal count = 0 -- Safety check against infinite/excessive growth\n\n\twhile not self.rootNode:add(obj, objBounds) do\n\t\tcount = count + 1\n\t\tself:grow(objBounds.center - self.rootNode.center)\n\n\t\tif count > 20 then\n\t\t\tprint(\"Aborted Add operation as it seemed to be going on forever (\" .. count - 1 .. \") attempts at growing the octree.\")\n\t\t\treturn\n\t\tend\n\n\t\tself.count = self.count + 1\n\tend\nend\n\n--- Remove an object. Makes the assumption that the object only exists once in the tree.\n-- @param obj Object to remove\n-- @return bool True if the object was removed successfully\nfunction Octree:remove(obj)\n\tlocal removed = self.rootNode:remove(obj)\n\n\t-- See if we can shrink the octree down now that we've removed the item\n\tif removed then\n\t\tself.count = self.count - 1\n\t\tself:shrink()\n\tend\n\n\treturn removed\nend\n\n--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.\n-- @param checkBounds bounds to check\n-- @return bool True if there was a collision\nfunction Octree:is_colliding(checkBounds)\n\treturn self.rootNode:is_colliding(checkBounds)\nend\n\n--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.\n-- @param checkBounds bounds to check\n-- @return table Objects that intersect with the specified bounds\nfunction Octree:get_colliding(checkBounds)\n\treturn self.rootNode:get_colliding(checkBounds)\nend\n\n--- Cast a ray through the node and its children\n-- @param ray Ray with a position and a direction\n-- @param func Function to execute on any objects within child nodes\n-- @param out Table to store results of func in\n-- @return boolean True if an intersect detected\nfunction Octree:cast_ray(ray, func, out)\n\tassert(func)\n\treturn self.rootNode:cast_ray(ray, func, out)\nend\n\n--- Draws node boundaries visually for debugging.\nfunction Octree:draw_bounds(cube)\n\tself.rootNode:draw_bounds(cube)\nend\n\n--- Draws the bounds of all objects in the tree visually for debugging.\nfunction Octree:draw_objects(cube, filter)\n\tself.rootNode:draw_objects(cube, filter)\nend\n\n--- Grow the octree to fit in all objects.\n-- @param direction Direction to grow\nfunction Octree:grow(direction)\n\tlocal xDirection = direction.x >= 0 and 1 or -1\n\tlocal yDirection = direction.y >= 0 and 1 or -1\n\tlocal zDirection = direction.z >= 0 and 1 or -1\n\n\tlocal oldRoot   = self.rootNode\n\tlocal half      = self.rootNode.baseLength / 2\n\tlocal newLength = self.rootNode.baseLength * 2\n\tlocal newCenter = self.rootNode.center + vec3(xDirection * half, yDirection * half, zDirection * half)\n\n\t-- Create a new, bigger octree root node\n\tself.rootNode = Node(newLength, self.minSize, self.looseness, newCenter)\n\n\t-- Create 7 new octree children to go with the old root as children of the new root\n\tlocal rootPos  = get_root_pos_index(xDirection, yDirection, zDirection)\n\tlocal children = {}\n\n\tfor i = 0, 7 do\n\t\tif i == rootPos then\n\t\t\tchildren[i+1] = oldRoot\n\t\telse\n\t\t\txDirection  = i % 2 == 0 and -1 or 1\n\t\t\tyDirection  = i > 3 and -1 or 1\n\t\t\tzDirection  = (i < 2 or (i > 3 and i < 6)) and -1 or 1\n\t\t\tchildren[i+1] = Node(self.rootNode.baseLength, self.minSize, self.looseness, newCenter + vec3(xDirection * half, yDirection * half, zDirection * half))\n\t\tend\n\tend\n\n\t-- Attach the new children to the new root node\n\tself.rootNode:set_children(children)\nend\n\n--- Shrink the octree if possible, else leave it the same.\nfunction Octree:shrink()\n\tself.rootNode = self.rootNode:shrink_if_possible(self.initialSize)\nend\n\n--== Octree Node ==--\n\n--- Constructor.\n-- @param baseLength Length of this node, not taking looseness into account\n-- @param minSize Minimum size of nodes in this octree\n-- @param looseness Multiplier for baseLengthVal to get the actual size\n-- @param center Centre position of this node\nlocal function new_node(baseLength, minSize, looseness, center)\n\tlocal node = setmetatable({}, OctreeNode)\n\n\t-- Objects in this node\n\tnode.objects = {}\n\n\t-- Child nodes\n\tnode.children = {}\n\n\t-- If there are already numObjectsAllowed in a node, we split it into children\n\t-- A generally good number seems to be something around 8-15\n\tnode.numObjectsAllowed = 8\n\n\tnode:set_values(baseLength, minSize, looseness, center)\n\n\treturn node\nend\n\nlocal function new_bound(center, size)\n\treturn {\n\t\tcenter = center,\n\t\tsize   = size,\n\t\tmin    = center - (size / 2),\n\t\tmax    = center + (size / 2)\n\t}\nend\n\n--- Add an object.\n-- @param obj Object to add\n-- @param objBounds 3D bounding box around the object\n-- @return boolean True if the object fits entirely within this node\nfunction OctreeNode:add(obj, objBounds)\n\tif not intersect.encapsulate_aabb(self.bounds, objBounds) then\n\t\treturn false\n\tend\n\n\t-- We know it fits at this level if we've got this far\n\t-- Just add if few objects are here, or children would be below min size\n\tif #self.objects < self.numObjectsAllowed\n\tor self.baseLength / 2 < self.minSize then\n\t\ttable.insert(self.objects, {\n\t\t\tdata   = obj,\n\t\t\tbounds = objBounds\n\t\t})\n\telse\n\t\t-- Fits at this level, but we can go deeper. Would it fit there?\n\n\t\tlocal best_fit_child\n\n\t\t-- Create the 8 children\n\t\tif #self.children == 0 then\n\t\t\tself:split()\n\n\t\t\tif #self.children == 0 then\n\t\t\t\tprint(\"Child creation failed for an unknown reason. Early exit.\")\n\t\t\t\treturn false\n\t\t\tend\n\n\t\t\t-- Now that we have the new children, see if this node's existing objects would fit there\n\t\t\tfor i = #self.objects, 1, -1 do\n\t\t\t\tlocal object = self.objects[i]\n\t\t\t\t-- Find which child the object is closest to based on where the\n\t\t\t\t-- object's center is located in relation to the octree's center.\n\t\t\t\tbest_fit_child = self:best_fit_child(object.bounds)\n\n\t\t\t\t-- Does it fit?\n\t\t\t\tif intersect.encapsulate_aabb(self.children[best_fit_child].bounds, object.bounds) then\n\t\t\t\t\tself.children[best_fit_child]:add(object.data, object.bounds) -- Go a level deeper\n\t\t\t\t\ttable.remove(self.objects, i) -- Remove from here\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\n\t\t-- Now handle the new object we're adding now\n\t\tbest_fit_child = self:best_fit_child(objBounds)\n\n\t\tif intersect.encapsulate_aabb(self.children[best_fit_child].bounds, objBounds) then\n\t\t\tself.children[best_fit_child]:add(obj, objBounds)\n\t\telse\n\t\t\ttable.insert(self.objects, {\n\t\t\t\tdata   = obj,\n\t\t\t\tbounds = objBounds\n\t\t\t})\n\t\tend\n\tend\n\n\treturn true\nend\n\n--- Remove an object. Makes the assumption that the object only exists once in the tree.\n-- @param obj Object to remove\n-- @return boolean True if the object was removed successfully\nfunction OctreeNode:remove(obj)\n\tlocal removed = false\n\n\tfor i, object in ipairs(self.objects) do\n\t\tif object == obj then\n\t\t\tremoved = table.remove(self.objects, i) and true or false\n\t\t\tbreak\n\t\tend\n\tend\n\n\tif not removed then\n\t\tfor _, child in ipairs(self.children) do\n\t\t\tremoved = child:remove(obj)\n\t\t\tif removed then break end\n\t\tend\n\tend\n\n\tif removed then\n\t\t-- Check if we should merge nodes now that we've removed an item\n\t\tif self:should_merge() then\n\t\t\tself:merge()\n\t\tend\n\tend\n\n\treturn removed\nend\n\n--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.\n-- @param checkBounds Bounds to check\n-- @return boolean True if there was a collision\nfunction OctreeNode:is_colliding(checkBounds)\n\t-- Are the input bounds at least partially in this node?\n\tif not intersect.aabb_aabb(self.bounds, checkBounds) then\n\t\treturn false\n\tend\n\n\t-- Check against any objects in this node\n\tfor _, object in ipairs(self.objects) do\n\t\tif intersect.aabb_aabb(object.bounds, checkBounds) then\n\t\t\treturn true\n\t\tend\n\tend\n\n\t-- Check children\n\tfor _, child in ipairs(self.children) do\n\t\tif child:is_colliding(checkBounds) then\n\t\t\treturn true\n\t\tend\n\tend\n\n\treturn false\nend\n\n--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.\n-- @param checkBounds Bounds to check. Passing by ref as it improve performance with structs\n-- @param results List results\n-- @return table Objects that intersect with the specified bounds\nfunction OctreeNode:get_colliding(checkBounds, results)\n\tresults = results or {}\n\n\t-- Are the input bounds at least partially in this node?\n\tif not intersect.aabb_aabb(self.bounds, checkBounds) then\n\t\treturn results\n\tend\n\n\t-- Check against any objects in this node\n\tfor _, object in ipairs(self.objects) do\n\t\tif intersect.aabb_aabb(object.bounds, checkBounds) then\n\t\t\ttable.insert(results, object.data)\n\t\tend\n\tend\n\n\t-- Check children\n\tfor _, child in ipairs(self.children) do\n\t\tresults = child:get_colliding(checkBounds, results)\n\tend\n\n\treturn results\nend\n\n--- Cast a ray through the node and its children\n-- @param ray Ray with a position and a direction\n-- @param func Function to execute on any objects within child nodes\n-- @param out Table to store results of func in\n-- @param depth (used internally)\n-- @return boolean True if an intersect is detected\nfunction OctreeNode:cast_ray(ray, func, out, depth)\n\tdepth = depth or 1\n\n\tif intersect.ray_aabb(ray, self.bounds) then\n\t\tif #self.objects > 0 then\n\t\t\tlocal hit = func(ray, self.objects, out)\n\n\t\t\tif hit then\n\t\t\t\treturn hit\n\t\t\tend\n\t\tend\n\n\t\tfor _, child in ipairs(self.children) do\n\t\t\tlocal hit = child:cast_ray(ray, func, out, depth + 1)\n\n\t\t\tif hit then\n\t\t\t\treturn hit\n\t\t\tend\n\t\tend\n\tend\n\n\treturn false\nend\n\n--- Set the 8 children of this octree.\n-- @param childOctrees The 8 new child nodes\nfunction OctreeNode:set_children(childOctrees)\n\tif #childOctrees ~= 8 then\n\t\tprint(\"Child octree array must be length 8. Was length: \" .. #childOctrees)\n\t\treturn\n\tend\n\n\tself.children = childOctrees\nend\n\n--- We can shrink the octree if:\n--- - This node is >= double minLength in length\n--- - All objects in the root node are within one octant\n--- - This node doesn't have children, or does but 7/8 children are empty\n--- We can also shrink it if there are no objects left at all!\n-- @param minLength Minimum dimensions of a node in this octree\n-- @return table The new root, or the existing one if we didn't shrink\nfunction OctreeNode:shrink_if_possible(minLength)\n\tif self.baseLength < 2 * minLength then\n\t\treturn self\n\tend\n\n\tif #self.objects == 0 and #self.children == 0 then\n\t\treturn self\n\tend\n\n\t-- Check objects in root\n\tlocal bestFit = 0\n\n\tfor i, object in ipairs(self.objects) do\n\t\tlocal newBestFit = self:best_fit_child(object.bounds)\n\n\t\tif i == 1 or newBestFit == bestFit then\n\t\t\t-- In same octant as the other(s). Does it fit completely inside that octant?\n\t\t\tif intersect.encapsulate_aabb(self.childBounds[newBestFit], object.bounds) then\n\t\t\t\tif bestFit < 1 then\n\t\t\t\t\tbestFit = newBestFit\n\t\t\t\tend\n\t\t\telse\n\t\t\t\t-- Nope, so we can't reduce. Otherwise we continue\n\t\t\t\treturn self\n\t\t\tend\n\t\telse\n\t\t\treturn self -- Can't reduce - objects fit in different octants\n\t\tend\n\tend\n\n\t-- Check objects in children if there are any\n\tif #self.children > 0 then\n\t\tlocal childHadContent = false\n\n\t\tfor i, child in ipairs(self.children) do\n\t\t\tif child:has_any_objects() then\n\t\t\t\tif childHadContent then\n\t\t\t\t\treturn self -- Can't shrink - another child had content already\n\t\t\t\tend\n\n\t\t\t\tif bestFit > 0 and bestFit ~= i then\n\t\t\t\t\treturn self -- Can't reduce - objects in root are in a different octant to objects in child\n\t\t\t\tend\n\n\t\t\t\tchildHadContent = true\n\t\t\t\tbestFit = i\n\t\t\tend\n\t\tend\n\tend\n\n\t-- Can reduce\n\tif #self.children == 0 then\n\t\t-- We don't have any children, so just shrink this node to the new size\n\t\t-- We already know that everything will still fit in it\n\t\tself:set_values(self.baseLength / 2, self.minSize, self.looseness, self.childBounds[bestFit].center)\n\t\treturn self\n\tend\n\n\t-- We have children. Use the appropriate child as the new root node\n\treturn self.children[bestFit]\nend\n\n--- Set values for this node.\n-- @param baseLength Length of this node, not taking looseness into account\n-- @param minSize Minimum size of nodes in this octree\n-- @param looseness Multiplier for baseLengthVal to get the actual size\n-- @param center Centre position of this node\nfunction OctreeNode:set_values(baseLength, minSize, looseness, center)\n\t-- Length of this node if it has a looseness of 1.0\n\tself.baseLength = baseLength\n\n\t-- Minimum size for a node in this octree\n\tself.minSize = minSize\n\n\t-- Looseness value for this node\n\tself.looseness = looseness\n\n\t-- Centre of this node\n\tself.center = center\n\n\t-- Actual length of sides, taking the looseness value into account\n\tself.adjLength = self.looseness * self.baseLength\n\n\t-- Create the bounding box.\n\tself.size = vec3(self.adjLength, self.adjLength, self.adjLength)\n\n\t-- Bounding box that represents this node\n\tself.bounds = new_bound(self.center, self.size)\n\n\tself.quarter           = self.baseLength / 4\n\tself.childActualLength = (self.baseLength / 2) * self.looseness\n\tself.childActualSize   = vec3(self.childActualLength, self.childActualLength, self.childActualLength)\n\n\t-- Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size\n\tself.childBounds =  {\n\t\tnew_bound(self.center + vec3(-self.quarter,  self.quarter, -self.quarter), self.childActualSize),\n\t\tnew_bound(self.center + vec3( self.quarter,  self.quarter, -self.quarter), self.childActualSize),\n\t\tnew_bound(self.center + vec3(-self.quarter,  self.quarter,  self.quarter), self.childActualSize),\n\t\tnew_bound(self.center + vec3( self.quarter,  self.quarter,  self.quarter), self.childActualSize),\n\t\tnew_bound(self.center + vec3(-self.quarter, -self.quarter, -self.quarter), self.childActualSize),\n\t\tnew_bound(self.center + vec3( self.quarter, -self.quarter, -self.quarter), self.childActualSize),\n\t\tnew_bound(self.center + vec3(-self.quarter, -self.quarter,  self.quarter), self.childActualSize),\n\t\tnew_bound(self.center + vec3( self.quarter, -self.quarter,  self.quarter), self.childActualSize)\n\t}\nend\n\n--- Splits the octree into eight children.\nfunction OctreeNode:split()\n\tif #self.children > 0 then return end\n\n\tlocal quarter   = self.baseLength / 4\n\tlocal newLength = self.baseLength / 2\n\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter,  quarter, -quarter)))\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter,  quarter, -quarter)))\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter,  quarter,  quarter)))\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter,  quarter,  quarter)))\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, -quarter)))\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, -quarter)))\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter,  quarter)))\n\ttable.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter,  quarter)))\nend\n\n--- Merge all children into this node - the opposite of Split.\n--- Note: We only have to check one level down since a merge will never happen if the children already have children,\n--- since THAT won't happen unless there are already too many objects to merge.\nfunction OctreeNode:merge()\n\tfor _, child in ipairs(self.children) do\n\t\tfor _, object in ipairs(child.objects) do\n\t\t\ttable.insert(self.objects, object)\n\t\tend\n\tend\n\n\t-- Remove the child nodes (and the objects in them - they've been added elsewhere now)\n\tself.children = {}\nend\n\n--- Find which child node this object would be most likely to fit in.\n-- @param objBounds The object's bounds\n-- @return number One of the eight child octants\nfunction OctreeNode:best_fit_child(objBounds)\n\treturn (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\nend\n\n--- Checks if there are few enough objects in this node and its children that the children should all be merged into this.\n-- @return boolean True there are less or the same abount of objects in this and its children than numObjectsAllowed\nfunction OctreeNode:should_merge()\n\tlocal totalObjects = #self.objects\n\n\tfor _, child in ipairs(self.children) do\n\t\tif #child.children > 0 then\n\t\t\t-- If any of the *children* have children, there are definitely too many to merge,\n\t\t\t-- or the child would have been merged already\n\t\t\treturn false\n\t\tend\n\n\t\ttotalObjects = totalObjects + #child.objects\n\tend\n\n\treturn totalObjects <= self.numObjectsAllowed\nend\n\n--- Checks if this node or anything below it has something in it.\n-- @return boolean True if this node or any of its children, grandchildren etc have something in the\nfunction OctreeNode:has_any_objects()\n\tif #self.objects > 0 then return true end\n\n\tfor _, child in ipairs(self.children) do\n\t\tif child:has_any_objects() then return true end\n\tend\n\n\treturn false\nend\n\n--- Draws node boundaries visually for debugging.\n-- @param cube Cube model to draw\n-- @param depth Used for recurcive calls to this method\nfunction OctreeNode:draw_bounds(cube, depth)\n\tdepth = depth or 0\n\tlocal tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically\n\n\tlove.graphics.setColor(tint * 255, 0, (1 - tint) * 255)\n\tlocal m = mat4()\n\t\t:translate(self.center)\n\t\t:scale(vec3(self.adjLength, self.adjLength, self.adjLength))\n\n\tlove.graphics.updateMatrix(\"transform\", m)\n\tlove.graphics.setWireframe(true)\n\tlove.graphics.draw(cube)\n\tlove.graphics.setWireframe(false)\n\n\tfor _, child in ipairs(self.children) do\n\t\tchild:draw_bounds(cube, depth + 1)\n\tend\n\n\tlove.graphics.setColor(255, 255, 255)\nend\n\n--- Draws the bounds of all objects in the tree visually for debugging.\n-- @param cube Cube model to draw\n-- @param filter a function returning true or false to determine visibility.\nfunction OctreeNode:draw_objects(cube, filter)\n\tlocal tint = self.baseLength / 20\n\tlove.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)\n\n\tfor _, object in ipairs(self.objects) do\n\t\tif filter and filter(object.data) or not filter then\n\t\t\tlocal m = mat4()\n\t\t\t\t:translate(object.bounds.center)\n\t\t\t\t:scale(object.bounds.size)\n\n\t\t\tlove.graphics.updateMatrix(\"transform\", m)\n\t\t\tlove.graphics.draw(cube)\n\t\tend\n\tend\n\n\tfor _, child in ipairs(self.children) do\n\t\tchild:draw_objects(cube, filter)\n\tend\n\n\tlove.graphics.setColor(255, 255, 255)\nend\n\nNode = setmetatable({\n\tnew = new_node\n}, {\n\t__call = function(_, ...) return new_node(...) end\n})\n\nreturn setmetatable({\n\tnew = new\n}, {\n\t__call = function(_, ...) return new(...) end\n})\n"
  },
  {
    "path": "modules/quat.lua",
    "content": "--- A quaternion and associated utilities.\n-- @module quat\n\nlocal modules       = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal constants     = require(modules .. \"constants\")\nlocal vec3          = require(modules .. \"vec3\")\nlocal precond       = require(modules .. \"_private_precond\")\nlocal private       = require(modules .. \"_private_utils\")\nlocal DOT_THRESHOLD = constants.DOT_THRESHOLD\nlocal DBL_EPSILON   = constants.DBL_EPSILON\nlocal acos          = math.acos\nlocal cos           = math.cos\nlocal sin           = math.sin\nlocal min           = math.min\nlocal max           = math.max\nlocal sqrt          = math.sqrt\nlocal quat          = {}\nlocal quat_mt       = {}\n\n-- Private constructor.\nlocal function new(x, y, z, w)\n\treturn setmetatable({\n\t\tx = x or 0,\n\t\ty = y or 0,\n\t\tz = z or 0,\n\t\tw = w or 1\n\t}, quat_mt)\nend\n\n-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.\nlocal status, ffi\nif type(jit) == \"table\" and jit.status() then\n\tstatus, ffi = pcall(require, \"ffi\")\n\tif status then\n\t\tffi.cdef \"typedef struct { double x, y, z, w;} cpml_quat;\"\n\t\tnew = ffi.typeof(\"cpml_quat\")\n\tend\nend\n\n-- Statically allocate a temporary variable used in some of our functions.\nlocal tmp = new()\nlocal qv, uv, uuv = vec3(), vec3(), vec3()\n\n--- Constants\n-- @table quat\n-- @field unit Unit quaternion\n-- @field zero Empty quaternion\nquat.unit = new(0, 0, 0, 1)\nquat.zero = new(0, 0, 0, 0)\n\n--- The public constructor.\n-- @param x Can be of two types: </br>\n-- number x X component\n-- table {x, y, z, w} or {x=x, y=y, z=z, w=w}\n-- @tparam number y Y component\n-- @tparam number z Z component\n-- @tparam number w W component\n-- @treturn quat out\nfunction quat.new(x, y, z, w)\n\t-- number, number, number, number\n\tif x and y and z and w then\n\t\tprecond.typeof(x, \"number\", \"new: Wrong argument type for x\")\n\t\tprecond.typeof(y, \"number\", \"new: Wrong argument type for y\")\n\t\tprecond.typeof(z, \"number\", \"new: Wrong argument type for z\")\n\t\tprecond.typeof(w, \"number\", \"new: Wrong argument type for w\")\n\n\t\treturn new(x, y, z, w)\n\n\t-- {x, y, z, w} or {x=x, y=y, z=z, w=w}\n\telseif type(x) == \"table\" or type (x) == \"cdata\" then\n\t\tlocal xx, yy, zz, ww = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]\n\t\tprecond.typeof(xx, \"number\", \"new: Wrong argument type for x\")\n\t\tprecond.typeof(yy, \"number\", \"new: Wrong argument type for y\")\n\t\tprecond.typeof(zz, \"number\", \"new: Wrong argument type for z\")\n\t\tprecond.typeof(ww, \"number\", \"new: Wrong argument type for w\")\n\n\t\treturn new(xx, yy, zz, ww)\n\n\telse\n\t\tprecond.assert(x == nil, \"new: Wrong arguments\")\n\t\treturn new(0, 0, 0, 1)\n\tend\nend\n\n--- Create a quaternion from an angle/axis pair.\n-- @tparam number angle Angle (in radians)\n-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis\n-- @param y axis -- y component of axis (optional, only if x component param used)\n-- @param z axis -- z component of axis (optional, only if x component param used)\n-- @treturn quat out\nfunction quat.from_angle_axis(angle, axis, a3, a4)\n\tif axis and a3 and a4 then\n\t\tlocal x, y, z = axis, a3, a4\n\t\tlocal s = sin(angle * 0.5)\n\t\tlocal c = cos(angle * 0.5)\n\t\treturn new(x * s, y * s, z * s, c)\n\telse\n\t\treturn quat.from_angle_axis(angle, axis.x, axis.y, axis.z)\n\tend\nend\n\n--- Create a quaternion from a normal/up vector pair.\n-- @tparam vec3 normal\n-- @tparam vec3 up (optional)\n-- @treturn quat out\nfunction quat.from_direction(normal, up)\n\tlocal u = up or vec3.unit_z\n\tlocal n = normal:normalize()\n\tlocal a = u:cross(n)\n\tlocal d = u:dot(n)\n\treturn new(a.x, a.y, a.z, d + 1)\nend\n\n--- Clone a quaternion.\n-- @tparam quat a Quaternion to clone\n-- @treturn quat out\nfunction quat.clone(a)\n\treturn new(a.x, a.y, a.z, a.w)\nend\n\n--- Add two quaternions.\n-- @tparam quat a Left hand operand\n-- @tparam quat b Right hand operand\n-- @treturn quat out\nfunction quat.add(a, b)\n\treturn new(\n\t\ta.x + b.x,\n\t\ta.y + b.y,\n\t\ta.z + b.z,\n\t\ta.w + b.w\n\t)\nend\n\n--- Subtract a quaternion from another.\n-- @tparam quat a Left hand operand\n-- @tparam quat b Right hand operand\n-- @treturn quat out\nfunction quat.sub(a, b)\n\treturn new(\n\t\ta.x - b.x,\n\t\ta.y - b.y,\n\t\ta.z - b.z,\n\t\ta.w - b.w\n\t)\nend\n\n--- Multiply two quaternions.\n-- @tparam quat a Left hand operand\n-- @tparam quat b Right hand operand\n-- @treturn quat quaternion equivalent to \"apply b, then a\"\nfunction quat.mul(a, b)\n\treturn new(\n\t\ta.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y,\n\t\ta.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z,\n\t\ta.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x,\n\t\ta.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z\n\t)\nend\n\n--- Multiply a quaternion and a vec3.\n-- @tparam quat a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 out\nfunction quat.mul_vec3(a, b)\n\tqv.x = a.x\n\tqv.y = a.y\n\tqv.z = a.z\n\tuv   = qv:cross(b)\n\tuuv  = qv:cross(uv)\n\treturn b + ((uv * a.w) + uuv) * 2\nend\n\n--- Raise a normalized quaternion to a scalar power.\n-- @tparam quat a Left hand operand (should be a unit quaternion)\n-- @tparam number s Right hand operand\n-- @treturn quat out\nfunction quat.pow(a, s)\n\t-- Do it as a slerp between identity and a (code borrowed from slerp)\n\tif a.w < 0 then\n\t\ta   = -a\n\tend\n\tlocal dot = a.w\n\n\tdot = min(max(dot, -1), 1)\n\n\tlocal theta = acos(dot) * s\n\tlocal c = new(a.x, a.y, a.z, 0):normalize() * sin(theta)\n\tc.w = cos(theta)\n\treturn c\nend\n\n--- Normalize a quaternion.\n-- @tparam quat a Quaternion to normalize\n-- @treturn quat out\nfunction quat.normalize(a)\n\tif a:is_zero() then\n\t\treturn new(0, 0, 0, 0)\n\tend\n\treturn a:scale(1 / a:len())\nend\n\n--- Get the dot product of two quaternions.\n-- @tparam quat a Left hand operand\n-- @tparam quat b Right hand operand\n-- @treturn number dot\nfunction quat.dot(a, b)\n\treturn a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w\nend\n\n--- Return the length of a quaternion.\n-- @tparam quat a Quaternion to get length of\n-- @treturn number len\nfunction quat.len(a)\n\treturn sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w)\nend\n\n--- Return the squared length of a quaternion.\n-- @tparam quat a Quaternion to get length of\n-- @treturn number len\nfunction quat.len2(a)\n\treturn a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w\nend\n\n--- Multiply a quaternion by a scalar.\n-- @tparam quat a Left hand operand\n-- @tparam number s Right hand operand\n-- @treturn quat out\nfunction quat.scale(a, s)\n\treturn new(\n\t\ta.x * s,\n\t\ta.y * s,\n\t\ta.z * s,\n\t\ta.w * s\n\t)\nend\n\n--- Alias of from_angle_axis.\n-- @tparam number angle Angle (in radians)\n-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis\n-- @param y axis -- y component of axis (optional, only if x component param used)\n-- @param z axis -- z component of axis (optional, only if x component param used)\n-- @treturn quat out\nfunction quat.rotate(angle, axis, a3, a4)\n\treturn quat.from_angle_axis(angle, axis, a3, a4)\nend\n\n--- Return the conjugate of a quaternion.\n-- @tparam quat a Quaternion to conjugate\n-- @treturn quat out\nfunction quat.conjugate(a)\n\treturn new(-a.x, -a.y, -a.z, a.w)\nend\n\n--- Return the inverse of a quaternion.\n-- @tparam quat a Quaternion to invert\n-- @treturn quat out\nfunction quat.inverse(a)\n\ttmp.x = -a.x\n\ttmp.y = -a.y\n\ttmp.z = -a.z\n\ttmp.w =  a.w\n\treturn tmp:normalize()\nend\n\n--- Return the reciprocal of a quaternion.\n-- @tparam quat a Quaternion to reciprocate\n-- @treturn quat out\nfunction quat.reciprocal(a)\n\tif a:is_zero() then\n\t\terror(\"Cannot reciprocate a zero quaternion\")\n\t\treturn false\n\tend\n\n\ttmp.x = -a.x\n\ttmp.y = -a.y\n\ttmp.z = -a.z\n\ttmp.w =  a.w\n\n\treturn tmp:scale(1 / a:len2())\nend\n\n--- Lerp between two quaternions.\n-- @tparam quat a Left hand operand\n-- @tparam quat b Right hand operand\n-- @tparam number s Step value\n-- @treturn quat out\nfunction quat.lerp(a, b, s)\n\treturn (a + (b - a) * s):normalize()\nend\n\n--- Slerp between two quaternions.\n-- @tparam quat a Left hand operand\n-- @tparam quat b Right hand operand\n-- @tparam number s Step value\n-- @treturn quat out\nfunction quat.slerp(a, b, s)\n\tlocal dot = a:dot(b)\n\n\tif dot < 0 then\n\t\ta   = -a\n\t\tdot = -dot\n\tend\n\n\tif dot > DOT_THRESHOLD then\n\t\treturn a:lerp(b, s)\n\tend\n\n\tdot = min(max(dot, -1), 1)\n\n\tlocal theta = acos(dot) * s\n\tlocal c = (b - a * dot):normalize()\n\treturn a * cos(theta) + c * sin(theta)\nend\n\n--- Unpack a quaternion into individual components.\n-- @tparam quat a Quaternion to unpack\n-- @treturn number x\n-- @treturn number y\n-- @treturn number z\n-- @treturn number w\nfunction quat.unpack(a)\n\treturn a.x, a.y, a.z, a.w\nend\n\n--- Return a boolean showing if a table is or is not a quat.\n-- @tparam quat a Quaternion to be tested\n-- @treturn boolean is_quat\nfunction quat.is_quat(a)\n\tif type(a) == \"cdata\" then\n\t\treturn ffi.istype(\"cpml_quat\", a)\n\tend\n\n\treturn\n\t\ttype(a)   == \"table\"  and\n\t\ttype(a.x) == \"number\" and\n\t\ttype(a.y) == \"number\" and\n\t\ttype(a.z) == \"number\" and\n\t\ttype(a.w) == \"number\"\nend\n\n--- Return a boolean showing if a table is or is not a zero quat.\n-- @tparam quat a Quaternion to be tested\n-- @treturn boolean is_zero\nfunction quat.is_zero(a)\n\treturn\n\t\ta.x == 0 and\n\t\ta.y == 0 and\n\t\ta.z == 0 and\n\t\ta.w == 0\nend\n\n--- Return a boolean showing if a table is or is not a real quat.\n-- @tparam quat a Quaternion to be tested\n-- @treturn boolean is_real\nfunction quat.is_real(a)\n\treturn\n\t\ta.x == 0 and\n\t\ta.y == 0 and\n\t\ta.z == 0\nend\n\n--- Return a boolean showing if a table is or is not an imaginary quat.\n-- @tparam quat a Quaternion to be tested\n-- @treturn boolean is_imaginary\nfunction quat.is_imaginary(a)\n\treturn a.w == 0\nend\n\n--- Return whether any component is NaN\n-- @tparam quat a Quaternion to be tested\n-- @treturn boolean if x,y,z, or w is NaN\nfunction quat.has_nan(a)\n\treturn private.is_nan(a.x) or\n\t\tprivate.is_nan(a.y) or\n\t\tprivate.is_nan(a.z) or\n\t\tprivate.is_nan(a.w)\nend\n\n--- Convert a quaternion into an angle plus axis components.\n-- @tparam quat a Quaternion to convert\n-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,0,0,1)\n-- @treturn number angle\n-- @treturn x axis-x\n-- @treturn y axis-y\n-- @treturn z axis-z\nfunction quat.to_angle_axis_unpack(a, identityAxis)\n\tif a.w > 1 or a.w < -1 then\n\t\ta = a:normalize()\n\tend\n\n\t-- If length of xyz components is less than DBL_EPSILON, this is zero or close enough (an identity quaternion)\n\t-- Normally an identity quat would return a nonsense answer, so we return an arbitrary zero rotation early.\n\t-- FIXME: Is it safe to assume there are *no* valid quaternions with nonzero degenerate lengths?\n\tif a.x*a.x + a.y*a.y + a.z*a.z < constants.DBL_EPSILON*constants.DBL_EPSILON then\n\t\tif identityAxis then\n\t\t\treturn 0,identityAxis:unpack()\n\t\telse\n\t\t\treturn 0,0,0,1\n\t\tend\n\tend\n\n\tlocal x, y, z\n\tlocal angle = 2 * acos(a.w)\n\tlocal s     = sqrt(1 - a.w * a.w)\n\n\tif s < DBL_EPSILON then\n\t\tx = a.x\n\t\ty = a.y\n\t\tz = a.z\n\telse\n\t\tx = a.x / s\n\t\ty = a.y / s\n\t\tz = a.z / s\n\tend\n\n\treturn angle, x, y, z\nend\n\n--- Convert a quaternion into an angle/axis pair.\n-- @tparam quat a Quaternion to convert\n-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,vec3(0,0,1))\n-- @treturn number angle\n-- @treturn vec3 axis\nfunction quat.to_angle_axis(a, identityAxis)\n\tlocal angle, x, y, z = a:to_angle_axis_unpack(identityAxis)\n\treturn angle, vec3(x, y, z)\nend\n\n--- Convert a quaternion into a vec3.\n-- @tparam quat a Quaternion to convert\n-- @treturn vec3 out\nfunction quat.to_vec3(a)\n\treturn vec3(a.x, a.y, a.z)\nend\n\n--- Return a formatted string.\n-- @tparam quat a Quaternion to be turned into a string\n-- @treturn string formatted\nfunction quat.to_string(a)\n\treturn string.format(\"(%+0.3f,%+0.3f,%+0.3f,%+0.3f)\", a.x, a.y, a.z, a.w)\nend\n\nquat_mt.__index    = quat\nquat_mt.__tostring = quat.to_string\n\nfunction quat_mt.__call(_, x, y, z, w)\n\treturn quat.new(x, y, z, w)\nend\n\nfunction quat_mt.__unm(a)\n\treturn a:scale(-1)\nend\n\nfunction quat_mt.__eq(a,b)\n\tif not quat.is_quat(a) or not quat.is_quat(b) then\n\t\treturn false\n\tend\n\treturn a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w\nend\n\nfunction quat_mt.__add(a, b)\n\tprecond.assert(quat.is_quat(a), \"__add: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)\", type(a))\n\tprecond.assert(quat.is_quat(b), \"__add: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)\", type(b))\n\treturn a:add(b)\nend\n\nfunction quat_mt.__sub(a, b)\n\tprecond.assert(quat.is_quat(a), \"__sub: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)\", type(a))\n\tprecond.assert(quat.is_quat(b), \"__sub: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)\", type(b))\n\treturn a:sub(b)\nend\n\nfunction quat_mt.__mul(a, b)\n\tprecond.assert(quat.is_quat(a), \"__mul: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)\", type(a))\n\tassert(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)\")\n\n\tif quat.is_quat(b) then\n\t\treturn a:mul(b)\n\tend\n\n\tif type(b) == \"number\" then\n\t\treturn a:scale(b)\n\tend\n\n\treturn a:mul_vec3(b)\nend\n\nfunction quat_mt.__pow(a, n)\n\tprecond.assert(quat.is_quat(a), \"__pow: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)\", type(a))\n\tprecond.typeof(n, \"number\", \"__pow: Wrong argument type for right hand operand.\")\n\treturn a:pow(n)\nend\n\nif status then\n\txpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded\n\t\tffi.metatype(new, quat_mt)\n\tend, function() end)\nend\n\nreturn setmetatable({}, quat_mt)\n"
  },
  {
    "path": "modules/simplex.lua",
    "content": "--- Simplex Noise\n-- @module simplex\n\n--\n-- Based on code in \"Simplex noise demystified\", by Stefan Gustavson\n-- www.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf\n--\n-- Thanks to Mike Pall for some cleanup and improvements (and for LuaJIT!)\n--\n-- Permission is hereby granted, free of charge, to any person obtaining\n-- a copy of this software and associated documentation files (the\n-- \"Software\"), to deal in the Software without restriction, including\n-- without limitation the rights to use, copy, modify, merge, publish,\n-- distribute, sublicense, and/or sell copies of the Software, and to\n-- permit persons to whom the Software is furnished to do so, subject to\n-- the following conditions:\n--\n-- The above copyright notice and this permission notice shall be\n-- included in all copies or substantial portions of the Software.\n--\n-- THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n--\n-- [ MIT license: http://www.opensource.org/licenses/mit-license.php ]\n--\n\nif _G.love and _G.love.math then\n\treturn love.math.noise\nend\n\n-- Bail out with dummy module if FFI is missing.\nlocal has_ffi, ffi = pcall(require, \"ffi\")\nif not has_ffi then\n\treturn function()\n\t\treturn 0\n\tend\nend\n\n-- Modules --\nlocal bit = require(\"bit\")\n\n-- Imports --\nlocal band   = bit.band\nlocal bor    = bit.bor\nlocal floor  = math.floor\nlocal lshift = bit.lshift\nlocal max    = math.max\nlocal rshift = bit.rshift\n\n-- Permutation of 0-255, replicated to allow easy indexing with sums of two bytes --\nlocal Perms = ffi.new(\"uint8_t[512]\", {\n\t151, 160, 137,  91,  90,  15, 131,  13, 201,  95,  96,  53, 194, 233,   7, 225,\n\t140,  36, 103,  30,  69, 142,   8,  99,  37, 240,  21,  10,  23, 190,   6, 148,\n\t247, 120, 234,  75,   0,  26, 197,  62,  94, 252, 219, 203, 117,  35,  11,  32,\n\t 57, 177,  33,  88, 237, 149,  56,  87, 174,  20, 125, 136, 171, 168,  68, 175,\n\t 74, 165,  71, 134, 139,  48,  27, 166,  77, 146, 158, 231,  83, 111, 229, 122,\n\t 60, 211, 133, 230, 220, 105,  92,  41,  55,  46, 245,  40, 244, 102, 143,  54,\n\t 65,  25,  63, 161,   1, 216,  80,  73, 209,  76, 132, 187, 208,  89,  18, 169,\n\t200, 196, 135, 130, 116, 188, 159,  86, 164, 100, 109, 198, 173, 186,   3,  64,\n\t 52, 217, 226, 250, 124, 123,   5, 202,  38, 147, 118, 126, 255,  82,  85, 212,\n\t207, 206,  59, 227,  47,  16,  58,  17, 182, 189,  28,  42, 223, 183, 170, 213,\n\t119, 248, 152,   2,  44, 154, 163,  70, 221, 153, 101, 155, 167,  43, 172,   9,\n\t129,  22,  39, 253,  19,  98, 108, 110,  79, 113, 224, 232, 178, 185, 112, 104,\n\t218, 246,  97, 228, 251,  34, 242, 193, 238, 210, 144,  12, 191, 179, 162, 241,\n\t 81,  51, 145, 235, 249,  14, 239, 107,  49, 192, 214,  31, 181, 199, 106, 157,\n\t184,  84, 204, 176, 115, 121,  50,  45, 127,   4, 150, 254, 138, 236, 205,  93,\n\t222, 114,  67,  29,  24,  72, 243, 141, 128, 195,  78,  66, 215,  61, 156, 180\n})\n\n-- The above, mod 12 for each element --\nlocal Perms12 = ffi.new(\"uint8_t[512]\")\n\nfor i = 0, 255 do\n\tlocal x = Perms[i] % 12\n\n\tPerms[i + 256], Perms12[i], Perms12[i + 256] = Perms[i], x, x\nend\n\n-- Gradients for 2D, 3D case --\nlocal Grads3 = ffi.new(\"const double[12][3]\",\n\t{ 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 },\n\t{ 1, 0, 1 }, { -1, 0, 1 }, { 1, 0, -1 }, { -1, 0, -1 },\n\t{ 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }\n)\n\n-- 2D weight contribution\nlocal function GetN2(bx, by, x, y)\n\tlocal t = .5 - x * x - y * y\n\tlocal index = Perms12[bx + Perms[by]]\n\n\treturn max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y)\nend\n\nlocal function simplex_2d(x, y)\n\t--[[\n\t\t2D skew factors:\n\t\tF = (math.sqrt(3) - 1) / 2\n\t\tG = (3 - math.sqrt(3)) / 6\n\t\tG2 = 2 * G - 1\n\t]]\n\n\t-- Skew the input space to determine which simplex cell we are in.\n\tlocal s = (x + y) * 0.366025403 -- F\n\tlocal ix, iy = floor(x + s), floor(y + s)\n\n\t-- Unskew the cell origin back to (x, y) space.\n\tlocal t = (ix + iy) * 0.211324865 -- G\n\tlocal x0 = x + t - ix\n\tlocal y0 = y + t - iy\n\n\t-- Calculate the contribution from the two fixed corners.\n\t-- A step of (1,0) in (i,j) means a step of (1-G,-G) in (x,y), and\n\t-- A step of (0,1) in (i,j) means a step of (-G,1-G) in (x,y).\n\tix, iy = band(ix, 255), band(iy, 255)\n\n\tlocal n0 = GetN2(ix, iy, x0, y0)\n\tlocal n2 = GetN2(ix + 1, iy + 1, x0 - 0.577350270, y0 - 0.577350270) -- G2\n\n\t--[[\n\t\tDetermine other corner based on simplex (equilateral triangle) we are in:\n\t\tif x0 > y0 then\n\t\t\tix, x1 = ix + 1, x1 - 1\n\t\telse\n\t\t\tiy, y1 = iy + 1, y1 - 1\n\t\tend\n\t]]\n\tlocal xi = rshift(floor(y0 - x0), 31) -- y0 < x0\n\tlocal n1 = GetN2(ix + xi, iy + (1 - xi), x0 + 0.211324865 - xi, y0 - 0.788675135 + xi) -- x0 + G - xi, y0 + G - (1 - xi)\n\n\t-- Add contributions from each corner to get the final noise value.\n\t-- The result is scaled to return values in the interval [-1,1].\n\treturn 70.1480580019 * (n0 + n1 + n2)\nend\n\n-- 3D weight contribution\nlocal function GetN3(ix, iy, iz, x, y, z)\n\tlocal t = .6 - x * x - y * y - z * z\n\tlocal index = Perms12[ix + Perms[iy + Perms[iz]]]\n\n\treturn max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y + Grads3[index][2] * z)\nend\n\nlocal function simplex_3d(x, y, z)\n\t--[[\n\t\t3D skew factors:\n\t\tF = 1 / 3\n\t\tG = 1 / 6\n\t\tG2 = 2 * G\n\t\tG3 = 3 * G - 1\n\t]]\n\n\t-- Skew the input space to determine which simplex cell we are in.\n\tlocal s = (x + y + z) * 0.333333333 -- F\n\tlocal ix, iy, iz = floor(x + s), floor(y + s), floor(z + s)\n\n\t-- Unskew the cell origin back to (x, y, z) space.\n\tlocal t = (ix + iy + iz) * 0.166666667 -- G\n\tlocal x0 = x + t - ix\n\tlocal y0 = y + t - iy\n\tlocal z0 = z + t - iz\n\n\t-- Calculate the contribution from the two fixed corners.\n\t-- A step of (1,0,0) in (i,j,k) means a step of (1-G,-G,-G) in (x,y,z);\n\t-- a step of (0,1,0) in (i,j,k) means a step of (-G,1-G,-G) in (x,y,z);\n\t-- a step of (0,0,1) in (i,j,k) means a step of (-G,-G,1-G) in (x,y,z).\n\tix, iy, iz = band(ix, 255), band(iy, 255), band(iz, 255)\n\n\tlocal n0 = GetN3(ix, iy, iz, x0, y0, z0)\n\tlocal n3 = GetN3(ix + 1, iy + 1, iz + 1, x0 - 0.5, y0 - 0.5, z0 - 0.5) -- G3\n\n\t--[[\n\t\tDetermine other corners based on simplex (skewed tetrahedron) we are in:\n\n\t\tif x0 >= y0 then -- ~A\n\t\t\tif y0 >= z0 then -- ~A and ~B\n\t\t\t\ti1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 1, 0\n\t\t\telseif x0 >= z0 then -- ~A and B and ~C\n\t\t\t\ti1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 0, 1\n\t\t\telse -- ~A and B and C\n\t\t\t\ti1, j1, k1, i2, j2, k2 = 0, 0, 1, 1, 0, 1\n\t\t\tend\n\t\telse -- A\n\t\t\tif y0 < z0 then -- A and B\n\t\t\t\ti1, j1, k1, i2, j2, k2 = 0, 0, 1, 0, 1, 1\n\t\t\telseif x0 < z0 then -- A and ~B and C\n\t\t\t\ti1, j1, k1, i2, j2, k2 = 0, 1, 0, 0, 1, 1\n\t\t\telse -- A and ~B and ~C\n\t\t\t\ti1, j1, k1, i2, j2, k2 = 0, 1, 0, 1, 1, 0\n\t\t\tend\n\t\tend\n\t]]\n\n\tlocal xLy = rshift(floor(x0 - y0), 31) -- x0 < y0\n\tlocal yLz = rshift(floor(y0 - z0), 31) -- y0 < z0\n\tlocal xLz = rshift(floor(x0 - z0), 31) -- x0 < z0\n\n\tlocal i1 = band(1 - xLy, bor(1 - yLz, 1 - xLz)) -- x0 >= y0 and (y0 >= z0 or x0 >= z0)\n\tlocal j1 = band(xLy, 1 - yLz) -- x0 < y0 and y0 >= z0\n\tlocal k1 = band(yLz, bor(xLy, xLz)) -- y0 < z0 and (x0 < y0 or x0 < z0)\n\n\tlocal i2 = bor(1 - xLy, band(1 - yLz, 1 - xLz)) -- x0 >= y0 or (y0 >= z0 and x0 >= z0)\n\tlocal j2 = bor(xLy, 1 - yLz) -- x0 < y0 or y0 >= z0\n\tlocal 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))\n\n\tlocal n1 = GetN3(ix + i1, iy + j1, iz + k1, x0 + 0.166666667 - i1, y0 + 0.166666667 - j1, z0 + 0.166666667 - k1) -- G\n\tlocal n2 = GetN3(ix + i2, iy + j2, iz + k2, x0 + 0.333333333 - i2, y0 + 0.333333333 - j2, z0 + 0.333333333 - k2) -- G2\n\n\t-- Add contributions from each corner to get the final noise value.\n\t-- The result is scaled to stay just inside [-1,1]\n\treturn 28.452842 * (n0 + n1 + n2 + n3)\nend\n\n-- Gradients for 4D case --\nlocal Grads4 = ffi.new(\"const double[32][4]\",\n\t{ 0, 1, 1, 1 }, { 0, 1, 1, -1 }, { 0, 1, -1, 1 }, { 0, 1, -1, -1 },\n\t{ 0, -1, 1, 1 }, { 0, -1, 1, -1 }, { 0, -1, -1, 1 }, { 0, -1, -1, -1 },\n\t{ 1, 0, 1, 1 }, { 1, 0, 1, -1 }, { 1, 0, -1, 1 }, { 1, 0, -1, -1 },\n\t{ -1, 0, 1, 1 }, { -1, 0, 1, -1 }, { -1, 0, -1, 1 }, { -1, 0, -1, -1 },\n\t{ 1, 1, 0, 1 }, { 1, 1, 0, -1 }, { 1, -1, 0, 1 }, { 1, -1, 0, -1 },\n\t{ -1, 1, 0, 1 }, { -1, 1, 0, -1 }, { -1, -1, 0, 1 }, { -1, -1, 0, -1 },\n\t{ 1, 1, 1, 0 }, { 1, 1, -1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 },\n\t{ -1, 1, 1, 0 }, { -1, 1, -1, 0 }, { -1, -1, 1, 0 }, { -1, -1, -1, 0 }\n)\n\n-- 4D weight contribution\nlocal function GetN4(ix, iy, iz, iw, x, y, z, w)\n\tlocal t = .6 - x * x - y * y - z * z - w * w\n\tlocal index = band(Perms[ix + Perms[iy + Perms[iz + Perms[iw]]]], 0x1F)\n\n\treturn max(0, (t * t) * (t * t)) * (Grads4[index][0] * x + Grads4[index][1] * y + Grads4[index][2] * z + Grads4[index][3] * w)\nend\n\n-- A lookup table to traverse the simplex around a given point in 4D.\n-- Details can be found where this table is used, in the 4D noise method.\nlocal Simplex = ffi.new(\"uint8_t[64][4]\",\n\t{ 0, 1, 2, 3 }, { 0, 1, 3, 2 }, {}, { 0, 2, 3, 1 }, {}, {}, {}, { 1, 2, 3 },\n\t{ 0, 2, 1, 3 }, {}, { 0, 3, 1, 2 }, { 0, 3, 2, 1 }, {}, {}, {}, { 1, 3, 2 },\n\t{}, {}, {}, {}, {}, {}, {}, {},\n\t{ 1, 2, 0, 3 }, {}, { 1, 3, 0, 2 }, {}, {}, {}, { 2, 3, 0, 1 }, { 2, 3, 1 },\n\t{ 1, 0, 2, 3 }, { 1, 0, 3, 2 }, {}, {}, {}, { 2, 0, 3, 1 }, {}, { 2, 1, 3 },\n\t{}, {}, {}, {}, {}, {}, {}, {},\n\t{ 2, 0, 1, 3 }, {}, {}, {}, { 3, 0, 1, 2 }, { 3, 0, 2, 1 }, {}, { 3, 1, 2 },\n\t{ 2, 1, 0, 3 }, {}, {}, {}, { 3, 1, 0, 2 }, {}, { 3, 2, 0, 1 }, { 3, 2, 1 }\n)\n\n-- Convert the above indices to masks that can be shifted / anded into offsets --\nfor i = 0, 63 do\n\tSimplex[i][0] = lshift(1, Simplex[i][0]) - 1\n\tSimplex[i][1] = lshift(1, Simplex[i][1]) - 1\n\tSimplex[i][2] = lshift(1, Simplex[i][2]) - 1\n\tSimplex[i][3] = lshift(1, Simplex[i][3]) - 1\nend\n\nlocal function simplex_4d(x, y, z, w)\n\t--[[\n\t\t4D skew factors:\n\t\tF = (math.sqrt(5) - 1) / 4\n\t\tG = (5 - math.sqrt(5)) / 20\n\t\tG2 = 2 * G\n\t\tG3 = 3 * G\n\t\tG4 = 4 * G - 1\n\t]]\n\n\t-- Skew the input space to determine which simplex cell we are in.\n\tlocal s = (x + y + z + w) * 0.309016994 -- F\n\tlocal ix, iy, iz, iw = floor(x + s), floor(y + s), floor(z + s), floor(w + s)\n\n\t-- Unskew the cell origin back to (x, y, z) space.\n\tlocal t = (ix + iy + iz + iw) * 0.138196601 -- G\n\tlocal x0 = x + t - ix\n\tlocal y0 = y + t - iy\n\tlocal z0 = z + t - iz\n\tlocal w0 = w + t - iw\n\n\t-- For the 4D case, the simplex is a 4D shape I won't even try to describe.\n\t-- To find out which of the 24 possible simplices we're in, we need to\n\t-- determine the magnitude ordering of x0, y0, z0 and w0.\n\t-- The method below is a good way of finding the ordering of x,y,z,w and\n\t-- then find the correct traversal order for the simplex we�re in.\n\t-- First, six pair-wise comparisons are performed between each possible pair\n\t-- of the four coordinates, and the results are used to add up binary bits\n\t-- for an integer index.\n\tlocal c1 = band(rshift(floor(y0 - x0), 26), 32)\n\tlocal c2 = band(rshift(floor(z0 - x0), 27), 16)\n\tlocal c3 = band(rshift(floor(z0 - y0), 28), 8)\n\tlocal c4 = band(rshift(floor(w0 - x0), 29), 4)\n\tlocal c5 = band(rshift(floor(w0 - y0), 30), 2)\n\tlocal c6 = rshift(floor(w0 - z0), 31)\n\n\t-- Simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.\n\t-- Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w\n\t-- impossible. Only the 24 indices which have non-zero entries make any sense.\n\t-- We use a thresholding to set the coordinates in turn from the largest magnitude.\n\tlocal c = c1 + c2 + c3 + c4 + c5 + c6\n\n\t-- The number 3 (i.e. bit 2) in the \"simplex\" array is at the position of the largest coordinate.\n\tlocal i1 = rshift(Simplex[c][0], 2)\n\tlocal j1 = rshift(Simplex[c][1], 2)\n\tlocal k1 = rshift(Simplex[c][2], 2)\n\tlocal l1 = rshift(Simplex[c][3], 2)\n\n\t-- The number 2 (i.e. bit 1) in the \"simplex\" array is at the second largest coordinate.\n\tlocal i2 = band(rshift(Simplex[c][0], 1), 1)\n\tlocal j2 = band(rshift(Simplex[c][1], 1), 1)\n\tlocal k2 = band(rshift(Simplex[c][2], 1), 1)\n\tlocal l2 = band(rshift(Simplex[c][3], 1), 1)\n\n\t-- The number 1 (i.e. bit 0) in the \"simplex\" array is at the second smallest coordinate.\n\tlocal i3 = band(Simplex[c][0], 1)\n\tlocal j3 = band(Simplex[c][1], 1)\n\tlocal k3 = band(Simplex[c][2], 1)\n\tlocal l3 = band(Simplex[c][3], 1)\n\n\t-- Work out the hashed gradient indices of the five simplex corners\n\t-- Sum up and scale the result to cover the range [-1,1]\n\tix, iy, iz, iw = band(ix, 255), band(iy, 255), band(iz, 255), band(iw, 255)\n\n\tlocal n0 = GetN4(ix, iy, iz, iw, x0, y0, z0, w0)\n\tlocal 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\n\tlocal 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\n\tlocal 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\n\tlocal n4 = GetN4(ix + 1, iy + 1, iz + 1, iw + 1, x0 - 0.447213595, y0 - 0.447213595, z0 - 0.447213595, w0 - 0.447213595) -- G4\n\n\treturn 2.210600293 * (n0 + n1 + n2 + n3 + n4)\nend\n\n--- Simplex Noise\n-- @param x\n-- @param y\n-- @param z optional\n-- @param w optional\n-- @return Noise value in the range [-1, +1]\nreturn function(x, y, z, w)\n\tif w then\n\t\treturn simplex_4d(x, y, z, w)\n\tend\n\tif z then\n\t\treturn simplex_3d(x, y, z)\n\tend\n\tif y then\n\t\treturn simplex_2d(x, y)\n\tend\n\terror \"Simplex requires at least two arguments\"\nend\n"
  },
  {
    "path": "modules/utils.lua",
    "content": "--- Various utility functions\n-- @module utils\n\nlocal modules = (...): gsub('%.[^%.]+$', '') .. \".\"\nlocal vec2    = require(modules .. \"vec2\")\nlocal vec3    = require(modules .. \"vec3\")\nlocal private = require(modules .. \"_private_utils\")\nlocal abs     = math.abs\nlocal ceil    = math.ceil\nlocal floor   = math.floor\nlocal log     = math.log\nlocal utils   = {}\n\n-- reimplementation of math.frexp, due to its removal from Lua 5.3 :(\n-- courtesy of airstruck\nlocal log2 = log(2)\n\nlocal frexp = math.frexp or function(x)\n\tif x == 0 then return 0, 0 end\n\tlocal e = floor(log(abs(x)) / log2 + 1)\n\treturn x / 2 ^ e, e\nend\n\n--- Clamps a value within the specified range.\n-- @param value Input value\n-- @param min Minimum output value\n-- @param max Maximum output value\n-- @return number\nfunction utils.clamp(value, min, max)\n\treturn math.max(math.min(value, max), min)\nend\n\n--- Returns `value` if it is equal or greater than |`size`|, or 0.\n-- @param value\n-- @param size\n-- @return number\nfunction utils.deadzone(value, size)\n\treturn abs(value) >= size and value or 0\nend\n\n--- Check if value is equal or greater than threshold.\n-- @param value\n-- @param threshold\n-- @return boolean\nfunction utils.threshold(value, threshold)\n\t-- I know, it barely saves any typing at all.\n\treturn abs(value) >= threshold\nend\n\n--- Check if value is equal or less than threshold.\n-- @param value\n-- @param threshold\n-- @return boolean\nfunction utils.tolerance(value, threshold)\n\t-- I know, it barely saves any typing at all.\n\treturn abs(value) <= threshold\nend\n\n--- Scales a value from one range to another.\n-- @param value Input value\n-- @param min_in Minimum input value\n-- @param max_in Maximum input value\n-- @param min_out Minimum output value\n-- @param max_out Maximum output value\n-- @return number\nfunction utils.map(value, min_in, max_in, min_out, max_out)\n\treturn ((value) - (min_in)) * ((max_out) - (min_out)) / ((max_in) - (min_in)) + (min_out)\nend\n\n--- Linear interpolation.\n-- Performs linear interpolation between 0 and 1 when `low` < `progress` < `high`.\n-- @param low value to return when `progress` is 0\n-- @param high value to return when `progress` is 1\n-- @param progress (0-1)\n-- @return number\nfunction utils.lerp(low, high, progress)\n\treturn low * (1 - progress) + high * progress\nend\n\n--- Exponential decay\n-- @param low initial value\n-- @param high target value\n-- @param rate portion of the original value remaining per second\n-- @param dt time delta\n-- @return number\nfunction utils.decay(low, high, rate, dt)\n\treturn utils.lerp(low, high, 1.0 - math.exp(-rate * dt))\nend\n\n--- Hermite interpolation.\n-- Performs smooth Hermite interpolation between 0 and 1 when `low` < `progress` < `high`.\n-- @param progress (0-1)\n-- @param low value to return when `progress` is 0\n-- @param high value to return when `progress` is 1\n-- @return number\nfunction utils.smoothstep(progress, low, high)\n\tlocal t = utils.clamp((progress - low) / (high - low), 0.0, 1.0)\n\treturn t * t * (3.0 - 2.0 * t)\nend\n\n--- Round number at a given precision.\n-- Truncates `value` at `precision` points after the decimal (whole number if\n-- left unspecified).\n-- @param value\n-- @param precision\n-- @return number\nutils.round = private.round\n\n--- Wrap `value` around if it exceeds `limit`.\n-- @param value\n-- @param limit\n-- @return number\nfunction utils.wrap(value, limit)\n\tif value < 0 then\n\t\tvalue = value + utils.round(((-value/limit)+1))*limit\n\tend\n\treturn value % limit\nend\n\n--- Check if a value is a power-of-two.\n-- Returns true if a number is a valid power-of-two, otherwise false.\n-- @author undef\n-- @param value\n-- @return boolean\nfunction utils.is_pot(value)\n\t-- found here: https://love2d.org/forums/viewtopic.php?p=182219#p182219\n\t-- check if a number is a power-of-two\n\treturn (frexp(value)) == 0.5\nend\n\n--- Check if a value is NaN\n-- Returns true if a number is not a valid number\n-- @param value\n-- @return boolean\nutils.is_nan = private.is_nan\n\n-- Originally from vec3\nfunction utils.project_on(a, b)\n\tlocal s =\n\t\t(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /\n\t\t(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0)\n\n\tif a.z and b.z then\n\t\treturn vec3(\n\t\t\tb.x * s,\n\t\t\tb.y * s,\n\t\t\tb.z * s\n\t\t)\n\tend\n\n\treturn vec2(\n\t\tb.x * s,\n\t\tb.y * s\n\t)\nend\n\n-- Originally from vec3\nfunction utils.project_from(a, b)\n\tlocal s =\n\t\t(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) /\n\t\t(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0)\n\n\tif a.z and b.z then\n\t\treturn vec3(\n\t\t\tb.x * s,\n\t\t\tb.y * s,\n\t\t\tb.z * s\n\t\t)\n\tend\n\n\treturn vec2(\n\t\tb.x * s,\n\t\tb.y * s\n\t)\nend\n\n-- Originally from vec3\nfunction utils.mirror_on(a, b)\n\tlocal s =\n\t\t(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /\n\t\t(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) * 2\n\n\tif a.z and b.z then\n\t\treturn vec3(\n\t\t\tb.x * s - a.x,\n\t\t\tb.y * s - a.y,\n\t\t\tb.z * s - a.z\n\t\t)\n\tend\n\n\treturn vec2(\n\t\tb.x * s - a.x,\n\t\tb.y * s - a.y\n\t)\nend\n\n-- Originally from vec3\nfunction utils.reflect(i, n)\n\treturn i - (n * (2 * n:dot(i)))\nend\n\n-- Originally from vec3\nfunction utils.refract(i, n, ior)\n\tlocal d = n:dot(i)\n\tlocal k = 1 - ior * ior * (1 - d * d)\n\n\tif k >= 0 then\n\t\treturn (i * ior) - (n * (ior * d + k ^ 0.5))\n\tend\n\n\treturn vec3()\nend\n\n--- Get the sign of a number\n-- returns 1 for positive values, -1 for negative and 0 for zero.\n-- @param value\n-- @return number\nfunction utils.sign(n)\n\tif n > 0 then\n\t\treturn 1\n\telseif n < 0 then\n\t\treturn -1\n\telse\n\t\treturn 0\n\tend\nend\n\nreturn utils\n"
  },
  {
    "path": "modules/vec2.lua",
    "content": "--- A 2 component vector.\n-- @module vec2\n\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal vec3    = require(modules .. \"vec3\")\nlocal precond = require(modules .. \"_private_precond\")\nlocal private = require(modules .. \"_private_utils\")\nlocal acos    = math.acos\nlocal atan2   = math.atan2 or math.atan\nlocal sqrt    = math.sqrt\nlocal cos     = math.cos\nlocal sin     = math.sin\nlocal vec2    = {}\nlocal vec2_mt = {}\n\n-- Private constructor.\nlocal function new(x, y)\n\treturn setmetatable({\n\t\tx = x or 0,\n\t\ty = y or 0\n\t}, vec2_mt)\nend\n\n-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.\nlocal status, ffi\nif type(jit) == \"table\" and jit.status() then\n\tstatus, ffi = pcall(require, \"ffi\")\n\tif status then\n\t\tffi.cdef \"typedef struct { double x, y;} cpml_vec2;\"\n\t\tnew = ffi.typeof(\"cpml_vec2\")\n\tend\nend\n\n--- Constants\n-- @table vec2\n-- @field unit_x X axis of rotation\n-- @field unit_y Y axis of rotation\n-- @field zero Empty vector\nvec2.unit_x = new(1, 0)\nvec2.unit_y = new(0, 1)\nvec2.zero   = new(0, 0)\n\n--- The public constructor.\n-- @param x Can be of three types: </br>\n-- number X component\n-- table {x, y} or {x = x, y = y}\n-- scalar to fill the vector eg. {x, x}\n-- @tparam number y Y component\n-- @treturn vec2 out\nfunction vec2.new(x, y)\n\t-- number, number\n\tif x and y then\n\t\tprecond.typeof(x, \"number\", \"new: Wrong argument type for x\")\n\t\tprecond.typeof(y, \"number\", \"new: Wrong argument type for y\")\n\n\t\treturn new(x, y)\n\n\t-- {x, y} or {x=x, y=y}\n\telseif type(x) == \"table\" or type(x) == \"cdata\" then -- table in vanilla lua, cdata in luajit\n\t\tlocal xx, yy = x.x or x[1], x.y or x[2]\n\t\tprecond.typeof(xx, \"number\", \"new: Wrong argument type for x\")\n\t\tprecond.typeof(yy, \"number\", \"new: Wrong argument type for y\")\n\n\t\treturn new(xx, yy)\n\n\t-- number\n\telseif type(x) == \"number\" then\n\t\treturn new(x, x)\n\telse\n\t\treturn new()\n\tend\nend\n\n--- Convert point from polar to cartesian.\n-- @tparam number radius Radius of the point\n-- @tparam number theta Angle of the point (in radians)\n-- @treturn vec2 out\nfunction vec2.from_cartesian(radius, theta)\n\treturn new(radius * cos(theta), radius * sin(theta))\nend\n\n--- Clone a vector.\n-- @tparam vec2 a Vector to be cloned\n-- @treturn vec2 out\nfunction vec2.clone(a)\n\treturn new(a.x, a.y)\nend\n\n--- Add two vectors.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn vec2 out\nfunction vec2.add(a, b)\n\treturn new(\n\t\ta.x + b.x,\n\t\ta.y + b.y\n\t)\nend\n\n--- Subtract one vector from another.\n-- Order: If a and b are positions, computes the direction and distance from b\n-- to a.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn vec2 out\nfunction vec2.sub(a, b)\n\treturn new(\n\t\ta.x - b.x,\n\t\ta.y - b.y\n\t)\nend\n\n--- Multiply a vector by another vector.\n-- Component-size multiplication not matrix multiplication.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn vec2 out\nfunction vec2.mul(a, b)\n\treturn new(\n\t\ta.x * b.x,\n\t\ta.y * b.y\n\t)\nend\n\n--- Divide a vector by another vector.\n-- Component-size inv multiplication. Like a non-uniform scale().\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn vec2 out\nfunction vec2.div(a, b)\n\treturn new(\n\t\ta.x / b.x,\n\t\ta.y / b.y\n\t)\nend\n\n--- Get the normal of a vector.\n-- @tparam vec2 a Vector to normalize\n-- @treturn vec2 out\nfunction vec2.normalize(a)\n\tif a:is_zero() then\n\t\treturn new()\n\tend\n\treturn a:scale(1 / a:len())\nend\n\n--- Trim a vector to a given length.\n-- @tparam vec2 a Vector to be trimmed\n-- @tparam number len Length to trim the vector to\n-- @treturn vec2 out\nfunction vec2.trim(a, len)\n\treturn a:normalize():scale(math.min(a:len(), len))\nend\n\n--- Get the cross product of two vectors.\n-- Order: Positive if a is clockwise from b. Magnitude is the area spanned by\n-- the parallelograms that a and b span.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn number magnitude\nfunction vec2.cross(a, b)\n\treturn a.x * b.y - a.y * b.x\nend\n\n--- Get the dot product of two vectors.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn number dot\nfunction vec2.dot(a, b)\n\treturn a.x * b.x + a.y * b.y\nend\n\n--- Get the length of a vector.\n-- @tparam vec2 a Vector to get the length of\n-- @treturn number len\nfunction vec2.len(a)\n\treturn sqrt(a.x * a.x + a.y * a.y)\nend\n\n--- Get the squared length of a vector.\n-- @tparam vec2 a Vector to get the squared length of\n-- @treturn number len\nfunction vec2.len2(a)\n\treturn a.x * a.x + a.y * a.y\nend\n\n--- Get the distance between two vectors.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn number dist\nfunction vec2.dist(a, b)\n\tlocal dx = a.x - b.x\n\tlocal dy = a.y - b.y\n\treturn sqrt(dx * dx + dy * dy)\nend\n\n--- Get the squared distance between two vectors.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn number dist\nfunction vec2.dist2(a, b)\n\tlocal dx = a.x - b.x\n\tlocal dy = a.y - b.y\n\treturn dx * dx + dy * dy\nend\n\n--- Scale a vector by a scalar.\n-- @tparam vec2 a Left hand operand\n-- @tparam number b Right hand operand\n-- @treturn vec2 out\nfunction vec2.scale(a, b)\n\treturn new(\n\t\ta.x * b,\n\t\ta.y * b\n\t)\nend\n\n--- Rotate a vector.\n-- @tparam vec2 a Vector to rotate\n-- @tparam number phi Angle to rotate vector by (in radians)\n-- @treturn vec2 out\nfunction vec2.rotate(a, phi)\n\tlocal c = cos(phi)\n\tlocal s = sin(phi)\n\treturn new(\n\t\tc * a.x - s * a.y,\n\t\ts * a.x + c * a.y\n\t)\nend\n\n--- Get the perpendicular vector of a vector.\n-- @tparam vec2 a Vector to get perpendicular axes from\n-- @treturn vec2 out\nfunction vec2.perpendicular(a)\n\treturn new(-a.y, a.x)\nend\n\n--- Signed angle from one vector to another.\n-- Rotations from +x to +y are positive.\n-- @tparam vec2 a Vector\n-- @tparam vec2 b Vector\n-- @treturn number angle in (-pi, pi]\nfunction vec2.angle_to(a, b)\n\tif b then\n\t\tlocal angle = atan2(b.y, b.x) - atan2(a.y, a.x)\n\t\t-- convert to (-pi, pi]\n\t\tif angle > math.pi       then\n\t\t\tangle = angle - 2 * math.pi\n\t\telseif angle <= -math.pi then\n\t\t\tangle = angle + 2 * math.pi\n\t\tend\n\t\treturn angle\n\tend\n\n\treturn atan2(a.y, a.x)\nend\n\n--- Unsigned angle between two vectors.\n-- Directionless and thus commutative.\n-- @tparam vec2 a Vector\n-- @tparam vec2 b Vector\n-- @treturn number angle in [0, pi]\nfunction vec2.angle_between(a, b)\n\tif b then\n\t\tif vec2.is_vec2(a) then\n\t\t\treturn acos(a:dot(b) / (a:len() * b:len()))\n\t\tend\n\n\t\treturn acos(vec3.dot(a, b) / (vec3.len(a) * vec3.len(b)))\n\tend\n\n\treturn 0\nend\n\n--- Lerp between two vectors.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @tparam number s Step value\n-- @treturn vec2 out\nfunction vec2.lerp(a, b, s)\n\treturn a + (b - a) * s\nend\n\n--- Unpack a vector into individual components.\n-- @tparam vec2 a Vector to unpack\n-- @treturn number x\n-- @treturn number y\nfunction vec2.unpack(a)\n\treturn a.x, a.y\nend\n\n--- Return the component-wise minimum of two vectors.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.\nfunction vec2.component_min(a, b)\n\treturn new(math.min(a.x, b.x), math.min(a.y, b.y))\nend\n\n--- Return the component-wise maximum of two vectors.\n-- @tparam vec2 a Left hand operand\n-- @tparam vec2 b Right hand operand\n-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.\nfunction vec2.component_max(a, b)\n\treturn new(math.max(a.x, b.x), math.max(a.y, b.y))\nend\n\n\n--- Return a boolean showing if a table is or is not a vec2.\n-- @tparam vec2 a Vector to be tested\n-- @treturn boolean is_vec2\nfunction vec2.is_vec2(a)\n\tif type(a) == \"cdata\" then\n\t\treturn ffi.istype(\"cpml_vec2\", a)\n\tend\n\n\treturn\n\t\ttype(a)   == \"table\"  and\n\t\ttype(a.x) == \"number\" and\n\t\ttype(a.y) == \"number\"\nend\n\n--- Return a boolean showing if a table is or is not a zero vec2.\n-- @tparam vec2 a Vector to be tested\n-- @treturn boolean is_zero\nfunction vec2.is_zero(a)\n\treturn a.x == 0 and a.y == 0\nend\n\n--- Return whether either value is NaN\n-- @tparam vec2 a Vector to be tested\n-- @treturn boolean if x or y is nan\nfunction vec2.has_nan(a)\n\treturn private.is_nan(a.x) or\n\t\tprivate.is_nan(a.y)\nend\n\n--- Convert point from cartesian to polar.\n-- @tparam vec2 a Vector to convert\n-- @treturn number radius\n-- @treturn number theta\nfunction vec2.to_polar(a)\n\tlocal radius = sqrt(a.x^2 + a.y^2)\n\tlocal theta  = atan2(a.y, a.x)\n\ttheta = theta > 0 and theta or theta + 2 * math.pi\n\treturn radius, theta\nend\n\n-- Round all components to nearest int (or other precision).\n-- @tparam vec2 a Vector to round.\n-- @tparam precision Digits after the decimal (integer if unspecified)\n-- @treturn vec2 Rounded vector\nfunction vec2.round(a, precision)\n\treturn vec2.new(private.round(a.x, precision), private.round(a.y, precision))\nend\n\n-- Negate x axis only of vector.\n-- @tparam vec2 a Vector to x-flip.\n-- @treturn vec2 x-flipped vector\nfunction vec2.flip_x(a)\n\treturn vec2.new(-a.x, a.y)\nend\n\n-- Negate y axis only of vector.\n-- @tparam vec2 a Vector to y-flip.\n-- @treturn vec2 y-flipped vector\nfunction vec2.flip_y(a)\n\treturn vec2.new(a.x, -a.y)\nend\n\n-- Convert vec2 to vec3.\n-- @tparam vec2 a Vector to convert.\n-- @tparam number the new z component, or nil for 0\n-- @treturn vec3 Converted vector\nfunction vec2.to_vec3(a, z)\n\treturn vec3(a.x, a.y, z or 0)\nend\n\n--- Return a formatted string.\n-- @tparam vec2 a Vector to be turned into a string\n-- @treturn string formatted\nfunction vec2.to_string(a)\n\treturn string.format(\"(%+0.3f,%+0.3f)\", a.x, a.y)\nend\n\nvec2_mt.__index    = vec2\nvec2_mt.__tostring = vec2.to_string\n\nfunction vec2_mt.__call(_, x, y)\n\treturn vec2.new(x, y)\nend\n\nfunction vec2_mt.__unm(a)\n\treturn new(-a.x, -a.y)\nend\n\nfunction vec2_mt.__eq(a, b)\n\tif not vec2.is_vec2(a) or not vec2.is_vec2(b) then\n\t\treturn false\n\tend\n\treturn a.x == b.x and a.y == b.y\nend\n\nfunction vec2_mt.__add(a, b)\n\tprecond.assert(vec2.is_vec2(a), \"__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)\", type(a))\n\tprecond.assert(vec2.is_vec2(b), \"__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)\", type(b))\n\treturn a:add(b)\nend\n\nfunction vec2_mt.__sub(a, b)\n\tprecond.assert(vec2.is_vec2(a), \"__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)\", type(a))\n\tprecond.assert(vec2.is_vec2(b), \"__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)\", type(b))\n\treturn a:sub(b)\nend\n\nfunction vec2_mt.__mul(a, b)\n\tprecond.assert(vec2.is_vec2(a), \"__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)\", type(a))\n\tassert(vec2.is_vec2(b) or type(b) == \"number\", \"__mul: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)\")\n\n\tif vec2.is_vec2(b) then\n\t\treturn a:mul(b)\n\tend\n\n\treturn a:scale(b)\nend\n\nfunction vec2_mt.__div(a, b)\n\tprecond.assert(vec2.is_vec2(a), \"__div: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)\", type(a))\n\tassert(vec2.is_vec2(b) or type(b) == \"number\", \"__div: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)\")\n\n\tif vec2.is_vec2(b) then\n\t\treturn a:div(b)\n\tend\n\n\treturn a:scale(1 / b)\nend\n\nif status then\n\txpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded\n\t\tffi.metatype(new, vec2_mt)\n\tend, function() end)\nend\n\nreturn setmetatable({}, vec2_mt)\n"
  },
  {
    "path": "modules/vec3.lua",
    "content": "--- A 3 component vector.\n-- @module vec3\n\nlocal modules = (...):gsub('%.[^%.]+$', '') .. \".\"\nlocal precond = require(modules .. \"_private_precond\")\nlocal private = require(modules .. \"_private_utils\")\nlocal sqrt    = math.sqrt\nlocal cos     = math.cos\nlocal sin     = math.sin\nlocal vec3    = {}\nlocal vec3_mt = {}\n\n-- Private constructor.\nlocal function new(x, y, z)\n\treturn setmetatable({\n\t\tx = x or 0,\n\t\ty = y or 0,\n\t\tz = z or 0\n\t}, vec3_mt)\nend\n\n-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.\nlocal status, ffi\nif type(jit) == \"table\" and jit.status() then\n\tstatus, ffi = pcall(require, \"ffi\")\n\tif status then\n\t\tffi.cdef \"typedef struct { double x, y, z;} cpml_vec3;\"\n\t\tnew = ffi.typeof(\"cpml_vec3\")\n\tend\nend\n\n--- Constants\n-- @table vec3\n-- @field unit_x X axis of rotation\n-- @field unit_y Y axis of rotation\n-- @field unit_z Z axis of rotation\n-- @field zero Empty vector\nvec3.unit_x = new(1, 0, 0)\nvec3.unit_y = new(0, 1, 0)\nvec3.unit_z = new(0, 0, 1)\nvec3.zero   = new(0, 0, 0)\n\n--- The public constructor.\n-- @param x Can be of three types: </br>\n-- number X component\n-- table {x, y, z} or {x=x, y=y, z=z}\n-- scalar To fill the vector eg. {x, x, x}\n-- @tparam number y Y component\n-- @tparam number z Z component\n-- @treturn vec3 out\nfunction vec3.new(x, y, z)\n\t-- number, number, number\n\tif x and y and z then\n\t\tprecond.typeof(x, \"number\", \"new: Wrong argument type for x\")\n\t\tprecond.typeof(y, \"number\", \"new: Wrong argument type for y\")\n\t\tprecond.typeof(z, \"number\", \"new: Wrong argument type for z\")\n\n\t\treturn new(x, y, z)\n\n\t-- {x, y, z} or {x=x, y=y, z=z}\n\telseif type(x) == \"table\" or type(x) == \"cdata\" then -- table in vanilla lua, cdata in luajit\n\t\tlocal xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]\n\t\tprecond.typeof(xx, \"number\", \"new: Wrong argument type for x\")\n\t\tprecond.typeof(yy, \"number\", \"new: Wrong argument type for y\")\n\t\tprecond.typeof(zz, \"number\", \"new: Wrong argument type for z\")\n\n\t\treturn new(xx, yy, zz)\n\n\t-- number\n\telseif type(x) == \"number\" then\n\t\treturn new(x, x, x)\n\telse\n\t\treturn new()\n\tend\nend\n\n--- Clone a vector.\n-- @tparam vec3 a Vector to be cloned\n-- @treturn vec3 out\nfunction vec3.clone(a)\n\treturn new(a.x, a.y, a.z)\nend\n\n--- Add two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 out\nfunction vec3.add(a, b)\n\treturn new(\n\t\ta.x + b.x,\n\t\ta.y + b.y,\n\t\ta.z + b.z\n\t)\nend\n\n--- Subtract one vector from another.\n-- Order: If a and b are positions, computes the direction and distance from b\n-- to a.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 out\nfunction vec3.sub(a, b)\n\treturn new(\n\t\ta.x - b.x,\n\t\ta.y - b.y,\n\t\ta.z - b.z\n\t)\nend\n\n--- Multiply a vector by another vector.\n-- Component-wise multiplication not matrix multiplication.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 out\nfunction vec3.mul(a, b)\n\treturn new(\n\t\ta.x * b.x,\n\t\ta.y * b.y,\n\t\ta.z * b.z\n\t)\nend\n\n--- Divide a vector by another.\n-- Component-wise inv multiplication. Like a non-uniform scale().\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 out\nfunction vec3.div(a, b)\n\treturn new(\n\t\ta.x / b.x,\n\t\ta.y / b.y,\n\t\ta.z / b.z\n\t)\nend\n\n--- Scale a vector to unit length (1).\n-- @tparam vec3 a vector to normalize\n-- @treturn vec3 out\nfunction vec3.normalize(a)\n\tif a:is_zero() then\n\t\treturn new()\n\tend\n\treturn a:scale(1 / a:len())\nend\n\n--- Scale a vector to unit length (1), and return the input length.\n-- @tparam vec3 a vector to normalize\n-- @treturn vec3 out\n-- @treturn number input vector length\nfunction vec3.normalize_len(a)\n\tif a:is_zero() then\n\t\treturn new(), 0\n\tend\n\tlocal len = a:len()\n\treturn a:scale(1 / len), len\nend\n\n--- Trim a vector to a given length\n-- @tparam vec3 a vector to be trimmed\n-- @tparam number len Length to trim the vector to\n-- @treturn vec3 out\nfunction vec3.trim(a, len)\n\treturn a:normalize():scale(math.min(a:len(), len))\nend\n\n--- Get the cross product of two vectors.\n-- Resulting direction is right-hand rule normal of plane defined by a and b.\n-- Magnitude is the area spanned by the parallelograms that a and b span.\n-- Order: Direction determined by right-hand rule.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 out\nfunction vec3.cross(a, b)\n\treturn new(\n\t\ta.y * b.z - a.z * b.y,\n\t\ta.z * b.x - a.x * b.z,\n\t\ta.x * b.y - a.y * b.x\n\t)\nend\n\n--- Get the dot product of two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn number dot\nfunction vec3.dot(a, b)\n\treturn a.x * b.x + a.y * b.y + a.z * b.z\nend\n\n--- Get the length of a vector.\n-- @tparam vec3 a Vector to get the length of\n-- @treturn number len\nfunction vec3.len(a)\n\treturn sqrt(a.x * a.x + a.y * a.y + a.z * a.z)\nend\n\n--- Get the squared length of a vector.\n-- @tparam vec3 a Vector to get the squared length of\n-- @treturn number len\nfunction vec3.len2(a)\n\treturn a.x * a.x + a.y * a.y + a.z * a.z\nend\n\n--- Get the distance between two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn number dist\nfunction vec3.dist(a, b)\n\tlocal dx = a.x - b.x\n\tlocal dy = a.y - b.y\n\tlocal dz = a.z - b.z\n\treturn sqrt(dx * dx + dy * dy + dz * dz)\nend\n\n--- Get the squared distance between two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn number dist\nfunction vec3.dist2(a, b)\n\tlocal dx = a.x - b.x\n\tlocal dy = a.y - b.y\n\tlocal dz = a.z - b.z\n\treturn dx * dx + dy * dy + dz * dz\nend\n\n--- Scale a vector by a scalar.\n-- @tparam vec3 a Left hand operand\n-- @tparam number b Right hand operand\n-- @treturn vec3 out\nfunction vec3.scale(a, b)\n\treturn new(\n\t\ta.x * b,\n\t\ta.y * b,\n\t\ta.z * b\n\t)\nend\n\n--- Rotate vector about an axis.\n-- @tparam vec3 a Vector to rotate\n-- @tparam number phi Angle to rotate vector by (in radians)\n-- @tparam vec3 axis Axis to rotate by\n-- @treturn vec3 out\nfunction vec3.rotate(a, phi, axis)\n\tif not vec3.is_vec3(axis) then\n\t\treturn a\n\tend\n\n\tlocal u = axis:normalize()\n\tlocal c = cos(phi)\n\tlocal s = sin(phi)\n\n\t-- Calculate generalized rotation matrix\n\tlocal 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))\n\tlocal 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))\n\tlocal 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))      )\n\n\treturn new(\n\t\ta:dot(m1),\n\t\ta:dot(m2),\n\t\ta:dot(m3)\n\t)\nend\n\n--- Get the perpendicular vector of a vector.\n-- @tparam vec3 a Vector to get perpendicular axes from\n-- @treturn vec3 out\nfunction vec3.perpendicular(a)\n\treturn new(-a.y, a.x, 0)\nend\n\n--- Lerp between two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @tparam number s Step value\n-- @treturn vec3 out\nfunction vec3.lerp(a, b, s)\n\treturn a + (b - a) * s\nend\n\n-- Round all components to nearest int (or other precision).\n-- @tparam vec3 a Vector to round.\n-- @tparam precision Digits after the decimal (round numebr if unspecified)\n-- @treturn vec3 Rounded vector\nfunction vec3.round(a, precision)\n\treturn vec3.new(private.round(a.x, precision), private.round(a.y, precision), private.round(a.z, precision))\nend\n\n--- Unpack a vector into individual components.\n-- @tparam vec3 a Vector to unpack\n-- @treturn number x\n-- @treturn number y\n-- @treturn number z\nfunction vec3.unpack(a)\n\treturn a.x, a.y, a.z\nend\n\n--- Return the component-wise minimum of two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.\nfunction vec3.component_min(a, b)\n\treturn new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z))\nend\n\n--- Return the component-wise maximum of two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.\nfunction vec3.component_max(a, b)\n\treturn new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))\nend\n\n--- Return the component-wise minimum and maximum of two vectors.\n-- @tparam vec3 a Left hand operand\n-- @tparam vec3 b Right hand operand\n-- @treturn vec3, vec3 sorted vectors\nfunction vec3.component_sort(a, b)\n\treturn vec3.component_min(a, b), vec3.component_max(a, b)\nend\n\n-- Negate x axis only of vector.\n-- @tparam vec3 a Vector to x-flip.\n-- @treturn vec3 x-flipped vector\nfunction vec3.flip_x(a)\n\treturn vec3.new(-a.x, a.y, a.z)\nend\n\n-- Negate y axis only of vector.\n-- @tparam vec3 a Vector to y-flip.\n-- @treturn vec3 y-flipped vector\nfunction vec3.flip_y(a)\n\treturn vec3.new(a.x, -a.y, a.z)\nend\n\n-- Negate z axis only of vector.\n-- @tparam vec3 a Vector to z-flip.\n-- @treturn vec3 z-flipped vector\nfunction vec3.flip_z(a)\n\treturn vec3.new(a.x, a.y, -a.z)\nend\n\nfunction vec3.angle_to(a, b)\n\tlocal v = a:normalize():dot(b:normalize())\n\treturn math.acos(v)\nend\n\n--- Return a boolean showing if a table is or is not a vec3.\n-- @tparam vec3 a Vector to be tested\n-- @treturn boolean is_vec3\nfunction vec3.is_vec3(a)\n\tif type(a) == \"cdata\" then\n\t\treturn ffi.istype(\"cpml_vec3\", a)\n\tend\n\n\treturn\n\t\ttype(a)   == \"table\"  and\n\t\ttype(a.x) == \"number\" and\n\t\ttype(a.y) == \"number\" and\n\t\ttype(a.z) == \"number\"\nend\n\n--- Return a boolean showing if a table is or is not a zero vec3.\n-- @tparam vec3 a Vector to be tested\n-- @treturn boolean is_zero\nfunction vec3.is_zero(a)\n\treturn a.x == 0 and a.y == 0 and a.z == 0\nend\n\n--- Return whether any component is NaN\n-- @tparam vec3 a Vector to be tested\n-- @treturn boolean if x,y, or z are nan\nfunction vec3.has_nan(a)\n\treturn private.is_nan(a.x) or\n\t\tprivate.is_nan(a.y) or\n\t\tprivate.is_nan(a.z)\nend\n\n--- Return a formatted string.\n-- @tparam vec3 a Vector to be turned into a string\n-- @treturn string formatted\nfunction vec3.to_string(a)\n\treturn string.format(\"(%+0.3f,%+0.3f,%+0.3f)\", a.x, a.y, a.z)\nend\n\nvec3_mt.__index    = vec3\nvec3_mt.__tostring = vec3.to_string\n\nfunction vec3_mt.__call(_, x, y, z)\n\treturn vec3.new(x, y, z)\nend\n\nfunction vec3_mt.__unm(a)\n\treturn new(-a.x, -a.y, -a.z)\nend\n\nfunction vec3_mt.__eq(a, b)\n\tif not vec3.is_vec3(a) or not vec3.is_vec3(b) then\n\t\treturn false\n\tend\n\treturn a.x == b.x and a.y == b.y and a.z == b.z\nend\n\nfunction vec3_mt.__add(a, b)\n\tprecond.assert(vec3.is_vec3(a), \"__add: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)\", type(a))\n\tprecond.assert(vec3.is_vec3(b), \"__add: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)\", type(b))\n\treturn a:add(b)\nend\n\nfunction vec3_mt.__sub(a, b)\n\tprecond.assert(vec3.is_vec3(a), \"__sub: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)\", type(a))\n\tprecond.assert(vec3.is_vec3(b), \"__sub: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)\", type(b))\n\treturn a:sub(b)\nend\n\nfunction vec3_mt.__mul(a, b)\n\tprecond.assert(vec3.is_vec3(a), \"__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)\", type(a))\n\tprecond.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))\n\n\tif vec3.is_vec3(b) then\n\t\treturn a:mul(b)\n\tend\n\n\treturn a:scale(b)\nend\n\nfunction vec3_mt.__div(a, b)\n\tprecond.assert(vec3.is_vec3(a), \"__div: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)\", type(a))\n\tprecond.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))\n\n\tif vec3.is_vec3(b) then\n\t\treturn a:div(b)\n\tend\n\n\treturn a:scale(1 / b)\nend\n\nif status then\n\txpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded\n\t\tffi.metatype(new, vec3_mt)\n\tend, function() end)\nend\n\nreturn setmetatable({}, vec3_mt)\n"
  },
  {
    "path": "spec/bound2_spec.lua",
    "content": "local bound2      = require \"modules.bound2\"\nlocal vec2      = require \"modules.vec2\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\n\ndescribe(\"bound2:\", function()\n\tit(\"creates an empty bound2\", function()\n\t\tlocal a = bound2()\n\t\tassert.is.equal(0, a.min.x)\n\t\tassert.is.equal(0, a.min.y)\n\t\tassert.is.equal(0, a.max.x)\n\t\tassert.is.equal(0, a.max.y)\n\tend)\n\n\tit(\"creates a bound2 from vec2s\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(4,5))\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\tend)\n\n\tit(\"creates a bound2 using new()\", function()\n\t\tlocal a = bound2.new(vec2(1,2), vec2(4,5))\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\tend)\n\n\tit(\"creates a bound2 using at()\", function()\n\t\tlocal a = bound2.at(vec2(4,5), vec2(1,2))\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\tend)\n\n\tit(\"clones a bound2\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(4,5))\n\t\tlocal b = a:clone()\n\t\ta.max = vec2.new(9,9)\n\t\tassert.is.equal(a.min, b.min)\n\t\tassert.is.not_equal(a.max, b.max)\n\tend)\n\n\tit(\"uses bound2 check()\", function()\n\t\tlocal a = bound2(vec2(4,2), vec2(1,5)):check()\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\tend)\n\n\tit(\"queries a bound2 size\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(4,6))\n\t\tlocal v = a:size()\n\t\tlocal r = a:radius()\n\t\tassert.is.equal(3, v.x)\n\t\tassert.is.equal(4, v.y)\n\n\t\tassert.is.equal(1.5, r.x)\n\t\tassert.is.equal(2, r.y)\n\tend)\n\n\tit(\"sets a bound2 size\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(4,5))\n\t\tlocal b = a:with_size(vec2(1,1))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\n\t\tassert.is.equal(1, b.min.x)\n\t\tassert.is.equal(2, b.min.y)\n\t\tassert.is.equal(2, b.max.x)\n\t\tassert.is.equal(3, b.max.y)\n\tend)\n\n\tit(\"queries a bound2 center\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(3,4))\n\t\tlocal v = a:center()\n\t\tassert.is.equal(2, v.x)\n\t\tassert.is.equal(3, v.y)\n\tend)\n\n\tit(\"sets a bound2 center\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(3,4))\n\t\tlocal b = a:with_center(vec2(1,1))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.max.x)\n\t\tassert.is.equal(4, a.max.y)\n\n\t\tassert.is.equal(0, b.min.x)\n\t\tassert.is.equal(0, b.min.y)\n\t\tassert.is.equal(2, b.max.x)\n\t\tassert.is.equal(2, b.max.y)\n\tend)\n\n\tit(\"sets a bound2 size centered\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(3,4))\n\t\tlocal b = a:with_size_centered(vec2(4,4))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.max.x)\n\t\tassert.is.equal(4, a.max.y)\n\n\t\tassert.is.equal(0, b.min.x)\n\t\tassert.is.equal(1, b.min.y)\n\t\tassert.is.equal(4, b.max.x)\n\t\tassert.is.equal(5, b.max.y)\n\tend)\n\n\tit(\"insets a bound2\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(5,10))\n\t\tlocal b = a:inset(vec2(1,2))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(5, a.max.x)\n\t\tassert.is.equal(10, a.max.y)\n\n\t\tassert.is.equal(2, b.min.x)\n\t\tassert.is.equal(4, b.min.y)\n\t\tassert.is.equal(4, b.max.x)\n\t\tassert.is.equal(8, b.max.y)\n\tend)\n\n\tit(\"outsets a bound2\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(5,6))\n\t\tlocal b = a:outset(vec2(1,2))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(5, a.max.x)\n\t\tassert.is.equal(6, a.max.y)\n\n\t\tassert.is.equal(0, b.min.x)\n\t\tassert.is.equal(0, b.min.y)\n\t\tassert.is.equal(6, b.max.x)\n\t\tassert.is.equal(8, b.max.y)\n\tend)\n\n\tit(\"offsets a bound2\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(5,6))\n\t\tlocal b = a:offset(vec2(1,2))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(5, a.max.x)\n\t\tassert.is.equal(6, a.max.y)\n\n\t\tassert.is.equal(2, b.min.x)\n\t\tassert.is.equal(4, b.min.y)\n\t\tassert.is.equal(6, b.max.x)\n\t\tassert.is.equal(8, b.max.y)\n\tend)\n\n\tit(\"tests for points inside bound2\", function()\n\t\tlocal a = bound2(vec2(1,2), vec2(4,5))\n\n\t\tassert.is_true(a:contains(vec2(1,2)))\n\t\tassert.is_true(a:contains(vec2(4,5)))\n\t\tassert.is_true(a:contains(vec2(2,3)))\n\t\tassert.is_not_true(a:contains(vec2(0,3)))\n\t\tassert.is_not_true(a:contains(vec2(5,3)))\n\t\tassert.is_not_true(a:contains(vec2(2,1)))\n\t\tassert.is_not_true(a:contains(vec2(2,6)))\n\tend)\n\n\tit(\"rounds a bound2\", function()\n\t\tlocal a = bound2(vec2(1.1,1.9), vec2(3.9,5.1)):round()\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\tend)\n\n\tit(\"extends a bound2 with a point\", function()\n\t\tlocal min = vec2(1,2)\n\t\tlocal max = vec2(4,5)\n\t\tlocal downright = vec2(8,8)\n\t\tlocal downleft = vec2(-4,8)\n\t\tlocal top = vec2(2, 0)\n\n\t\tlocal a = bound2(min, max)\n\t\tlocal temp\n\n\t\ttemp = a:extend(downright)\n\t\tassert.is_true(a.min == min and a.max == max)\n\t\tassert.is_true(temp.min == min and temp.max == downright)\n\t\ttemp = a:extend(downleft)\n\t\tassert.is_true(temp.min == vec2(-4,2) and temp.max == vec2(4,8))\n\t\ttemp = a:extend(top)\n\t\tassert.is_true(temp.min == vec2(1,0) and temp.max == max)\n\tend)\n\n\tit(\"extends a bound with another bound\", function()\n\t\tlocal min = vec2(1,2)\n\t\tlocal max = vec2(4,5)\n\t\tlocal leftexpand = bound2.new(vec2(0,0), vec2(1.5, 6))\n\t\tlocal rightexpand = bound2.new(vec2(1.5,0), vec2(5, 6))\n\n\t\tlocal a = bound2(min, max)\n\t\tlocal temp\n\n\t\ttemp = a:extend_bound(leftexpand)\n\t\tassert.is_equal(temp.min, vec2(0,0))\n\t\tassert.is_equal(temp.max, vec2(4,6))\n\t\ttemp = temp:extend_bound(rightexpand)\n\t\tassert.is_equal(temp.min, vec2(0,0))\n\t\tassert.is_equal(temp.max, vec2(5,6))\n\tend)\n\t\n\tit(\"checks for bound2.zero\", function()\n\t\tassert.is.equal(0, bound2.zero.min.x)\n\t\tassert.is.equal(0, bound2.zero.min.y)\n\t\tassert.is.equal(0, bound2.zero.max.x)\n\t\tassert.is.equal(0, bound2.zero.max.y)\n\tend)\nend)\n"
  },
  {
    "path": "spec/bound3_spec.lua",
    "content": "local bound3      = require \"modules.bound3\"\nlocal vec3      = require \"modules.vec3\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\n\ndescribe(\"bound3:\", function()\n\tit(\"creates an empty bound3\", function()\n\t\tlocal a = bound3()\n\t\tassert.is.equal(0, a.min.x)\n\t\tassert.is.equal(0, a.min.y)\n\t\tassert.is.equal(0, a.min.z)\n\t\tassert.is.equal(0, a.max.x)\n\t\tassert.is.equal(0, a.max.y)\n\t\tassert.is.equal(0, a.max.z)\n\tend)\n\n\tit(\"creates a bound3 from vec3s\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(4,5,6))\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\t\tassert.is.equal(6, a.max.z)\n\tend)\n\n\tit(\"creates a bound3 using new()\", function()\n\t\tlocal a = bound3.new(vec3(1,2,3), vec3(4,5,6))\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\t\tassert.is.equal(6, a.max.z)\n\tend)\n\n\tit(\"creates a bound3 using at()\", function()\n\t\tlocal a = bound3.at(vec3(4,5,6), vec3(1,2,3))\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\t\tassert.is.equal(6, a.max.z)\n\tend)\n\n\tit(\"clones a bound3\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(4,5,6))\n\t\tlocal b = a:clone()\n\t\ta.max = vec3.new(9,9,9)\n\t\tassert.is.equal(a.min, b.min)\n\t\tassert.is.not_equal(a.max, b.max)\n\tend)\n\n\tit(\"uses bound3 check()\", function()\n\t\tlocal a = bound3(vec3(4,2,6), vec3(1,5,3)):check()\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\t\tassert.is.equal(6, a.max.z)\n\tend)\n\n\tit(\"queries a bound3 size\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(4,6,8))\n\t\tlocal v = a:size()\n\t\tlocal r = a:radius()\n\t\tassert.is.equal(3, v.x)\n\t\tassert.is.equal(4, v.y)\n\t\tassert.is.equal(5, v.z)\n\n\t\tassert.is.equal(1.5, r.x)\n\t\tassert.is.equal(2, r.y)\n\t\tassert.is.equal(2.5, r.z)\n\tend)\n\n\tit(\"sets a bound3 size\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(4,5,6))\n\t\tlocal b = a:with_size(vec3(1,1,1))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\t\tassert.is.equal(6, a.max.z)\n\n\t\tassert.is.equal(1, b.min.x)\n\t\tassert.is.equal(2, b.min.y)\n\t\tassert.is.equal(3, b.min.z)\n\t\tassert.is.equal(2, b.max.x)\n\t\tassert.is.equal(3, b.max.y)\n\t\tassert.is.equal(4, b.max.z)\n\tend)\n\n\tit(\"queries a bound3 center\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(3,4,5))\n\t\tlocal v = a:center()\n\t\tassert.is.equal(2, v.x)\n\t\tassert.is.equal(3, v.y)\n\t\tassert.is.equal(4, v.z)\n\tend)\n\n\tit(\"sets a bound3 center\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(3,4,5))\n\t\tlocal b = a:with_center(vec3(1,1,1))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(3, a.max.x)\n\t\tassert.is.equal(4, a.max.y)\n\t\tassert.is.equal(5, a.max.z)\n\n\t\tassert.is.equal(0, b.min.x)\n\t\tassert.is.equal(0, b.min.y)\n\t\tassert.is.equal(0, b.min.z)\n\t\tassert.is.equal(2, b.max.x)\n\t\tassert.is.equal(2, b.max.y)\n\t\tassert.is.equal(2, b.max.z)\n\tend)\n\n\tit(\"sets a bound3 size centered\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(3,4,5))\n\t\tlocal b = a:with_size_centered(vec3(4,4,4))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(3, a.max.x)\n\t\tassert.is.equal(4, a.max.y)\n\t\tassert.is.equal(5, a.max.z)\n\n\t\tassert.is.equal(0, b.min.x)\n\t\tassert.is.equal(1, b.min.y)\n\t\tassert.is.equal(2, b.min.z)\n\t\tassert.is.equal(4, b.max.x)\n\t\tassert.is.equal(5, b.max.y)\n\t\tassert.is.equal(6, b.max.z)\n\tend)\n\n\tit(\"insets a bound3\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(5,10,11))\n\t\tlocal b = a:inset(vec3(1,2,3))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(5, a.max.x)\n\t\tassert.is.equal(10, a.max.y)\n\t\tassert.is.equal(11, a.max.z)\n\n\t\tassert.is.equal(2, b.min.x)\n\t\tassert.is.equal(4, b.min.y)\n\t\tassert.is.equal(6, b.min.z)\n\t\tassert.is.equal(4, b.max.x)\n\t\tassert.is.equal(8, b.max.y)\n\t\tassert.is.equal(8, b.max.z)\n\tend)\n\n\tit(\"outsets a bound3\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(5,6,7))\n\t\tlocal b = a:outset(vec3(1,2,3))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(5, a.max.x)\n\t\tassert.is.equal(6, a.max.y)\n\t\tassert.is.equal(7, a.max.z)\n\n\t\tassert.is.equal(0, b.min.x)\n\t\tassert.is.equal(0, b.min.y)\n\t\tassert.is.equal(0, b.min.z)\n\t\tassert.is.equal(6, b.max.x)\n\t\tassert.is.equal(8, b.max.y)\n\t\tassert.is.equal(10, b.max.z)\n\tend)\n\n\tit(\"offsets a bound3\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(5,6,7))\n\t\tlocal b = a:offset(vec3(1,2,3))\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(5, a.max.x)\n\t\tassert.is.equal(6, a.max.y)\n\t\tassert.is.equal(7, a.max.z)\n\n\t\tassert.is.equal(2, b.min.x)\n\t\tassert.is.equal(4, b.min.y)\n\t\tassert.is.equal(6, b.min.z)\n\t\tassert.is.equal(6, b.max.x)\n\t\tassert.is.equal(8, b.max.y)\n\t\tassert.is.equal(10, b.max.z)\n\tend)\n\n\tit(\"tests for points inside bound3\", function()\n\t\tlocal a = bound3(vec3(1,2,3), vec3(4,5,6))\n\n\t\tassert.is_true(a:contains(vec3(1,2,3)))\n\t\tassert.is_true(a:contains(vec3(4,5,6)))\n\t\tassert.is_true(a:contains(vec3(2,3,4)))\n\t\tassert.is_not_true(a:contains(vec3(0,3,4)))\n\t\tassert.is_not_true(a:contains(vec3(5,3,4)))\n\t\tassert.is_not_true(a:contains(vec3(2,1,4)))\n\t\tassert.is_not_true(a:contains(vec3(2,6,4)))\n\t\tassert.is_not_true(a:contains(vec3(2,3,2)))\n\t\tassert.is_not_true(a:contains(vec3(2,3,7)))\n\tend)\n\n\tit(\"rounds a bound3\", function()\n\t\tlocal a = bound3(vec3(1.1,1.9,3), vec3(3.9,5.1,6)):round()\n\n\t\tassert.is.equal(1, a.min.x)\n\t\tassert.is.equal(2, a.min.y)\n\t\tassert.is.equal(3, a.min.z)\n\t\tassert.is.equal(4, a.max.x)\n\t\tassert.is.equal(5, a.max.y)\n\t\tassert.is.equal(6, a.max.z)\n\tend)\n\n\tit(\"extends a bound3 with a point\", function()\n\t\tlocal min = vec3(1,2,6)\n\t\tlocal max = vec3(4,5,9)\n\t\tlocal downright = vec3(8,8,10)\n\t\tlocal downleft = vec3(-4,8,10)\n\t\tlocal top = vec3(2, 0, 7)\n\n\t\tlocal a = bound3(min, max)\n\t\tlocal temp\n\n\t\ttemp = a:extend(downright)\n\t\tassert.is_true(a.min == min and a.max == max)\n\t\tassert.is_true(temp.min == min and temp.max == downright)\n\t\ttemp = a:extend(downleft)\n\t\tassert.is_true(temp.min == vec3(-4,2,6) and temp.max == vec3(4,8,10))\n\t\ttemp = a:extend(top)\n\t\tassert.is_true(temp.min == vec3(1,0,6) and temp.max == max)\n\tend)\n\n\tit(\"extends a bound with another bound\", function()\n\t\tlocal min = vec3(1,2,3)\n\t\tlocal max = vec3(4,5,6)\n\t\tlocal leftexpand = bound3.new(vec3(0,0,4), vec3(1.5,6,5))\n\t\tlocal rightexpand = bound3.new(vec3(1.5,0,1), vec3(5,6,7))\n\n\t\tlocal a = bound3(min, max)\n\t\tlocal temp\n\n\t\ttemp = a:extend_bound(leftexpand)\n\t\tassert.is_equal(temp.min, vec3(0,0,3))\n\t\tassert.is_equal(temp.max, vec3(4,6,6))\n\t\ttemp = temp:extend_bound(rightexpand)\n\t\tassert.is_equal(temp.min, vec3(0,0,1))\n\t\tassert.is_equal(temp.max, vec3(5,6,7))\n\tend)\n\n\tit(\"checks for bound3.zero\", function()\n\t\tassert.is.equal(0, bound3.zero.min.x)\n\t\tassert.is.equal(0, bound3.zero.min.y)\n\t\tassert.is.equal(0, bound3.zero.min.z)\n\t\tassert.is.equal(0, bound3.zero.max.x)\n\t\tassert.is.equal(0, bound3.zero.max.y)\n\t\tassert.is.equal(0, bound3.zero.max.z)\n\tend)\nend)\n"
  },
  {
    "path": "spec/color_spec.lua",
    "content": "local color = require \"modules.color\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\n\nlocal function assert_is_float_equal(a, b)\n\tif math.abs(a - b) > DBL_EPSILON then\n\t\tassert.is.equal(a, b)\n\tend\nend\n\nlocal function assert_is_approx_equal(a, b)\n\tif math.abs(a - b) > 0.001 then\n\t\tassert.is.equal(a, b)\n\tend\nend\n\n\ndescribe(\"color:\", function()\n\tit(\"operators: add, subract, multiply\", function()\n\t\tlocal c = color(1, 1, 1, 1)\n\t\tassert.is_true(c:is_color())\n\t\tlocal r = c + c\n\t\tassert.is_true(r:is_color())\n\t\tassert_is_float_equal(r[1], 2)\n\t\tassert_is_float_equal(r[2], 2)\n\t\tassert_is_float_equal(r[3], 2)\n\t\tr = c - c\n\t\tassert.is_true(r:is_color())\n\t\tassert_is_float_equal(r[1], 0)\n\t\tassert_is_float_equal(r[2], 0)\n\t\tassert_is_float_equal(r[3], 0)\n\t\tr = c * 5\n\t\tassert.is_true(r:is_color())\n\t\tassert_is_float_equal(r[1], 5)\n\t\tassert_is_float_equal(r[2], 5)\n\t\tassert_is_float_equal(r[3], 5)\n\tend)\n\n\tit(\"rgb -> hsv -> rgb\", function()\n\t\tlocal c = color(1,1,1,1)\n\t\tlocal hsv = c:color_to_hsv_table()\n\t\tlocal c1 = color.hsv_to_color_table(hsv)\n\t\tlocal c2 = color.from_hsva(hsv[1], hsv[2], hsv[3], hsv[4])\n\t\tlocal c3 = color.from_hsv(hsv[1], hsv[2], hsv[3])\n\t\tc3[4] = c[4]\n\t\tfor i=1,4 do\n\t\t\tassert_is_float_equal(c[i], c1[i])\n\t\t\tassert_is_float_equal(c[i], c2[i])\n\t\t\tassert_is_float_equal(c[i], c3[i])\n\t\tend\n\t\tassert.is_true(c:is_color())\n\t\tassert.is_true(c1:is_color())\n\t\tassert.is_true(c2:is_color())\n\t\tassert.is_true(c3:is_color())\n\tend)\n\n\tit(\"hsv -> rgb -> hsv\", function()\n\t\tlocal hsv1 = { 0, 0.3, 0.8, 0.9 }\n\t\tfor h=0,1, 0.1 do\n\t\t\thsv1[1] = h\n\t\t\tlocal cc = color.hsv_to_color_table(hsv1)\n\t\t\tlocal hsv2 = cc:color_to_hsv_table()\n\t\t\tfor i=1,4 do\n\t\t\t\tassert_is_approx_equal(hsv1[i], hsv2[i])\n\t\t\tend\n\t\tend\n\tend)\n\n\tit(\"unpack\", function()\n\t\tlocal c = color(122/255, 20/255, 122/255, 255/255)\n\t\tlocal r, g, b, a = c:unpack()\n\t\tassert_is_float_equal(c[1], r)\n\t\tassert_is_float_equal(c[2], g)\n\t\tassert_is_float_equal(c[3], b)\n\t\tassert_is_float_equal(c[4], a)\n\t\tr, g, b, a = c:as_255()\n\t\tassert_is_float_equal(122, r)\n\t\tassert_is_float_equal(20, g)\n\t\tassert_is_float_equal(122, b)\n\t\tassert_is_float_equal(255, a)\n\tend)\n\n\tit(\"set hsv\", function()\n\t\t-- hsv value conversion values from http://colorizer.org/\n\t\tlocal c = color(122/255, 20/255, 122/255, 1)\n\t\tlocal hsv = c:color_to_hsv_table()\n\t\tassert_is_approx_equal(hsv[1], 300/360)\n\t\tassert_is_approx_equal(hsv[2], 0.8361)\n\t\tassert_is_approx_equal(hsv[3], 0.4784)\n\t\tlocal r = c:hue(200/360)\n\t\tassert_is_approx_equal(r[1], 20/255)\n\t\tassert_is_approx_equal(r[2], 88/255)\n\t\tassert_is_approx_equal(r[3], 122/255)\n\t\tr = c:saturation(0.2)\n\t\tassert_is_approx_equal(r[1], 122/255)\n\t\tassert_is_approx_equal(r[2], 97.6/255)\n\t\tassert_is_approx_equal(r[3], 122/255)\n\t\tr = c:value(0.2)\n\t\tassert_is_approx_equal(r[1], 51/255)\n\t\tassert_is_approx_equal(r[2], 8.36/255)\n\t\tassert_is_approx_equal(r[3], 51/255)\n\tend)\n\n\tit(\"lighten a color\", function()\n\t\tlocal c = color(0, 0, 0, 0)\n\t\tlocal r = c:lighten(0.1)\n\t\tassert.is.equal(r[1], 0.1)\n\t\tr = c:lighten(1000)\n\t\tassert.is.equal(r[1], 1)\n\tend)\n\n\tit(\"darken a color\", function()\n\t\tlocal c = color(1, 1, 1, 1)\n\t\tlocal r = c:darken(0.04)\n\t\tassert.is.equal(r[1], 0.96)\n\t\tr = c:darken(1000)\n\t\tassert.is.equal(r[1], 0)\n\tend)\n\n\tit(\"multiply a color by a scalar\", function()\n\t\tlocal c = color(1, 1, 1, 1)\n\t\tlocal r = c:multiply(0.04)\n\t\tassert.is.equal(r[1], 0.04)\n\n\t\tr = c:multiply(0)\n\t\tfor i=1,3 do\n\t\t\tassert.is.equal(0, r[i])\n\t\tend\n\t\tassert.is.equal(1, r[4])\n\tend)\n\n\tit(\"modify alpha\", function()\n\t\tlocal c = color(1, 1, 1, 1)\n\t\tlocal r = c:alpha(0.1)\n\t\tassert.is.equal(r[4], 0.1)\n\t\tr = c:opacity(0.5)\n\t\tassert.is.equal(r[4], 0.5)\n\t\tr = c:opacity(0.5)\n\t\t\t:opacity(0.5)\n\t\tassert.is.equal(r[4], 0.25)\n\tend)\n\n\tit(\"invert\", function()\n\t\tlocal c = color(1, 0.6, 0.25, 1)\n\t\tlocal r = c:invert()\n\t\tassert_is_float_equal(r[1], 0)\n\t\tassert_is_float_equal(r[2], 0.4)\n\t\tassert_is_float_equal(r[3], 0.75)\n\t\tassert_is_float_equal(r[4], 1)\n\t\tr = c:invert()\n\t\t\t:invert()\n\t\tfor i=1,4 do\n\t\t\tassert.is.equal(c[i], r[i])\n\t\tend\n\tend)\n\n\tit(\"lerp\", function()\n\t\tlocal a = color(1, 0.6, 0.25, 1)\n\t\tlocal b = color(1, 0.8, 0.75, 0.5)\n\t\tlocal r = a:lerp(b, 0.5)\n\t\tassert_is_float_equal(r[1], 1)\n\t\tassert_is_float_equal(r[2], 0.7)\n\t\tassert_is_float_equal(r[3], 0.5)\n\t\tassert_is_float_equal(r[4], 0.75)\n\t\tlocal r_a = a:lerp(b, 0)\n\t\tlocal r_b = a:lerp(b, 1)\n\t\tfor i=1,4 do\n\t\t\tassert.is.equal(a[i], r_a[i])\n\t\t\tassert.is.equal(b[i], r_b[i])\n\t\tend\n\tend)\n\n\tit(\"linear_to_gamma -> gamma_to_linear round trip\", function()\n\t\tlocal c = color(0.25, 0.25, 0.25, 1)\n\t\tlocal r = color.gamma_to_linear(c:linear_to_gamma())\n\t\tfor i=1,4 do\n\t\t\tassert_is_approx_equal(c[i], r[i])\n\t\tend\n\tend)\n\nend)\n\n--[[\nto_string(a)\n--]]\n"
  },
  {
    "path": "spec/intersect_spec.lua",
    "content": "local intersect = require \"modules.intersect\"\nlocal vec3      = require \"modules.vec3\"\nlocal mat4      = require \"modules.mat4\"\n\ndescribe(\"intersect:\", function()\n\tit(\"intersects a point with a triangle\", function()\n\t\tlocal a = vec3()\n\t\tlocal b = vec3(0, 0, 5)\n\t\tlocal c = {\n\t\t\tvec3(-1,  -1, 0),\n\t\t\tvec3( 1,  -1, 0),\n\t\t\tvec3( 0.5, 1, 0)\n\t\t}\n\t\tassert.is_true(intersect.point_triangle(a, c))\n\t\tassert.is_not_true(intersect.point_triangle(b, c))\n\tend)\n\n\tit(\"intersects a point with an aabb\", function()\n\t\tlocal a = vec3()\n\t\tlocal b = vec3(0, 0, 5)\n\t\tlocal c = {\n\t\t\tmin = vec3(-1),\n\t\t\tmax = vec3( 1)\n\t\t}\n\t\tassert.is_true(intersect.point_aabb(a, c))\n\t\tassert.is_not_true(intersect.point_aabb(b, c))\n\tend)\n\n\tit(\"intersects a point with a frustum\", function()\n\t\tpending(\"TODO\")\n\tend)\n\n\tit(\"intersects a ray with a triangle\", function()\n\t\tlocal a = {\n\t\t\tposition  = vec3(0.5, 0.5, -1),\n\t\t\tdirection = vec3(0,   0,    1)\n\t\t}\n\t\tlocal b = {\n\t\t\tposition  = vec3(0.5, 0.5, -1),\n\t\t\tdirection = vec3(0,   0,   -1)\n\t\t}\n\t\tlocal c = {\n\t\t\tvec3(-1,  -1, 0),\n\t\t\tvec3( 1,  -1, 0),\n\t\t\tvec3( 0.5, 1, 0)\n\t\t}\n\t\tassert.is_true(vec3.is_vec3(intersect.ray_triangle(a, c)))\n\t\tassert.is_not_true(intersect.ray_triangle(b, c))\n\tend)\n\n\tit(\"intersects a ray with a sphere\", function()\n\t\tlocal a = {\n\t\t\tposition  = vec3(0, 0, -2),\n\t\t\tdirection = vec3(0, 0,  1)\n\t\t}\n\t\tlocal b = {\n\t\t\tposition  = vec3(0, 0, -2),\n\t\t\tdirection = vec3(0, 0, -1)\n\t\t}\n\t\tlocal c = {\n\t\t\tposition = vec3(),\n\t\t\tradius   = 1\n\t\t}\n\n\t\tlocal w, x = intersect.ray_sphere(a, c)\n\t\tlocal y, z = intersect.ray_sphere(b, c)\n\t\tassert.is_true(vec3.is_vec3(w))\n\t\tassert.is_not_true(y)\n\tend)\n\n\tit(\"intersects a ray with an aabb\", function()\n\t\tlocal a = {\n\t\t\tposition  = vec3(0, 0, -2),\n\t\t\tdirection = vec3(0, 0,  1)\n\t\t}\n\t\tlocal b = {\n\t\t\tposition  = vec3(0, 0, -2),\n\t\t\tdirection = vec3(0, 0, -1)\n\t\t}\n\t\tlocal c = {\n\t\t\tmin = vec3(-1),\n\t\t\tmax = vec3( 1)\n\t\t}\n\n\t\tlocal w, x = intersect.ray_aabb(a, c)\n\t\tlocal y, z = intersect.ray_aabb(b, c)\n\t\tassert.is_true(vec3.is_vec3(w))\n\t\tassert.is_not_true(y)\n\tend)\n\n\tit(\"intersects a ray with a plane\", function()\n\t\tlocal a = {\n\t\t\tposition  = vec3(0, 0,  1),\n\t\t\tdirection = vec3(0, 0, -1)\n\t\t}\n\t\tlocal b = {\n\t\t\tposition  = vec3(0, 0, 1),\n\t\t\tdirection = vec3(0, 0, 1)\n\t\t}\n\t\tlocal c = {\n\t\t\tposition = vec3(),\n\t\t\tnormal   = vec3(0, 0, 1)\n\t\t}\n\n\t\tlocal w, x = intersect.ray_plane(a, c)\n\t\tlocal y, z = intersect.ray_plane(b, c)\n\t\tassert.is_true(vec3.is_vec3(w))\n\t\tassert.is_not_true(y)\n\tend)\n\n\tit(\"intersects a line with a line\", function()\n\t\tlocal a = {\n\t\t\tvec3(0, 0, -1),\n\t\t\tvec3(0, 0,  1)\n\t\t}\n\t\tlocal b = {\n\t\t\tvec3(0, 0, -1),\n\t\t\tvec3(0, 1, -1)\n\t\t}\n\t\tlocal c = {\n\t\t\tvec3(-1, 0, 0),\n\t\t\tvec3( 1, 0, 0)\n\t\t}\n\n\t\tlocal w, x = intersect.line_line(a, c, 0.001)\n\t\tlocal y, z = intersect.line_line(b, c, 0.001)\n\t\tlocal u, v = intersect.line_line(b, c)\n\t\tassert.is_truthy(w)\n\t\tassert.is_not_truthy(y)\n\t\tassert.is_truthy(u)\n\tend)\n\n\tit(\"intersects a segment with a segment\", function()\n\t\tlocal a = {\n\t\t\tvec3(0, 0, -1),\n\t\t\tvec3(0, 0,  1)\n\t\t}\n\t\tlocal b = {\n\t\t\tvec3(0, 0, -1),\n\t\t\tvec3(0, 1, -1)\n\t\t}\n\t\tlocal c = {\n\t\t\tvec3(-1, 0, 0),\n\t\t\tvec3( 1, 0, 0)\n\t\t}\n\n\t\tlocal w, x = intersect.segment_segment(a, c, 0.001)\n\t\tlocal y, z = intersect.segment_segment(b, c, 0.001)\n\t\tlocal u, v = intersect.segment_segment(b, c)\n\t\tassert.is_truthy(w)\n\t\tassert.is_not_truthy(y)\n\t\tassert.is_truthy(u)\n\tend)\n\n\tit(\"intersects an aabb with an aabb\", function()\n\t\tlocal a = {\n\t\t\tmin = vec3(-1),\n\t\t\tmax = vec3( 1)\n\t\t}\n\t\tlocal b = {\n\t\t\tmin = vec3(-5),\n\t\t\tmax = vec3(-3)\n\t\t}\n\t\tlocal c = {\n\t\t\tmin = vec3(),\n\t\t\tmax = vec3(2)\n\t\t}\n\t\tassert.is_true(intersect.aabb_aabb(a, c))\n\t\tassert.is_not_true(intersect.aabb_aabb(b, c))\n\tend)\n\n\tit(\"intersects an aabb with an obb\", function()\n\t\tlocal r = mat4():rotate(mat4(), math.pi / 4, vec3.unit_z)\n\n\t\tlocal a = {\n\t\t\tposition = vec3(),\n\t\t\textent   = vec3(0.5)\n\t\t}\n\t\tlocal b = {\n\t\t\tposition = vec3(),\n\t\t\textent   = vec3(0.5),\n\t\t\trotation = r\n\t\t}\n\t\tlocal c = {\n\t\t\tposition = vec3(0, 0, 2),\n\t\t\textent   = vec3(0.5),\n\t\t\trotation = r\n\t\t}\n\t\tassert.is_true(vec3.is_vec3(intersect.aabb_obb(a, b)))\n\t\tassert.is_not_true(intersect.aabb_obb(a, c))\n\tend)\n\n\tit(\"intersects an aabb with a sphere\", function()\n\t\tlocal a = {\n\t\t\tmin = vec3(-1),\n\t\t\tmax = vec3( 1)\n\t\t}\n\t\tlocal b = {\n\t\t\tmin = vec3(-5),\n\t\t\tmax = vec3(-3)\n\t\t}\n\t\tlocal c = {\n\t\t\tposition = vec3(0, 0, 3),\n\t\t\tradius   = 3\n\t\t}\n\t\tassert.is_true(intersect.aabb_sphere(a, c))\n\t\tassert.is_not_true(intersect.aabb_sphere(b, c))\n\tend)\n\n\tit(\"intersects an aabb with a frustum\", function()\n\t\tpending(\"TODO\")\n\tend)\n\n\tit(\"encapsulates an aabb\", function()\n\t\tlocal a = {\n\t\t\tmin = vec3(-1),\n\t\t\tmax = vec3( 1)\n\t\t}\n\t\tlocal b = {\n\t\t\tmin = vec3(-1.5),\n\t\t\tmax = vec3( 1.5)\n\t\t}\n\t\tlocal c = {\n\t\t\tmin = vec3(-0.5),\n\t\t\tmax = vec3( 0.5)\n\t\t}\n\t\tlocal d = {\n\t\t\tmin = vec3(-1),\n\t\t\tmax = vec3( 1)\n\t\t}\n\t\tassert.is_true(intersect.encapsulate_aabb(a, d))\n\t\tassert.is_true(intersect.encapsulate_aabb(b, d))\n\t\tassert.is_not_true(intersect.encapsulate_aabb(c, d))\n\tend)\n\n\tit(\"intersects a circle with a circle\", function()\n\t\tlocal a = {\n\t\t\tposition = vec3(0, 0, 6),\n\t\t\tradius   = 3\n\t\t}\n\t\tlocal b = {\n\t\t\tposition = vec3(0, 0, 7),\n\t\t\tradius   = 3\n\t\t}\n\t\tlocal c = {\n\t\t\tposition = vec3(),\n\t\t\tradius   = 3\n\t\t}\n\t\tassert.is_true(intersect.circle_circle(a, c))\n\t\tassert.is_not_true(intersect.circle_circle(b, c))\n\tend)\n\n\tit(\"intersects a sphere with a sphere\", function()\n\t\tlocal a = {\n\t\t\tposition = vec3(0, 0, 6),\n\t\t\tradius   = 3\n\t\t}\n\t\tlocal b = {\n\t\t\tposition = vec3(0, 0, 7),\n\t\t\tradius   = 3\n\t\t}\n\t\tlocal c = {\n\t\t\tposition = vec3(),\n\t\t\tradius   = 3\n\t\t}\n\t\tassert.is_true(intersect.sphere_sphere(a, c))\n\t\tassert.is_not_true(intersect.sphere_sphere(b, c))\n\tend)\n\n\tit(\"intersects a sphere with a frustum\", function()\n\t\tpending(\"TODO\")\n\tend)\n\n\tit(\"intersects a capsule with another capsule\", function()\n\t\tpending(\"TODO\")\n\tend)\nend)\n"
  },
  {
    "path": "spec/mat4_spec.lua",
    "content": "local mat4        = require \"modules.mat4\"\nlocal vec3        = require \"modules.vec3\"\nlocal quat        = require \"modules.quat\"\nlocal utils       = require \"modules.utils\"\nlocal FLT_EPSILON = require(\"modules.constants\").FLT_EPSILON\n\ndescribe(\"mat4:\", function()\n\tit(\"creates an identity matrix\", function()\n\t\tlocal a = mat4()\n\t\tassert.is.equal(1, a[1])\n\t\tassert.is.equal(0, a[2])\n\t\tassert.is.equal(0, a[3])\n\t\tassert.is.equal(0, a[4])\n\t\tassert.is.equal(0, a[5])\n\t\tassert.is.equal(1, a[6])\n\t\tassert.is.equal(0, a[7])\n\t\tassert.is.equal(0, a[8])\n\t\tassert.is.equal(0, a[9])\n\t\tassert.is.equal(0, a[10])\n\t\tassert.is.equal(1, a[11])\n\t\tassert.is.equal(0, a[12])\n\t\tassert.is.equal(0, a[13])\n\t\tassert.is.equal(0, a[14])\n\t\tassert.is.equal(0, a[15])\n\t\tassert.is.equal(1, a[16])\n\t\tassert.is_true(a:is_mat4())\n\tend)\n\n\tit(\"creates a filled matrix\", function()\n\t\tlocal a = mat4 {\n\t\t\t3, 3, 3, 3,\n\t\t\t4, 4, 4, 4,\n\t\t\t5, 5, 5, 5,\n\t\t\t6, 6, 6, 6\n\t\t}\n\t\tassert.is.equal(3, a[1])\n\t\tassert.is.equal(3, a[2])\n\t\tassert.is.equal(3, a[3])\n\t\tassert.is.equal(3, a[4])\n\t\tassert.is.equal(4, a[5])\n\t\tassert.is.equal(4, a[6])\n\t\tassert.is.equal(4, a[7])\n\t\tassert.is.equal(4, a[8])\n\t\tassert.is.equal(5, a[9])\n\t\tassert.is.equal(5, a[10])\n\t\tassert.is.equal(5, a[11])\n\t\tassert.is.equal(5, a[12])\n\t\tassert.is.equal(6, a[13])\n\t\tassert.is.equal(6, a[14])\n\t\tassert.is.equal(6, a[15])\n\t\tassert.is.equal(6, a[16])\n\tend)\n\n\tit(\"creates a filled matrix from vec4s\", function()\n\t\tlocal a = mat4 {\n\t\t\t{ 3, 3, 3, 3 },\n\t\t\t{ 4, 4, 4, 4 },\n\t\t\t{ 5, 5, 5, 5 },\n\t\t\t{ 6, 6, 6, 6 }\n\t\t}\n\t\tassert.is.equal(3, a[1])\n\t\tassert.is.equal(3, a[2])\n\t\tassert.is.equal(3, a[3])\n\t\tassert.is.equal(3, a[4])\n\t\tassert.is.equal(4, a[5])\n\t\tassert.is.equal(4, a[6])\n\t\tassert.is.equal(4, a[7])\n\t\tassert.is.equal(4, a[8])\n\t\tassert.is.equal(5, a[9])\n\t\tassert.is.equal(5, a[10])\n\t\tassert.is.equal(5, a[11])\n\t\tassert.is.equal(5, a[12])\n\t\tassert.is.equal(6, a[13])\n\t\tassert.is.equal(6, a[14])\n\t\tassert.is.equal(6, a[15])\n\t\tassert.is.equal(6, a[16])\n\tend)\n\n\tit(\"creates a filled matrix from a 3x3 matrix\", function()\n\t\tlocal a = mat4 {\n\t\t\t3, 3, 3,\n\t\t\t4, 4, 4,\n\t\t\t5, 5, 5\n\t\t}\n\t\tassert.is.equal(3, a[1])\n\t\tassert.is.equal(3, a[2])\n\t\tassert.is.equal(3, a[3])\n\t\tassert.is.equal(0, a[4])\n\t\tassert.is.equal(4, a[5])\n\t\tassert.is.equal(4, a[6])\n\t\tassert.is.equal(4, a[7])\n\t\tassert.is.equal(0, a[8])\n\t\tassert.is.equal(5, a[9])\n\t\tassert.is.equal(5, a[10])\n\t\tassert.is.equal(5, a[11])\n\t\tassert.is.equal(0, a[12])\n\t\tassert.is.equal(0, a[13])\n\t\tassert.is.equal(0, a[14])\n\t\tassert.is.equal(0, a[15])\n\t\tassert.is.equal(1, a[16])\n\tend)\n\n\tit(\"creates a matrix from perspective\", function()\n\t\tlocal a = mat4.from_perspective(45, 1, 0.1, 1000)\n\t\tassert.is_true(utils.tolerance( 2.414-a[1],  0.001))\n\t\tassert.is_true(utils.tolerance( 2.414-a[6],  0.001))\n\t\tassert.is_true(utils.tolerance(-1    -a[11], 0.001))\n\t\tassert.is_true(utils.tolerance(-1    -a[12], 0.001))\n\t\tassert.is_true(utils.tolerance(-0.2  -a[15], 0.001))\n\tend)\n\n\tit(\"creates a matrix from HMD perspective\", function()\n\t\tlocal t = {\n\t\t\tLeftTan  = 2.3465312,\n\t\t\tRightTan = 0.9616399,\n\t\t\tUpTan    = 2.8664987,\n\t\t\tDownTan  = 2.8664987\n\t\t}\n\t\tlocal a = mat4.from_hmd_perspective(t, 0.1, 1000, false, false)\n\t\tassert.is_true(utils.tolerance(a[1] -  0.605, 0.001))\n\t\tassert.is_true(utils.tolerance(a[6] -  0.349, 0.001))\n\t\tassert.is_true(utils.tolerance(a[9] - -0.419, 0.001))\n\t\tassert.is_true(utils.tolerance(a[11]- -1.000, 0.001))\n\t\tassert.is_true(utils.tolerance(a[12]- -1.000, 0.001))\n\t\tassert.is_true(utils.tolerance(a[15]- -0.200, 0.001))\n\tend)\n\n\tit(\"clones a matrix\", function()\n\t\tlocal a = mat4.identity()\n\t\tlocal b = a:clone()\n\t\tassert.is.equal(a, b)\n\tend)\n\n\tit(\"multiplies two 4x4 matrices\", function()\n\t\tlocal a = mat4 {\n\t\t\t1, 5, 9,  13,\n\t\t\t2, 6, 10, 14,\n\t\t\t3, 7, 11, 15,\n\t\t\t4, 8, 12, 16\n\t\t}\n\t\tlocal b = mat4 {\n\t\t\t1,  2,  3,  4,\n\t\t\t5,  6,  7,  8,\n\t\t\t9,  10, 11, 12,\n\t\t\t13, 14, 15, 16\n\t\t}\n\t\tlocal c = mat4():mul(a, b)\n\t\tlocal d = a * b\n\t\tlocal e = mat4():mul{a, b}\n\t\tassert.is.equal(30,  c[1])\n\t\tassert.is.equal(70,  c[2])\n\t\tassert.is.equal(110, c[3])\n\t\tassert.is.equal(150, c[4])\n\t\tassert.is.equal(70,  c[5])\n\t\tassert.is.equal(174, c[6])\n\t\tassert.is.equal(278, c[7])\n\t\tassert.is.equal(382, c[8])\n\t\tassert.is.equal(110, c[9])\n\t\tassert.is.equal(278, c[10])\n\t\tassert.is.equal(446, c[11])\n\t\tassert.is.equal(614, c[12])\n\t\tassert.is.equal(150, c[13])\n\t\tassert.is.equal(382, c[14])\n\t\tassert.is.equal(614, c[15])\n\t\tassert.is.equal(846, c[16])\n\t\tassert.is.equal(c, d)\n\t\tassert.is.equal(c, e)\n\tend)\n\n\tit(\"multiplies a matrix and a vec4\", function()\n\t\tlocal a = mat4 {\n\t\t\t1,  2,  3,  4,\n\t\t\t5,  6,  7,  8,\n\t\t\t9,  10, 11, 12,\n\t\t\t13, 14, 15, 16\n\t\t}\n\t\tlocal b = { 10, 20, 30, 40 }\n\t\tlocal c = mat4.mul_vec4(mat4(), a, b)\n\t\tlocal d = a * b\n\t\tassert.is.equal(900,  c[1])\n\t\tassert.is.equal(1000, c[2])\n\t\tassert.is.equal(1100, c[3])\n\t\tassert.is.equal(1200, c[4])\n\n\t\tassert.is.equal(c[1], d[1])\n\t\tassert.is.equal(c[2], d[2])\n\t\tassert.is.equal(c[3], d[3])\n\t\tassert.is.equal(c[4], d[4])\n\tend)\n\n\tit(\"verifies mat4 composition order\", function()\n\t\tlocal a = mat4 {\n\t\t\t1, 5, 9,  13,\n\t\t\t2, 6, 10, 14,\n\t\t\t3, 7, 11, 15,\n\t\t\t4, 8, 12, 16\n\t\t}\n\t\tlocal b = mat4 {\n\t\t\t2,  3,  5,  7,\n\t\t\t11, 13, 17, 19,\n\t\t\t23, 29, 31, 37,\n\t\t\t41, 43, 47, 53\n\t\t}\n\t\tlocal c = mat4():mul(a, b)\n\t\tlocal d = a * b\n\n\t\tlocal v = { 10, 20, 30, 40 }\n\n\t\tlocal cv = c * v\n\t\tlocal abv = a*(b*v)\n\n\t\tassert.is.equal(cv.x, abv.x) -- Verify (a*b)*v == a*(b*v)\n\t\tassert.is.equal(cv.y, abv.y)\n\t\tassert.is.equal(cv.z, abv.z)\n\tend)\n\n\tit(\"scales a matrix\", function()\n\t\tlocal a = mat4():scale(mat4(), vec3(5, 5, 5))\n\t\tassert.is.equal(5, a[1])\n\t\tassert.is.equal(5, a[6])\n\t\tassert.is.equal(5, a[11])\n\tend)\n\n\tit(\"rotates a matrix\", function()\n\t\tlocal a = mat4():rotate(mat4(), math.rad(45), vec3.unit_z)\n\t\tassert.is_true(utils.tolerance( 0.7071-a[1], 0.001))\n\t\tassert.is_true(utils.tolerance( 0.7071-a[2], 0.001))\n\t\tassert.is_true(utils.tolerance(-0.7071-a[5], 0.001))\n\t\tassert.is_true(utils.tolerance( 0.7071-a[6], 0.001))\n\tend)\n\n\tit(\"translates a matrix\", function()\n\t\tlocal a = mat4():translate(mat4(), vec3(5, 5, 5))\n\t\tassert.is.equal(5, a[13])\n\t\tassert.is.equal(5, a[14])\n\t\tassert.is.equal(5, a[15])\n\tend)\n\n\tit(\"inverts a matrix\", function()\n\t\tlocal a = mat4()\n\t\ta = a:rotate(a, math.pi/4, vec3.unit_y)\n\t\ta = a:translate(a, vec3(4, 5, 6))\n\n\t\tlocal b = mat4.invert(mat4(), a)\n\t\tlocal c = a * b\n\t\tassert.is.equal(mat4(), c)\n\n\t\tlocal d = mat4()\n\t\td:rotate(d, math.pi/4, vec3.unit_y)\n\t\td:translate(d, vec3(4, 5, 6))\n\n\t\tlocal e = -d\n\t\tlocal f = d * e\n\t\tassert.is.equal(mat4(), f)\n\tend)\n\n\tit(\"transposes a matrix\", function()\n\t\tlocal a = mat4({\n\t\t\t1, 1, 1, 1,\n\t\t\t2, 2, 2, 2,\n\t\t\t3, 3, 3, 3,\n\t\t\t4, 4, 4, 4\n\t\t})\n\t\ta = a:transpose(a)\n\t\tassert.is.equal(1, a[1])\n\t\tassert.is.equal(2, a[2])\n\t\tassert.is.equal(3, a[3])\n\t\tassert.is.equal(4, a[4])\n\t\tassert.is.equal(1, a[5])\n\t\tassert.is.equal(2, a[6])\n\t\tassert.is.equal(3, a[7])\n\t\tassert.is.equal(4, a[8])\n\t\tassert.is.equal(1, a[9])\n\t\tassert.is.equal(2, a[10])\n\t\tassert.is.equal(3, a[11])\n\t\tassert.is.equal(4, a[12])\n\t\tassert.is.equal(1, a[13])\n\t\tassert.is.equal(2, a[14])\n\t\tassert.is.equal(3, a[15])\n\t\tassert.is.equal(4, a[16])\n\tend)\n\n\tit(\"shears a matrix\", function()\n\t\tlocal yx, zx, xy, zy, xz, yz = 1, 1, 1, -1, -1, -1\n\t\tlocal a = mat4():shear(mat4(), yx, zx, xy, zy, xz, yz)\n\t\tassert.is.equal( 1, a[2])\n\t\tassert.is.equal( 1, a[3])\n\t\tassert.is.equal( 1, a[5])\n\t\tassert.is.equal(-1, a[7])\n\t\tassert.is.equal(-1, a[9])\n\t\tassert.is.equal(-1, a[10])\n\tend)\n\n\tit(\"reflects a matrix along a plane\", function()\n\t\tlocal origin = vec3(5, 1, 0)\n\t\tlocal normal = vec3(0, -1, 0):normalize()\n\t\tlocal a = mat4():reflect(mat4(), origin, normal)\n\t\tlocal p = a * vec3(-5, 2, 5)\n\t\tassert.is.equal(p.x, -5)\n\t\tassert.is.equal(p.y, 0)\n\t\tassert.is.equal(p.z, 5)\n\tend)\n\n\tit(\"projects a point into screen space\", function()\n\t\tlocal znear = 0.1\n\t\tlocal zfar = 1000\n\t\tlocal proj = mat4.from_perspective(45, 1, znear, zfar)\n\t\tlocal vp = { 0, 0, 400, 400 }\n\n\t\t-- -z is away from the viewer into the far plane\n\t\tlocal p1 = vec3(0, 0, -znear)\n\t\tlocal c1 = mat4.project(p1, proj, vp)\n\t\tassert.is.near(0, c1.z, 0.0001)\n\t\tassert.is.equal(200, c1.x)\n\t\tassert.is.equal(200, c1.y)\n\n\t\tlocal p2 = vec3(0, 0, -zfar)\n\t\tlocal c2 = mat4.project(p2, proj, vp)\n\t\tassert.is.near(1, c2.z, 0.0001)\n\t\tassert.is.equal(200, c2.x)\n\t\tassert.is.equal(200, c2.y)\n\n\t\tlocal p3 = vec3(0, 0, zfar)\n\t\tlocal c3 = mat4.project(p3, proj, vp)\n\t\tassert.is_true(c3.z < 0)\n\t\tassert.is.equal(200, c3.x)\n\t\tassert.is.equal(200, c3.y)\n\tend)\n\n\tit(\"unprojects a point into world space\", function()\n\t\tlocal p  = vec3(0, 0, -10)\n\t\tlocal proj = mat4.from_perspective(45, 1, 0.1, 1000)\n\t\tlocal vp = { 0, 0, 400, 400 }\n\t\tlocal c  = mat4.project(p, proj, vp)\n\t\tlocal d  = mat4.unproject(c, proj, vp)\n\t\tassert.is.near(0.0, p.x-d.x, 0.0001)\n\t\tassert.is.near(0.0, p.y-d.y, 0.0001)\n\t\tassert.is.near(0.0, p.z-d.z, 0.0001)\n\tend)\n\n\tit(\"transforms a matrix to look at a point\", function()\n\t\tlocal e = vec3(0, 0, 1.55)\n\t\tlocal c = vec3(4, 7, 1)\n\t\tlocal u = vec3(0, 0, 1)\n\t\tlocal a = mat4():look_at(e, c, u)\n\n\t\tassert.is_true(utils.tolerance( 0.868-a[1], 0.001))\n\t\tassert.is_true(utils.tolerance( 0.034-a[2], 0.001))\n\t\tassert.is_true(utils.tolerance(-0.495-a[3], 0.001))\n\t\tassert.is_true(utils.tolerance( 0    -a[4], 0.001))\n\n\t\tassert.is_true(utils.tolerance(-0.496-a[5], 0.001))\n\t\tassert.is_true(utils.tolerance( 0.059-a[6], 0.001))\n\t\tassert.is_true(utils.tolerance(-0.866-a[7], 0.001))\n\t\tassert.is_true(utils.tolerance( 0    -a[8], 0.001))\n\n\t\tassert.is_true(utils.tolerance( 0    -a[9],  0.001))\n\t\tassert.is_true(utils.tolerance( 0.998-a[10], 0.001))\n\t\tassert.is_true(utils.tolerance( 0.068-a[11], 0.001))\n\t\tassert.is_true(utils.tolerance( 0    -a[12], 0.001))\n\n\t\tassert.is_true(utils.tolerance( 0    -a[13], 0.001))\n\t\tassert.is_true(utils.tolerance(-1.546-a[14], 0.001))\n\t\tassert.is_true(utils.tolerance(-0.106-a[15], 0.001))\n\t\tassert.is_true(utils.tolerance( 1    -a[16], 0.001))\n\tend)\n\n\tit(\"converts a matrix to vec4s\", function()\n\t\tlocal a = mat4 {\n\t\t\t1,  2,  3,  4,\n\t\t\t5,  6,  7,  8,\n\t\t\t9,  10, 11, 12,\n\t\t\t13, 14, 15, 16\n\t\t}\n\t\tlocal v = a:to_vec4s()\n\t\tassert.is_true(type(v)    == \"table\")\n\t\tassert.is_true(type(v[1]) == \"table\")\n\t\tassert.is_true(type(v[2]) == \"table\")\n\t\tassert.is_true(type(v[3]) == \"table\")\n\t\tassert.is_true(type(v[4]) == \"table\")\n\n\t\tassert.is.equal(1,  v[1][1])\n\t\tassert.is.equal(2,  v[1][2])\n\t\tassert.is.equal(3,  v[1][3])\n\t\tassert.is.equal(4,  v[1][4])\n\n\t\tassert.is.equal(5,  v[2][1])\n\t\tassert.is.equal(6,  v[2][2])\n\t\tassert.is.equal(7,  v[2][3])\n\t\tassert.is.equal(8,  v[2][4])\n\n\t\tassert.is.equal(9,  v[3][1])\n\t\tassert.is.equal(10, v[3][2])\n\t\tassert.is.equal(11, v[3][3])\n\t\tassert.is.equal(12, v[3][4])\n\n\t\tassert.is.equal(13, v[4][1])\n\t\tassert.is.equal(14, v[4][2])\n\t\tassert.is.equal(15, v[4][3])\n\t\tassert.is.equal(16, v[4][4])\n\tend)\n\n\tit(\"converts a matrix to vec4s, column-wise\", function()\n\t\tlocal a = mat4 {\n\t\t\t1,  2,  3,  4,\n\t\t\t5,  6,  7,  8,\n\t\t\t9,  10, 11, 12,\n\t\t\t13, 14, 15, 16\n\t\t}\n\t\tlocal v = a:to_vec4s_cols()\n\t\tassert.is_true(type(v)    == \"table\")\n\t\tassert.is_true(type(v[1]) == \"table\")\n\t\tassert.is_true(type(v[2]) == \"table\")\n\t\tassert.is_true(type(v[3]) == \"table\")\n\t\tassert.is_true(type(v[4]) == \"table\")\n\n\t\tassert.is.equal(1,  v[1][1])\n\t\tassert.is.equal(5,  v[1][2])\n\t\tassert.is.equal(9,  v[1][3])\n\t\tassert.is.equal(13, v[1][4])\n\n\t\tassert.is.equal(2,  v[2][1])\n\t\tassert.is.equal(6,  v[2][2])\n\t\tassert.is.equal(10, v[2][3])\n\t\tassert.is.equal(14, v[2][4])\n\n\t\tassert.is.equal(3,  v[3][1])\n\t\tassert.is.equal(7,  v[3][2])\n\t\tassert.is.equal(11, v[3][3])\n\t\tassert.is.equal(15, v[3][4])\n\n\t\tassert.is.equal(4,  v[4][1])\n\t\tassert.is.equal(8,  v[4][2])\n\t\tassert.is.equal(12, v[4][3])\n\t\tassert.is.equal(16, v[4][4])\n\tend)\n\n\tit(\"converts a matrix to a quaternion\", function()\n\t\tlocal q = mat4({\n\t\t\t0, 0, 1, 0,\n\t\t\t1, 0, 0, 0,\n\t\t\t0, 1, 0, 0,\n\t\t\t0, 0, 0, 0\n\t\t}):to_quat()\n\t\tassert.is.equal(-0.5, q.x)\n\t\tassert.is.equal(-0.5, q.y)\n\t\tassert.is.equal(-0.5, q.z)\n\t\tassert.is.equal( 0.5, q.w)\n\tend)\n\n\tit(\"converts a matrix to a frustum\", function()\n\t\tlocal a = mat4()\n\t\tlocal b = mat4.from_perspective(45, 1, 0.1, 1000)\n\t\tlocal f = (b * a):to_frustum()\n\n\t\tassert.is_true(utils.tolerance( 0.9239-f.left.a, 0.001))\n\t\tassert.is_true(utils.tolerance( 0     -f.left.b, 0.001))\n\t\tassert.is_true(utils.tolerance(-0.3827-f.left.c, 0.001))\n\t\tassert.is_true(utils.tolerance( 0     -f.left.d, 0.001))\n\n\t\tassert.is_true(utils.tolerance(-0.9239-f.right.a, 0.001))\n\t\tassert.is_true(utils.tolerance( 0     -f.right.b, 0.001))\n\t\tassert.is_true(utils.tolerance(-0.3827-f.right.c, 0.001))\n\t\tassert.is_true(utils.tolerance( 0     -f.right.d, 0.001))\n\n\t\tassert.is_true(utils.tolerance( 0     -f.bottom.a, 0.001))\n\t\tassert.is_true(utils.tolerance( 0.9239-f.bottom.b, 0.001))\n\t\tassert.is_true(utils.tolerance(-0.3827-f.bottom.c, 0.001))\n\t\tassert.is_true(utils.tolerance( 0     -f.bottom.d, 0.001))\n\n\t\tassert.is_true(utils.tolerance( 0     -f.top.a, 0.001))\n\t\tassert.is_true(utils.tolerance(-0.9239-f.top.b, 0.001))\n\t\tassert.is_true(utils.tolerance(-0.3827-f.top.c, 0.001))\n\t\tassert.is_true(utils.tolerance( 0     -f.top.d, 0.001))\n\n\t\tassert.is_true(utils.tolerance( 0  -f.near.a, 0.001))\n\t\tassert.is_true(utils.tolerance( 0  -f.near.b, 0.001))\n\t\tassert.is_true(utils.tolerance(-1  -f.near.c, 0.001))\n\t\tassert.is_true(utils.tolerance(-0.1-f.near.d, 0.001))\n\n\t\tassert.is_true(utils.tolerance( 0   -f.far.a, 0.001))\n\t\tassert.is_true(utils.tolerance( 0   -f.far.b, 0.001))\n\t\tassert.is_true(utils.tolerance( 1   -f.far.c, 0.001))\n\t\tassert.is_true(utils.tolerance( 1000-f.far.d, 0.001))\n\tend)\n\n\tit(\"checks to see if data is a valid matrix (not a table)\", function()\n\t\tassert.is_not_true(mat4.is_mat4(0))\n\tend)\n\n\tit(\"checks to see if data is a valid matrix (invalid data)\", function()\n\t\tassert.is_not_true(mat4.is_mat4({}))\n\tend)\n\n\tit(\"gets a string representation of a matrix\", function()\n\t\tlocal a = mat4():to_string()\n\t\tlocal z = \"+0.000\"\n\t\tlocal o = \"+1.000\"\n\t\tlocal s = string.format(\n\t\t\t\"[ %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s ]\",\n\t\t\to, z, z, z, z, o, z, z, z, z, o, z, z, z ,z, o\n\t\t)\n\t\tassert.is.equal(s, a)\n\tend)\n\n\tit(\"creates a matrix out of transform values\", function()\n\t\tlocal scale = vec3(1, 2, 3)\n\t\tlocal rot = quat.from_angle_axis(math.pi * 0.5, vec3(0, 1, 0))\n\t\tlocal trans = vec3(3, 4, 5)\n\t\tlocal a = mat4.from_transform(trans, rot, scale)\n\n\t\tlocal v = vec3(-2, 3, 4)\n\t\t-- scaled, rotated, then translated\n\t\t-- v * mT * mR * mS\n\n\t\tlocal result = a * v\n\t\tlocal expected = vec3(-9, 10, 3)\n\n\t\t-- float margin is considered\n\t\tassert.is_true(math.abs(expected.x - result.x) < FLT_EPSILON)\n\t\tassert.is_true(math.abs(expected.y - result.y) < FLT_EPSILON)\n\t\tassert.is_true(math.abs(expected.z - result.z) < FLT_EPSILON)\n\tend)\n\nend)\n\n--[[\n\tfrom_angle_axis\n\tfrom_quaternion\n\tfrom_direction\n\tfrom_transform\n\tfrom_ortho\n--]]\n"
  },
  {
    "path": "spec/mesh_spec.lua",
    "content": "local mesh = require \"modules.mesh\"\n\ndescribe(\"mesh:\", function()\nend)\n\n--[[\naverage(vertices)\nnormal(triangle)\nplane_from_triangle(triangle)\nis_front_facing(plane, direction)\nsigned_distance(point, plane)\n--]]"
  },
  {
    "path": "spec/octree_spec.lua",
    "content": "local octree = require \"modules.octree\"\n\ndescribe(\"octree:\", function()\nend)\n\n--[[\nlocal function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)\nfunction Octree:add(obj, objBounds)\nfunction Octree:remove(obj)\nfunction Octree:is_colliding(checkBounds)\nfunction Octree:get_colliding(checkBounds)\nfunction Octree:cast_ray(ray, func, out)\nfunction Octree:draw_bounds(cube)\nfunction Octree:draw_objects(cube, filter)\nfunction Octree:grow(direction)\nfunction Octree:shrink()\nfunction Octree:get_root_pos_index(xDir, yDir, zDir)\n\nfunction OctreeNode:add(obj, objBounds)\nfunction OctreeNode:remove(obj)\nfunction OctreeNode:is_colliding(checkBounds)\nfunction OctreeNode:get_colliding(checkBounds, results)\nfunction OctreeNode:cast_ray(ray, func, out, depth)\nfunction OctreeNode:set_children(childOctrees)\nfunction OctreeNode:shrink_if_possible(minLength)\nfunction OctreeNode:set_values(baseLength, minSize, looseness, center)\nfunction OctreeNode:split()\nfunction OctreeNode:merge()\nfunction OctreeNode:best_fit_child(objBounds)\nfunction OctreeNode:should_merge()\nfunction OctreeNode:has_any_objects()\nfunction OctreeNode:draw_bounds(cube, depth)\nfunction OctreeNode:draw_objects(cube, filter)\n--]]"
  },
  {
    "path": "spec/quat_spec.lua",
    "content": "local quat  = require \"modules.quat\"\nlocal vec3  = require \"modules.vec3\"\nlocal utils = require \"modules.utils\"\nlocal constants = require \"modules.constants\"\n\ndescribe(\"quat:\", function()\n\tit(\"creates an identity quaternion\", function()\n\t\tlocal a = quat()\n\t\tassert.is.equal(0, a.x)\n\t\tassert.is.equal(0, a.y)\n\t\tassert.is.equal(0, a.z)\n\t\tassert.is.equal(1, a.w)\n\t\tassert.is_true(a:is_quat())\n\t\tassert.is_true(a:is_real())\n\tend)\n\n\tit(\"creates a quaternion from numbers\", function()\n\t\tlocal a = quat(0, 0, 0, 0)\n\t\tassert.is.equal(0, a.x)\n\t\tassert.is.equal(0, a.y)\n\t\tassert.is.equal(0, a.z)\n\t\tassert.is.equal(0, a.w)\n\t\tassert.is_true(a:is_zero())\n\t\tassert.is_true(a:is_imaginary())\n\tend)\n\n\tit(\"creates a quaternion from a list\", function()\n\t\tlocal a = quat { 2, 3, 4, 1 }\n\t\tassert.is.equal(2, a.x)\n\t\tassert.is.equal(3, a.y)\n\t\tassert.is.equal(4, a.z)\n\t\tassert.is.equal(1, a.w)\n\tend)\n\n\tit(\"creates a quaternion from a record\", function()\n\t\tlocal a = quat { x=2, y=3, z=4, w=1 }\n\t\tassert.is.equal(2, a.x)\n\t\tassert.is.equal(3, a.y)\n\t\tassert.is.equal(4, a.z)\n\t\tassert.is.equal(1, a.w)\n\tend)\n\n\tit(\"creates a quaternion from a quaternion\", function()\n\t\tlocal a = quat (quat(2, 3, 4, 1))\n\t\tassert.is.equal(2, a.x)\n\t\tassert.is.equal(3, a.y)\n\t\tassert.is.equal(4, a.z)\n\t\tassert.is.equal(1, a.w)\n\tend)\n\n\tit(\"creates a quaternion from a direction\", function()\n\t\tlocal v = vec3(-80, 80, -80):normalize()\n\t\tlocal a = quat.from_direction(v, vec3.unit_z)\n\t\tassert.is_true(utils.tolerance(-0.577-a.x, 0.001))\n\t\tassert.is_true(utils.tolerance(-0.577-a.y, 0.001))\n\t\tassert.is_true(utils.tolerance( 0    -a.z, 0.001))\n\t\tassert.is_true(utils.tolerance( 0.423-a.w, 0.001))\n\tend)\n\n\tit(\"clones a quaternion\", function()\n\t\tlocal a = quat()\n\t\tlocal b = a:clone()\n\t\tassert.is.equal(a.x, b.x)\n\t\tassert.is.equal(a.y, b.y)\n\t\tassert.is.equal(a.z, b.z)\n\t\tassert.is.equal(a.w, b.w)\n\tend)\n\n\tit(\"adds a quaternion to another\", function()\n\t\tlocal a = quat(2, 3, 4, 1)\n\t\tlocal b = quat(3, 6, 9, 1)\n\t\tlocal c = a:add(b)\n\t\tlocal d = a + b\n\t\tassert.is.equal(5,  c.x)\n\t\tassert.is.equal(9,  c.y)\n\t\tassert.is.equal(13, c.z)\n\t\tassert.is.equal(2,  c.w)\n\t\tassert.is.equal(c,  d)\n\tend)\n\n\tit(\"subtracts a quaternion from another\", function()\n\t\tlocal a = quat(2, 3, 4, 1)\n\t\tlocal b = quat(3, 6, 9, 1)\n\t\tlocal c = a:sub(b)\n\t\tlocal d = a - b\n\t\tassert.is.equal(-1, c.x)\n\t\tassert.is.equal(-3, c.y)\n\t\tassert.is.equal(-5, c.z)\n\t\tassert.is.equal( 0, c.w)\n\t\tassert.is.equal(c,  d)\n\tend)\n\n\tit(\"multiplies a quaternion by another\", function()\n\t\tlocal a = quat(2, 3, 4, 1)\n\t\tlocal b = quat(3, 6, 9, 1)\n\t\tlocal c = a:mul(b)\n\t\tlocal d = a * b\n\t\tassert.is.equal( 8,  c.x)\n\t\tassert.is.equal( 3,  c.y)\n\t\tassert.is.equal( 16, c.z)\n\t\tassert.is.equal(-59, c.w)\n\t\tassert.is.equal(c,   d)\n\tend)\n\n\tit(\"multiplies a quaternion by a scale factor\", function()\n\t\tlocal a = quat(2, 3, 4, 1)\n\t\tlocal s = 3\n\t\tlocal b = a:scale(s)\n\t\tlocal c = a * s\n\t\tassert.is.equal(6,  b.x)\n\t\tassert.is.equal(9,  b.y)\n\t\tassert.is.equal(12, b.z)\n\t\tassert.is.equal(3,  b.w)\n\t\tassert.is.equal(b,  c)\n\tend)\n\n\tit(\"inverts a quaternion\", function()\n\t\tlocal a = quat(2, 3, 4, 1)\n\t\tlocal b = -a\n\t\tassert.is.equal(-a.x, b.x)\n\t\tassert.is.equal(-a.y, b.y)\n\t\tassert.is.equal(-a.z, b.z)\n\t\tassert.is.equal(-a.w, b.w)\n\tend)\n\n\tit(\"multiplies a quaternion by a vec3\", function()\n\t\tlocal a = quat(2, 3, 4, 1)\n\t\tlocal v = vec3(3, 4, 5)\n\t\tlocal b = a:mul_vec3(v)\n\t\tlocal c = a * v\n\t\tassert.is.equal(-21, c.x)\n\t\tassert.is.equal( 4,  c.y)\n\t\tassert.is.equal( 17, c.z)\n\t\tassert.is.equal(b, c)\n\tend)\n\n\tit(\"verifies quat composition order\", function()\n\t\tlocal a = quat(2, 3, 4, 1):normalize() -- Only the normal quaternions represent rotations\n\t\tlocal b = quat(3, 6, 9, 1):normalize()\n\t\tlocal c = a * b\n\n\t\tlocal v = vec3(3, 4, 5)\n\n\t\tlocal cv = c * v\n\t\tlocal abv = a * (b * v)\n\n\t\tassert.is_true((abv - cv):len() < 1e-07) -- Verify (a*b)*v == a*(b*v) within an epsilon\n\tend)\n\n\tit(\"multiplies a quaternion by an exponent of 0\", function()\n\t\tlocal a = quat(2, 3, 4, 1):normalize()\n\t\tlocal e = 0\n\t\tlocal b = a:pow(e)\n\t\tlocal c = a^e\n\n\t\tassert.is.equal(0, b.x)\n\t\tassert.is.equal(0, b.y)\n\t\tassert.is.equal(0, b.z)\n\t\tassert.is.equal(1, b.w)\n\t\tassert.is.equal(b, c)\n\tend)\n\n\tit(\"multiplies a quaternion by a positive exponent\", function()\n\t\tlocal a = quat(2, 3, 4, 1):normalize()\n\t\tlocal e = 0.75\n\t\tlocal b = a:pow(e)\n\t\tlocal c = a^e\n\n\t\tassert.is_true(utils.tolerance(-0.3204+b.x, 0.0001))\n\t\tassert.is_true(utils.tolerance(-0.4805+b.y, 0.0001))\n\t\tassert.is_true(utils.tolerance(-0.6407+b.z, 0.0001))\n\t\tassert.is_true(utils.tolerance(-0.5059+b.w, 0.0001))\n\t\tassert.is.equal( b,  c)\n\tend)\n\n\tit(\"multiplies a quaternion by a negative exponent\", function()\n\t\tlocal a = quat(2, 3, 4, 1):normalize()\n\t\tlocal e = -1\n\t\tlocal b = a:pow(e)\n\t\tlocal c = a^e\n\n\t\tassert.is_true(utils.tolerance( 0.3651+b.x, 0.0001))\n\t\tassert.is_true(utils.tolerance( 0.5477+b.y, 0.0001))\n\t\tassert.is_true(utils.tolerance( 0.7303+b.z, 0.0001))\n\t\tassert.is_true(utils.tolerance(-0.1826+b.w, 0.0001))\n\t\tassert.is.equal(b, c)\n\tend)\n\n\tit(\"inverts a quaternion\", function()\n\t\tlocal a = quat(1, 1, 1, 1):inverse()\n\t\tassert.is.equal(-0.5, a.x)\n\t\tassert.is.equal(-0.5, a.y)\n\t\tassert.is.equal(-0.5, a.z)\n\t\tassert.is.equal( 0.5, a.w)\n\tend)\n\n\tit(\"normalizes a quaternion\", function()\n\t\tlocal a = quat(1, 1, 1, 1):normalize()\n\t\tassert.is.equal(0.5, a.x)\n\t\tassert.is.equal(0.5, a.y)\n\t\tassert.is.equal(0.5, a.z)\n\t\tassert.is.equal(0.5, a.w)\n\tend)\n\n\tit(\"dots two quaternions\", function()\n\t\tlocal a = quat(1, 1, 1, 1)\n\t\tlocal b = quat(4, 4, 4, 4)\n\t\tlocal c = a:dot(b)\n\t\tassert.is.equal(16, c)\n\tend)\n\n\tit(\"dots two quaternions (negative)\", function()\n\t\tlocal a = quat(-1, 1, 1, 1)\n\t\tlocal b = quat(4, 4, 4, 4)\n\t\tlocal c = a:dot(b)\n\t\tassert.is.equal(8, c)\n\tend)\n\n\tit(\"dots two quaternions (tiny)\", function()\n\t\tlocal a = quat(0.1, 0.1, 0.1, 0.1)\n\t\tlocal b = quat(0.4, 0.4, 0.4, 0.4)\n\t\tlocal c = a:dot(b)\n\t\tassert.is_true(utils.tolerance(0.16-c, 0.001))\n\tend)\n\n\tit(\"gets the length of a quaternion\", function()\n\t\tlocal a = quat(2, 3, 4, 5):len()\n\t\tassert.is.equal(math.sqrt(54), a)\n\tend)\n\n\tit(\"gets the square length of a quaternion\", function()\n\t\tlocal a = quat(2, 3, 4, 5):len2()\n\t\tassert.is.equal(54, a)\n\tend)\n\n\tit(\"interpolates between two quaternions\", function()\n\t\tlocal a = quat(3, 3, 3, 3)\n\t\tlocal b = quat(6, 6, 6, 6)\n\t\tlocal s = 0.1\n\t\tlocal c = a:lerp(b, s)\n\t\tassert.is.equal(0.5, c.x)\n\t\tassert.is.equal(0.5, c.y)\n\t\tassert.is.equal(0.5, c.z)\n\t\tassert.is.equal(0.5, c.w)\n\tend)\n\n\tit(\"interpolates between two quaternions (spherical)\", function()\n\t\tlocal a = quat(3, 3, 3, 3)\n\t\tlocal b = quat(6, 6, 6, 6)\n\t\tlocal s = 0.1\n\t\tlocal c = a:slerp(b, s)\n\t\tassert.is.equal(0.5, c.x)\n\t\tassert.is.equal(0.5, c.y)\n\t\tassert.is.equal(0.5, c.z)\n\t\tassert.is.equal(0.5, c.w)\n\tend)\n\n\tit(\"unpacks a quaternion\", function()\n\t\tlocal x, y, z, w = quat(2, 3, 4, 1):unpack()\n\t\tassert.is.equal(2, x)\n\t\tassert.is.equal(3, y)\n\t\tassert.is.equal(4, z)\n\t\tassert.is.equal(1, w)\n\tend)\n\n\tit(\"converts quaternion to a vec3\", function()\n\t\tlocal v = quat(2, 3, 4, 1):to_vec3()\n\t\tassert.is.equal(2, v.x)\n\t\tassert.is.equal(3, v.y)\n\t\tassert.is.equal(4, v.z)\n\tend)\n\n\tit(\"gets the conjugate quaternion\", function()\n\t\tlocal a = quat(2, 3, 4, 1):conjugate()\n\t\tassert.is.equal(-2, a.x)\n\t\tassert.is.equal(-3, a.y)\n\t\tassert.is.equal(-4, a.z)\n\t\tassert.is.equal( 1, a.w)\n\tend)\n\n\tit(\"gets the reciprocal quaternion\", function()\n\t\tlocal a = quat(1, 1, 1, 1)\n\t\tlocal b = a:reciprocal()\n\t\tlocal c = b:reciprocal()\n\n\t\tassert.is_not.equal(a.x, b.x)\n\t\tassert.is_not.equal(a.y, b.y)\n\t\tassert.is_not.equal(a.z, b.z)\n\t\tassert.is_not.equal(a.w, b.w)\n\n\t\tassert.is.equal(a.x, c.x)\n\t\tassert.is.equal(a.y, c.y)\n\t\tassert.is.equal(a.z, c.z)\n\t\tassert.is.equal(a.w, c.w)\n\tend)\n\n\tit(\"converts between a quaternion and angle/axis\", function()\n\t\tlocal a = quat.from_angle_axis(math.pi, vec3.unit_z)\n\t\tlocal angle, axis = a:to_angle_axis()\n\t\tassert.is.equal(math.pi,     angle)\n\t\tassert.is.equal(vec3.unit_z, axis)\n\tend)\n\n\tit(\"converts between a quaternion and angle/axis (specify by component)\", function()\n\t\tlocal a = quat.from_angle_axis(math.pi, vec3.unit_z.x, vec3.unit_z.y, vec3.unit_z.z)\n\t\tlocal angle, axis = a:to_angle_axis()\n\t\tassert.is.equal(math.pi,     angle)\n\t\tassert.is.equal(vec3.unit_z, axis)\n\tend)\n\n\tit(\"converts between a quaternion and angle/axis (w=2)\", function()\n\t\tlocal angle, axis = quat(1, 1, 1, 2):to_angle_axis()\n\t\tassert.is_true(utils.tolerance(1.427-angle,  0.001))\n\t\tassert.is_true(utils.tolerance(0.577-axis.x, 0.001))\n\t\tassert.is_true(utils.tolerance(0.577-axis.y, 0.001))\n\t\tassert.is_true(utils.tolerance(0.577-axis.z, 0.001))\n\tend)\n\n\tit(\"converts between a quaternion and angle/axis (w=2) (by component)\", function()\n\t\tlocal angle, x,y,z = quat(1, 1, 1, 2):to_angle_axis_unpack()\n\t\tassert.is_true(utils.tolerance(1.427-angle,  0.001))\n\t\tassert.is_true(utils.tolerance(0.577-x, 0.001))\n\t\tassert.is_true(utils.tolerance(0.577-y, 0.001))\n\t\tassert.is_true(utils.tolerance(0.577-z, 0.001))\n\tend)\n\n\tit(\"converts between a quaternion and angle/axis (w=1)\", function()\n\t\tlocal angle, axis = quat(1, 2, 3, 1):to_angle_axis()\n\t\tassert.is.equal(0, angle)\n\t\tassert.is.equal(1, axis.x)\n\t\tassert.is.equal(2, axis.y)\n\t\tassert.is.equal(3, axis.z)\n\tend)\n\n\tit(\"converts between a quaternion and angle/axis (identity quaternion) (by component)\", function()\n\t\tlocal angle, x,y,z = quat():to_angle_axis_unpack()\n\t\tassert.is.equal(0, angle)\n\t\tassert.is.equal(0, x)\n\t\tassert.is.equal(0, y)\n\t\tassert.is.equal(1, z)\n\tend)\n\n\tit(\"converts between a quaternion and angle/axis (identity quaternion with fallback)\", function()\n\t\tlocal angle, axis = quat():to_angle_axis(vec3(2,3,4))\n\t\tassert.is.equal(0, angle)\n\t\tassert.is.equal(2, axis.x)\n\t\tassert.is.equal(3, axis.y)\n\t\tassert.is.equal(4, axis.z)\n\tend)\n\n\tit(\"gets a string representation of a quaternion\", function()\n\t\tlocal a = quat():to_string()\n\t\tassert.is.equal(\"(+0.000,+0.000,+0.000,+1.000)\", a)\n\tend)\nend)\n"
  },
  {
    "path": "spec/utils_spec.lua",
    "content": "local vec3      = require \"modules.vec3\"\nlocal utils     = require \"modules.utils\"\nlocal constants = require \"modules.constants\"\n\nlocal function tolerance(v, t)\n\treturn math.abs(v - t) < 1e-6\nend\n\ndescribe(\"utils:\", function()\n\tit(\"interpolates between two numbers\", function()\n\t\tassert.is_true(tolerance(utils.lerp(0, 1, 0.5), 0.5))\n\tend)\n\n\tit(\"interpolates between two vectors\", function()\n\t\tlocal a = vec3(0, 0, 0)\n\t\tlocal b = vec3(1, 1, 1)\n\t\tlocal c = vec3(0.5, 0.5, 0.5)\n\t\tassert.is.equal(utils.lerp(a, b, 0.5), c)\n\n\t\ta = vec3(5, 5, 5)\n\t\tb = vec3(0, 0, 0)\n\t\tc = vec3(2.5, 2.5, 2.5)\n\t\tassert.is.equal(utils.lerp(a, b, 0.5), c)\n\tend)\n\n\tit(\"decays exponentially\", function()\n\t\tlocal v = utils.decay(0, 1, 0.5, 1)\n\t\tassert.is_true(tolerance(v, 0.39346934028737))\n\tend)\n\n\tit(\"checks a nan\", function()\n\t\tlocal a = 0/0\n\t\tassert.is_true(utils.is_nan(a))\n\tend)\n\n\tit(\"rounds a number\", function()\n\t\t-- round up\n\t\tlocal v = utils.round(1.3252525, 0.01)\n\t\tassert.is_true(tolerance(v, 1.33))\n\t\t-- round down\n\t\tv = utils.round(1.3242525, 0.1)\n\t\tassert.is_true(tolerance(v, 1.3))\n\t\t-- no precision\n\t\tv = utils.round(1.3242525)\n\t\tassert.is_true(tolerance(v, 1))\n\tend)\n\n\tit(\"checks sign\", function()\n\t\tassert.is.equal(utils.sign(-9), -1)\n\t\tassert.is.equal(utils.sign(0), 0)\n\t\tassert.is.equal(utils.sign(12), 1)\n\tend)\nend)\n\n--[[\nclamp(value, min, max)\ndeadzone(value, size)\nthreshold(value, threshold)\ntolerance(value, threshold)\nmap(value, min_in, max_in, min_out, max_out)\nlerp(progress, low, high)\nsmoothstep(progress, low, high)\nwrap(value, limit)\nis_pot(value)\nproject_on(out, a, b)\nproject_from(out, a, b)\nmirror_on(out, a, b)\nreflect(out, i, n)\nrefract(out, i, n, ior)\n--]]\n"
  },
  {
    "path": "spec/vec2_spec.lua",
    "content": "local vec2        = require \"modules.vec2\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\nlocal abs, sqrt   = math.abs, math.sqrt\n\ndescribe(\"vec2:\", function()\n\tit(\"creates an empty vector\", function()\n\t\tlocal a = vec2()\n\t\tassert.is.equal(0, a.x)\n\t\tassert.is.equal(0, a.y)\n\t\tassert.is_true(a:is_vec2())\n\t\tassert.is_true(a:is_zero())\n\tend)\n\n\tit(\"creates a vector from a number\", function()\n\t\tlocal a = vec2(3)\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(3, a.y)\n\tend)\n\n\tit(\"creates a vector from numbers\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(5, a.y)\n\tend)\n\n\tit(\"creates a vector from a list\", function()\n\t\tlocal a = vec2 { 3, 5 }\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(5, a.y)\n\tend)\n\n\tit(\"creates a vector from a record\", function()\n\t\tlocal a = vec2 { x=3, y=5 }\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(5, a.y)\n\tend)\n\n\tit(\"creates a vector from nan\", function()\n\t\tlocal a = vec2(0/0)\n\t\tassert.is_true(a:has_nan())\n\tend)\n\n\tit(\"clones a vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = a:clone()\n\t\tassert.is.equal(a, b)\n\tend)\n\n\tit(\"clones a vector using the constructor\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(a)\n\t\tassert.is.equal(a, b)\n\tend)\n\n\tit(\"adds a vector to another\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(7, 4)\n\t\tlocal c = a:add(b)\n\t\tlocal d = a + b\n\t\tassert.is.equal(10, c.x)\n\t\tassert.is.equal(9,  c.y)\n\t\tassert.is.equal(c,  d)\n\tend)\n\n\tit(\"subracts a vector from another\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(7, 4)\n\t\tlocal c = a:sub(b)\n\t\tlocal d = a - b\n\t\tassert.is.equal(-4, c.x)\n\t\tassert.is.equal( 1, c.y)\n\t\tassert.is.equal( c, d)\n\tend)\n\n\tit(\"multiplies a vector by a scale factor\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal s = 2\n\t\tlocal c = a:scale(s)\n\t\tlocal d = a * s\n\t\tassert.is.equal(6,  c.x)\n\t\tassert.is.equal(10, c.y)\n\t\tassert.is.equal(c,  d)\n\tend)\n\n\tit(\"divides a vector by another vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal s = vec2(2, 2)\n\t\tlocal c = a:div(s)\n\t\tlocal d = a / s\n\t\tassert.is.equal(1.5, c.x)\n\t\tassert.is.equal(2.5, c.y)\n\t\tassert.is.equal(c,   d)\n\tend)\n\n\tit(\"inverts a vector\", function()\n\t\tlocal a = vec2(3, -5)\n\t\tlocal b = -a\n\t\tassert.is.equal(-a.x, b.x)\n\t\tassert.is.equal(-a.y, b.y)\n\tend)\n\n\tit(\"gets the length of a vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tassert.is.equal(sqrt(34), a:len())\n\t\tend)\n\n\tit(\"gets the square length of a vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tassert.is.equal(34, a:len2())\n\tend)\n\n\tit(\"normalizes a vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = a:normalize()\n\t\tassert.is_true(abs(b:len()-1) < DBL_EPSILON)\n\t\tend)\n\n\tit(\"trims the length of a vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = a:trim(0.5)\n\t\tassert.is_true(abs(b:len()-0.5) < DBL_EPSILON)\n\tend)\n\n\tit(\"gets the distance between two vectors\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(7, 4)\n\t\tlocal c = a:dist(b)\n\t\tassert.is.equal(sqrt(17), c)\n\tend)\n\n\tit(\"gets the square distance between two vectors\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(7, 4)\n\t\tlocal c = a:dist2(b)\n\t\tassert.is.equal(17, c)\n\tend)\n\n\tit(\"crosses two vectors\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(7, 4)\n\t\tlocal c = a:cross(b)\n\t\tassert.is.equal(-23, c)\n\tend)\n\n\tit(\"dots two vectors\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(7, 4)\n\t\tlocal c = a:dot(b)\n\t\tassert.is.equal(41, c)\n\tend)\n\n\tit(\"interpolates between two vectors\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec2(7, 4)\n\t\tlocal s = 0.1\n\t\tlocal c = a:lerp(b, s)\n\t\tassert.is.equal(3.4, c.x)\n\t\tassert.is.equal(4.9, c.y)\n\tend)\n\n\tit(\"unpacks a vector\", function()\n\t\tlocal a    = vec2(3, 5)\n\t\tlocal x, y = a:unpack()\n\t\tassert.is.equal(3, x)\n\t\tassert.is.equal(5, y)\n\tend)\n\n\tit(\"rotates a vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = a:rotate( math.pi)\n\t\tlocal c = b:rotate(-math.pi)\n\t\tassert.is_not.equal(a, b)\n\t\tassert.is.equal(a, c)\n\tend)\n\n\tit(\"converts between polar and cartesian coordinates\", function()\n\t\tlocal a    = vec2(3, 5)\n\t\tlocal r, t = a:to_polar()\n\t\tlocal b    = vec2.from_cartesian(r, t)\n\t\tassert.is_true(abs(a.x - b.x) <= DBL_EPSILON*2) -- Allow 2X epsilon error because there were 2 operations.\n\t\tassert.is_true(abs(a.y - b.y) <= DBL_EPSILON*2)\n\tend)\n\n\tit(\"gets a perpendicular vector\", function()\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = a:perpendicular()\n\t\tassert.is.equal(-5, b.x)\n\t\tassert.is.equal( 3, b.y)\n\tend)\n\n\tit(\"gets a string representation of a vector\", function()\n\t\tlocal a = vec2()\n\t\tlocal b = a:to_string()\n\t\tassert.is.equal(\"(+0.000,+0.000)\", b)\n\tend)\n\n\tit(\"rounds a 2-vector\", function()\n\t\tlocal a = vec2(1.1,1.9):round()\n\t\tassert.is.equal(a.x, 1)\n\t\tassert.is.equal(a.y, 2)\n\tend)\n\n\tit(\"flips a 2-vector\", function()\n\t\tlocal a = vec2(1,2)\n\t\tlocal temp = a:flip_x()\n\t\tassert.is.equal(temp, vec2(-1, 2))\n\t\ttemp = temp:flip_y()\n\t\tassert.is.equal(temp, vec2(-1, -2))\n\tend)\n\n\tit(\"finds angle from one 2-vector to another\", function()\n\t\tlocal d = {\n\t\t\tright = vec2(1,  0),\n\t\t\tdown  = vec2(0,  -1),\n\t\t\tleft  = vec2(-1, 0),\n\t\t\tup    = vec2(0,  1),\n\t\t}\n\t\tassert.is.equal(math.deg(d.right:angle_to(d.right)), 0.0)\n\t\tassert.is.equal(math.deg(d.right:angle_to(d.down)), -90.0)\n\t\tassert.is.equal(math.deg(d.right:angle_to(d.left)), 180.0)\n\t\tassert.is.equal(math.deg(d.right:angle_to(d.up)), 90.0)\n\n\t\tassert.is.equal(math.deg(d.down:angle_to(d.right)), 90.0)\n\t\tassert.is.equal(math.deg(d.down:angle_to(d.down)), 0.0)\n\t\tassert.is.equal(math.deg(d.down:angle_to(d.left)), -90.0)\n\t\tassert.is.equal(math.deg(d.down:angle_to(d.up)), 180.0)\n\n\t\tassert.is.equal(math.deg(d.left:angle_to(d.right)), 180.0)\n\t\tassert.is.equal(math.deg(d.left:angle_to(d.down)), 90.0)\n\t\tassert.is.equal(math.deg(d.left:angle_to(d.left)), 0.0)\n\t\tassert.is.equal(math.deg(d.left:angle_to(d.up)), -90.0)\n\n\t\tassert.is.equal(math.deg(d.up:angle_to(d.right)), -90.0)\n\t\tassert.is.equal(math.deg(d.up:angle_to(d.down)), 180.0)\n\t\tassert.is.equal(math.deg(d.up:angle_to(d.left)), 90.0)\n\t\tassert.is.equal(math.deg(d.up:angle_to(d.up)), 0.0)\n\tend)\n\n\tit(\"finds angle between two 2-vectors\", function()\n\t\tlocal d = {\n\t\t\tright = vec2(1,  0),\n\t\t\tdown  = vec2(0,  -1),\n\t\t\tleft  = vec2(-1, 0),\n\t\t\tup    = vec2(0,  1),\n\t\t}\n\t\tassert.is.equal(math.deg(d.right:angle_between(d.right)), 0.0)\n\t\tassert.is.equal(math.deg(d.right:angle_between(d.down)), 90.0)\n\t\tassert.is.equal(math.deg(d.right:angle_between(d.left)), 180.0)\n\t\tassert.is.equal(math.deg(d.right:angle_between(d.up)), 90.0)\n\n\t\tassert.is.equal(math.deg(d.down:angle_between(d.right)), 90.0)\n\t\tassert.is.equal(math.deg(d.down:angle_between(d.down)), 0.0)\n\t\tassert.is.equal(math.deg(d.down:angle_between(d.left)), 90.0)\n\t\tassert.is.equal(math.deg(d.down:angle_between(d.up)), 180.0)\n\n\t\tassert.is.equal(math.deg(d.left:angle_between(d.right)), 180.0)\n\t\tassert.is.equal(math.deg(d.left:angle_between(d.down)), 90.0)\n\t\tassert.is.equal(math.deg(d.left:angle_between(d.left)), 0.0)\n\t\tassert.is.equal(math.deg(d.left:angle_between(d.up)), 90.0)\n\n\t\tassert.is.equal(math.deg(d.up:angle_between(d.right)), 90.0)\n\t\tassert.is.equal(math.deg(d.up:angle_between(d.down)), 180.0)\n\t\tassert.is.equal(math.deg(d.up:angle_between(d.left)), 90.0)\n\t\tassert.is.equal(math.deg(d.up:angle_between(d.up)), 0.0)\n\tend)\n\n\t-- Do this last, to insulate tests from accidental state contamination\n\t-- Do vec3 tests last, to insulate tests from accidental state contamination\n\tit(\"converts a 2-vector to a 3-vector\", function()\n\t\tlocal vec3 = require \"modules.vec3\"\n\t\tlocal a = vec2(1,2)\n\t\tlocal b = a:to_vec3()\n\t\tlocal c = a:to_vec3(3)\n\t\tassert.is.equal(b, vec3(1,2,0))\n\t\tassert.is.equal(c, vec3(1,2,3))\n\tend)\n\n\tit(\"converts a vec3 to vec2 using the constructor\", function()\n\t\tlocal vec3 = require \"modules.vec3\"\n\t\tlocal a = vec2(3, 5)\n\t\tlocal b = vec3(3, 5, 7)\n\t\tlocal c = vec2(b)\n\t\tassert.is.equal(a, c)\n\tend)\nend)\n"
  },
  {
    "path": "spec/vec3_spec.lua",
    "content": "local vec3        = require \"modules.vec3\"\nlocal DBL_EPSILON = require(\"modules.constants\").DBL_EPSILON\nlocal abs, sqrt   = math.abs, math.sqrt\n\ndescribe(\"vec3:\", function()\n\tit(\"creates an empty vector\", function()\n\t\tlocal a = vec3()\n\t\tassert.is.equal(0, a.x)\n\t\tassert.is.equal(0, a.y)\n\t\tassert.is.equal(0, a.z)\n\t\tassert.is_true(a:is_vec3())\n\t\tassert.is_true(a:is_zero())\n\tend)\n\n\tit(\"creates a vector from a number\", function()\n\t\tlocal a = vec3(3)\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(3, a.y)\n\t\tassert.is.equal(3, a.z)\n\tend)\n\n\tit(\"creates a vector from numbers\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(5, a.y)\n\t\tassert.is.equal(7, a.z)\n\tend)\n\n\tit(\"creates a vector from a list\", function()\n\t\tlocal a = vec3 { 3, 5, 7 }\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(5, a.y)\n\t\tassert.is.equal(7, a.z)\n\tend)\n\n\tit(\"creates a vector from a record\", function()\n\t\tlocal a = vec3 { x=3, y=5, z=7 }\n\t\tassert.is.equal(3, a.x)\n\t\tassert.is.equal(5, a.y)\n\t\tassert.is.equal(7, a.z)\n\tend)\n\n\tit(\"creates a vector from nan\", function()\n\t\tlocal a = vec3(0/0)\n\t\tassert.is_true(a:has_nan())\n\tend)\n\n\tit(\"clones a vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = a:clone()\n\t\tassert.is.equal(a, b)\n\tend)\n\n\tit(\"clones a vector using the constructor\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(a)\n\t\tassert.is.equal(a, b)\n\tend)\n\n\tit(\"adds a vector to another\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(7, 4, 1)\n\t\tlocal c = a:add(b)\n\t\tlocal d = a + b\n\t\tassert.is.equal(10, c.x)\n\t\tassert.is.equal(9,  c.y)\n\t\tassert.is.equal(8,  c.z)\n\t\tassert.is.equal(c,  d)\n\tend)\n\n\tit(\"subracts a vector from another\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(7, 4, 1)\n\t\tlocal c = a:sub(b)\n\t\tlocal d = a - b\n\t\tassert.is.equal(-4, c.x)\n\t\tassert.is.equal( 1, c.y)\n\t\tassert.is.equal( 6, c.z)\n\t\tassert.is.equal( c, d)\n\tend)\n\n\tit(\"multiplies a vector by a scale factor\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal s = 2\n\t\tlocal c = a:scale(s)\n\t\tlocal d = a * s\n\t\tassert.is.equal(6,  c.x)\n\t\tassert.is.equal(10, c.y)\n\t\tassert.is.equal(14, c.z)\n\t\tassert.is.equal(c,  d)\n\tend)\n\n\tit(\"divides a vector by another vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal s = vec3(2, 2, 2)\n\t\tlocal c = a:div(s)\n\t\tlocal d = a / s\n\t\tassert.is.equal(1.5, c.x)\n\t\tassert.is.equal(2.5, c.y)\n\t\tassert.is.equal(3.5, c.z)\n\t\tassert.is.equal(c,   d)\n\tend)\n\n\tit(\"inverts a vector\", function()\n\t\tlocal a = vec3(3, -5, 7)\n\t\tlocal b = -a\n\t\tassert.is.equal(-a.x, b.x)\n\t\tassert.is.equal(-a.y, b.y)\n\t\tassert.is.equal(-a.z, b.z)\n\tend)\n\n\tit(\"gets the length of a vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tassert.is.equal(sqrt(83), a:len())\n\t\tend)\n\n\tit(\"gets the square length of a vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tassert.is.equal(83, a:len2())\n\tend)\n\n\tit(\"normalizes a vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = a:normalize()\n\t\tassert.is_true(abs(b:len()-1) < DBL_EPSILON)\n\tend)\n\n\tit(\"normalizes a vector and gets the length\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b, l = a:normalize_len()\n\t\tassert.is_true(abs(b:len()-1) < DBL_EPSILON)\n\t\tassert.is.equal(sqrt(83), l)\n\tend)\n\n\tit(\"trims the length of a vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = a:trim(0.5)\n\t\tassert.is_true(abs(b:len()-0.5) < DBL_EPSILON)\n\tend)\n\n\tit(\"gets the distance between two vectors\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(7, 4, 1)\n\t\tlocal c = a:dist(b)\n\t\tassert.is.equal(sqrt(53), c)\n\tend)\n\n\tit(\"gets the square distance between two vectors\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(7, 4, 1)\n\t\tlocal c = a:dist2(b)\n\t\tassert.is.equal(53, c)\n\tend)\n\n\tit(\"crosses two vectors\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(7, 4, 1)\n\t\tlocal c = a:cross(b)\n\t\tassert.is.equal(-23, c.x)\n\t\tassert.is.equal( 46, c.y)\n\t\tassert.is.equal(-23, c.z)\n\tend)\n\n\tit(\"dots two vectors\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(7, 4, 1)\n\t\tlocal c = a:dot(b)\n\t\tassert.is.equal(48, c)\n\tend)\n\n\tit(\"interpolates between two vectors\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = vec3(7, 4, 1)\n\t\tlocal s = 0.1\n\t\tlocal c = a:lerp(b, s)\n\t\tassert.is.equal(3.4, c.x)\n\t\tassert.is.equal(4.9, c.y)\n\t\tassert.is.equal(6.4, c.z)\n\tend)\n\n\tit(\"unpacks a vector\", function()\n\t\tlocal a       = vec3(3, 5, 7)\n\t\tlocal x, y, z = a:unpack()\n\t\tassert.is.equal(3, x)\n\t\tassert.is.equal(5, y)\n\t\tassert.is.equal(7, z)\n\tend)\n\n\tit(\"rotates a vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = a:rotate( math.pi, vec3.unit_z)\n\t\tlocal c = b:rotate(-math.pi, vec3.unit_z)\n\t\tassert.is_not.equal(a, b)\n\t\tassert.is.equal(7, b.z)\n\t\tassert.is.equal(a, c)\n\tend)\n\n\tit(\"cannot rotate a vector without a valis axis\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = a:rotate(math.pi, 0)\n\t\tassert.is_equal(a, b)\n\tend)\n\n\tit(\"gets a perpendicular vector\", function()\n\t\tlocal a = vec3(3, 5, 7)\n\t\tlocal b = a:perpendicular()\n\t\tassert.is.equal(-5, b.x)\n\t\tassert.is.equal( 3, b.y)\n\t\tassert.is.equal( 0, b.z)\n\tend)\n\n\tit(\"gets a string representation of a vector\", function()\n\t\tlocal a = vec3()\n\t\tlocal b = a:to_string()\n\t\tassert.is.equal(\"(+0.000,+0.000,+0.000)\", b)\n\tend)\n\n\tit(\"rounds a 3-vector\", function()\n\t\tlocal a = vec3(1.1,1.9,3):round()\n\t\tassert.is.equal(a.x, 1)\n\t\tassert.is.equal(a.y, 2)\n\t\tassert.is.equal(a.z, 3)\n\tend)\n\n\tit(\"flips a 3-vector\", function()\n\t\tlocal a = vec3(1,2,3)\n\t\tlocal temp = a:flip_x()\n\t\tassert.is.equal(temp, vec3(-1, 2, 3))\n\t\ttemp = temp:flip_y()\n\t\tassert.is.equal(temp, vec3(-1, -2, 3))\n\t\ttemp = temp:flip_z()\n\t\tassert.is.equal(temp, vec3(-1, -2, -3))\n\tend)\n\n\tit(\"get two 3-vectors angle\", function()\n\t\tlocal angle_to = function(a, b)\n\t\t\tlocal deg = math.deg(a:angle_to(b))\n\t\t\treturn string.format('%.2f', deg)\n\t\tend\n\n\t\tlocal a = vec3(1,2,3)\n\t\tassert.is.equal(angle_to(a, vec3(3, 2, 1)), '44.42')\n\t\tassert.is.equal(angle_to(a, vec3(0, 10, 0)), '57.69')\n\t\tassert.is.equal(angle_to(a, vec3(0, -12, -10)), '157.51')\n\n\t\ta = vec3.unit_z\n\t\tassert.is.equal(angle_to(a, vec3(0, 10, 0)), '90.00')\n\t\tassert.is.equal(angle_to(a, vec3(-123, 10, 0)), '90.00')\n\t\tassert.is.equal(angle_to(a, vec3(-10, 0, 10)), '45.00')\n\t\tassert.is.equal(angle_to(a, vec3(-10, 0, -10)), '135.00')\n\t\tassert.is.equal(angle_to(a, vec3(0, -10, -10)), '135.00')\n\t\tassert.is.equal(angle_to(a, vec3(0, 0, -10)), '180.00')\n\t\tassert.is.equal(angle_to(a, vec3(0, 0, 100)), '0.00')\n\n\t\ta = vec3(100, 100, 0)\n\t\tassert.is.equal(angle_to(a, vec3(0, 0, 100)), '90.00')\n\t\tassert.is.equal(angle_to(a, vec3(0, 0, -100)), '90.00')\n\t\tassert.is.equal(angle_to(a, vec3(-10, -10, 0)), '180.00')\n\t\tassert.is.equal(angle_to(a, vec3.unit_z), '90.00')\n\tend)\nend)\n"
  }
]