From 7b09710392d35fb55b52031d447a542d99fc6b4b Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Tue, 19 Aug 2014 17:10:03 -0400
Subject: Modularize the libdimension codebase.

---
 libdimension/Makefile.am                     | 211 ++++++-----
 libdimension/array.c                         |  33 --
 libdimension/base/array.c                    |  33 ++
 libdimension/base/dictionary.c               | 228 +++++++++++
 libdimension/base/error.c                    | 144 +++++++
 libdimension/base/inline.c                   |  43 +++
 libdimension/base/malloc.c                   |  94 +++++
 libdimension/base/pool.c                     | 177 +++++++++
 libdimension/base/profile.c                  | 168 ++++++++
 libdimension/bench/future.c                  |   6 +-
 libdimension/bench/geometry.c                |  37 +-
 libdimension/bench/polynomial.c              |   3 +-
 libdimension/bench/prtree.c                  |  20 +-
 libdimension/bench/triangle.c                |  10 +-
 libdimension/bvh.c                           | 404 --------------------
 libdimension/bvh.h                           |  74 ----
 libdimension/bvh/bvh.c                       | 402 ++++++++++++++++++++
 libdimension/bvh/prtree.c                    | 372 ++++++++++++++++++
 libdimension/camera.c                        |  51 ---
 libdimension/canvas.c                        |  92 -----
 libdimension/canvas/canvas.c                 |  92 +++++
 libdimension/canvas/gl-stubs.c               |  48 +++
 libdimension/canvas/gl.c                     | 113 ++++++
 libdimension/canvas/png-stubs.c              |  62 +++
 libdimension/canvas/png.c                    | 393 +++++++++++++++++++
 libdimension/canvas/rgba.c                   |  95 +++++
 libdimension/canvas_pigment.c                |  57 ---
 libdimension/checker.c                       |  63 ---
 libdimension/compiler-internal.h             |  92 -----
 libdimension/concurrency/future.c            | 281 ++++++++++++++
 libdimension/concurrency/threads.c           | 326 ++++++++++++++++
 libdimension/cone.c                          | 206 ----------
 libdimension/csg.c                           | 332 ----------------
 libdimension/cube.c                          | 154 --------
 libdimension/dictionary.c                    | 228 -----------
 libdimension/dimension-internal.h            |  40 --
 libdimension/dimension.h                     |  58 +--
 libdimension/dimension/array.h               | 357 -----------------
 libdimension/dimension/base.h                |  46 +++
 libdimension/dimension/base/array.h          | 361 ++++++++++++++++++
 libdimension/dimension/base/common.h         |  34 ++
 libdimension/dimension/base/compiler.h       | 139 +++++++
 libdimension/dimension/base/dictionary.h     |  87 +++++
 libdimension/dimension/base/error.h          | 118 ++++++
 libdimension/dimension/base/malloc.h         |  70 ++++
 libdimension/dimension/base/pool.h           |  81 ++++
 libdimension/dimension/camera.h              |  67 ----
 libdimension/dimension/cameras.h             |  34 --
 libdimension/dimension/canvas.h              | 114 +-----
 libdimension/dimension/canvas/canvas.h       | 131 +++++++
 libdimension/dimension/canvas/gl.h           |  52 +++
 libdimension/dimension/canvas/png.h          |  75 ++++
 libdimension/dimension/color.h               | 177 +--------
 libdimension/dimension/color/color.h         | 198 ++++++++++
 libdimension/dimension/color/tcolor.h        | 116 ++++++
 libdimension/dimension/common.h              |  30 --
 libdimension/dimension/compiler.h            | 135 -------
 libdimension/dimension/concurrency.h         |  39 ++
 libdimension/dimension/concurrency/future.h  |  85 +++++
 libdimension/dimension/csg.h                 |  59 ---
 libdimension/dimension/dictionary.h          |  83 ----
 libdimension/dimension/error.h               | 117 ------
 libdimension/dimension/finish.h              | 141 -------
 libdimension/dimension/finishes.h            |  51 ---
 libdimension/dimension/future.h              |  79 ----
 libdimension/dimension/geometry.h            | 500 ------------------------
 libdimension/dimension/gl.h                  |  48 ---
 libdimension/dimension/interior.h            |  44 ---
 libdimension/dimension/light.h               |  76 ----
 libdimension/dimension/lights.h              |  33 --
 libdimension/dimension/malloc.h              |  66 ----
 libdimension/dimension/map.h                 |  68 ----
 libdimension/dimension/math.h                |  82 +---
 libdimension/dimension/math/aabb.h           | 129 +++++++
 libdimension/dimension/math/matrix.h         | 193 ++++++++++
 libdimension/dimension/math/ray.h            |  83 ++++
 libdimension/dimension/math/scalar.h         | 103 +++++
 libdimension/dimension/math/vector.h         | 183 +++++++++
 libdimension/dimension/model.h               |  59 +++
 libdimension/dimension/model/camera.h        |  66 ++++
 libdimension/dimension/model/cameras.h       |  34 ++
 libdimension/dimension/model/csg.h           |  59 +++
 libdimension/dimension/model/finish.h        | 141 +++++++
 libdimension/dimension/model/finishes.h      |  51 +++
 libdimension/dimension/model/interior.h      |  44 +++
 libdimension/dimension/model/light.h         |  76 ++++
 libdimension/dimension/model/lights.h        |  33 ++
 libdimension/dimension/model/object.h        | 165 ++++++++
 libdimension/dimension/model/objects.h       | 103 +++++
 libdimension/dimension/model/pigment.h       |  86 +++++
 libdimension/dimension/model/pigments.h      |  66 ++++
 libdimension/dimension/model/scene.h         |  95 +++++
 libdimension/dimension/model/texture.h       |  57 +++
 libdimension/dimension/object.h              | 164 --------
 libdimension/dimension/objects.h             | 103 -----
 libdimension/dimension/pattern.h             |  50 +--
 libdimension/dimension/pattern/map.h         |  68 ++++
 libdimension/dimension/pattern/pattern.h     |  62 +++
 libdimension/dimension/pattern/patterns.h    |  48 +++
 libdimension/dimension/patterns.h            |  48 ---
 libdimension/dimension/pigment.h             |  86 -----
 libdimension/dimension/pigments.h            |  66 ----
 libdimension/dimension/platform.h            |  39 ++
 libdimension/dimension/platform/timer.h      |  58 +++
 libdimension/dimension/png.h                 |  71 ----
 libdimension/dimension/polynomial.h          |  85 -----
 libdimension/dimension/pool.h                |  77 ----
 libdimension/dimension/ray_trace.h           |  37 --
 libdimension/dimension/render.h              |  41 ++
 libdimension/dimension/render/render.h       |  41 ++
 libdimension/dimension/scene.h               |  95 -----
 libdimension/dimension/tcolor.h              | 112 ------
 libdimension/dimension/texture.h             |  57 ---
 libdimension/dimension/timer.h               |  54 ---
 libdimension/error.c                         | 143 -------
 libdimension/finish.c                        | 102 -----
 libdimension/future-internal.h               |  71 ----
 libdimension/future.c                        | 279 --------------
 libdimension/geometry.c                      | 387 -------------------
 libdimension/gl-stubs.c                      |  48 ---
 libdimension/gl.c                            | 111 ------
 libdimension/gradient.c                      |  56 ---
 libdimension/inline.c                        |  43 ---
 libdimension/interior.c                      |  46 ---
 libdimension/internal.h                      |  35 ++
 libdimension/internal/bvh.h                  |  79 ++++
 libdimension/internal/compiler.h             |  71 ++++
 libdimension/internal/concurrency.h          | 236 ++++++++++++
 libdimension/internal/future.h               |  61 +++
 libdimension/internal/platform.h             |  67 ++++
 libdimension/internal/polynomial.h           |  85 +++++
 libdimension/internal/profile.h              |  68 ++++
 libdimension/internal/prtree.h               |  38 ++
 libdimension/internal/rgba.h                 |  55 +++
 libdimension/lambertian.c                    |  57 ---
 libdimension/leopard.c                       |  46 ---
 libdimension/light.c                         |  45 ---
 libdimension/malloc.c                        |  94 -----
 libdimension/map.c                           | 125 ------
 libdimension/math/matrix.c                   | 388 +++++++++++++++++++
 libdimension/math/polynomial.c               | 443 ++++++++++++++++++++++
 libdimension/model/camera.c                  |  52 +++
 libdimension/model/cameras/perspective.c     |  47 +++
 libdimension/model/finish.c                  | 103 +++++
 libdimension/model/finishes/lambertian.c     |  57 +++
 libdimension/model/finishes/phong.c          |  69 ++++
 libdimension/model/finishes/reflection.c     |  64 ++++
 libdimension/model/interior.c                |  47 +++
 libdimension/model/light.c                   |  46 +++
 libdimension/model/lights/point_light.c      |  72 ++++
 libdimension/model/object.c                  | 110 ++++++
 libdimension/model/objects/cone.c            | 207 ++++++++++
 libdimension/model/objects/csg.c             | 334 ++++++++++++++++
 libdimension/model/objects/cube.c            | 154 ++++++++
 libdimension/model/objects/plane.c           |  88 +++++
 libdimension/model/objects/sphere.c          | 116 ++++++
 libdimension/model/objects/torus.c           | 173 +++++++++
 libdimension/model/objects/triangle.c        | 163 ++++++++
 libdimension/model/objects/triangle_fan.c    | 347 +++++++++++++++++
 libdimension/model/pigment.c                 |  72 ++++
 libdimension/model/pigments/canvas_pigment.c |  57 +++
 libdimension/model/pigments/pigment_map.c    |  96 +++++
 libdimension/model/pigments/solid_pigment.c  |  35 ++
 libdimension/model/scene.c                   |  79 ++++
 libdimension/model/texture.c                 |  68 ++++
 libdimension/object.c                        | 109 ------
 libdimension/pattern.c                       |  45 ---
 libdimension/pattern/checker.c               |  63 +++
 libdimension/pattern/gradient.c              |  56 +++
 libdimension/pattern/leopard.c               |  46 +++
 libdimension/pattern/map.c                   | 126 ++++++
 libdimension/pattern/pattern.c               |  45 +++
 libdimension/perspective.c                   |  47 ---
 libdimension/phong.c                         |  69 ----
 libdimension/pigment.c                       |  72 ----
 libdimension/pigment_map.c                   |  96 -----
 libdimension/plane.c                         |  88 -----
 libdimension/platform.c                      | 185 ---------
 libdimension/platform.h                      |  60 ---
 libdimension/platform/platform.c             | 187 +++++++++
 libdimension/platform/timer.c                |  43 +++
 libdimension/png-stubs.c                     |  62 ---
 libdimension/png.c                           | 390 -------------------
 libdimension/point_light.c                   |  72 ----
 libdimension/polynomial.c                    | 441 ---------------------
 libdimension/pool.c                          | 176 ---------
 libdimension/profile.c                       | 167 --------
 libdimension/profile.h                       |  39 --
 libdimension/prtree.c                        | 370 ------------------
 libdimension/prtree.h                        |  30 --
 libdimension/ray_trace.c                     | 547 --------------------------
 libdimension/reflection.c                    |  64 ----
 libdimension/render/render.c                 | 548 +++++++++++++++++++++++++++
 libdimension/rgba.c                          |  95 -----
 libdimension/rgba.h                          |  48 ---
 libdimension/scene.c                         |  77 ----
 libdimension/solid_pigment.c                 |  36 --
 libdimension/sphere.c                        | 114 ------
 libdimension/tests/future.c                  |   6 +-
 libdimension/tests/polynomial.c              |   2 +-
 libdimension/tests/pool.c                    |   6 +-
 libdimension/tests/prtree.c                  |  24 +-
 libdimension/tests/render.c                  |   2 +-
 libdimension/texture.c                       |  68 ----
 libdimension/threads.c                       | 324 ----------------
 libdimension/threads.h                       | 216 -----------
 libdimension/timer.c                         |  42 --
 libdimension/torus.c                         | 172 ---------
 libdimension/triangle.c                      | 162 --------
 libdimension/triangle_fan.c                  | 346 -----------------
 210 files changed, 12525 insertions(+), 11916 deletions(-)
 delete mode 100644 libdimension/array.c
 create mode 100644 libdimension/base/array.c
 create mode 100644 libdimension/base/dictionary.c
 create mode 100644 libdimension/base/error.c
 create mode 100644 libdimension/base/inline.c
 create mode 100644 libdimension/base/malloc.c
 create mode 100644 libdimension/base/pool.c
 create mode 100644 libdimension/base/profile.c
 delete mode 100644 libdimension/bvh.c
 delete mode 100644 libdimension/bvh.h
 create mode 100644 libdimension/bvh/bvh.c
 create mode 100644 libdimension/bvh/prtree.c
 delete mode 100644 libdimension/camera.c
 delete mode 100644 libdimension/canvas.c
 create mode 100644 libdimension/canvas/canvas.c
 create mode 100644 libdimension/canvas/gl-stubs.c
 create mode 100644 libdimension/canvas/gl.c
 create mode 100644 libdimension/canvas/png-stubs.c
 create mode 100644 libdimension/canvas/png.c
 create mode 100644 libdimension/canvas/rgba.c
 delete mode 100644 libdimension/canvas_pigment.c
 delete mode 100644 libdimension/checker.c
 delete mode 100644 libdimension/compiler-internal.h
 create mode 100644 libdimension/concurrency/future.c
 create mode 100644 libdimension/concurrency/threads.c
 delete mode 100644 libdimension/cone.c
 delete mode 100644 libdimension/csg.c
 delete mode 100644 libdimension/cube.c
 delete mode 100644 libdimension/dictionary.c
 delete mode 100644 libdimension/dimension-internal.h
 delete mode 100644 libdimension/dimension/array.h
 create mode 100644 libdimension/dimension/base.h
 create mode 100644 libdimension/dimension/base/array.h
 create mode 100644 libdimension/dimension/base/common.h
 create mode 100644 libdimension/dimension/base/compiler.h
 create mode 100644 libdimension/dimension/base/dictionary.h
 create mode 100644 libdimension/dimension/base/error.h
 create mode 100644 libdimension/dimension/base/malloc.h
 create mode 100644 libdimension/dimension/base/pool.h
 delete mode 100644 libdimension/dimension/camera.h
 delete mode 100644 libdimension/dimension/cameras.h
 create mode 100644 libdimension/dimension/canvas/canvas.h
 create mode 100644 libdimension/dimension/canvas/gl.h
 create mode 100644 libdimension/dimension/canvas/png.h
 create mode 100644 libdimension/dimension/color/color.h
 create mode 100644 libdimension/dimension/color/tcolor.h
 delete mode 100644 libdimension/dimension/common.h
 delete mode 100644 libdimension/dimension/compiler.h
 create mode 100644 libdimension/dimension/concurrency.h
 create mode 100644 libdimension/dimension/concurrency/future.h
 delete mode 100644 libdimension/dimension/csg.h
 delete mode 100644 libdimension/dimension/dictionary.h
 delete mode 100644 libdimension/dimension/error.h
 delete mode 100644 libdimension/dimension/finish.h
 delete mode 100644 libdimension/dimension/finishes.h
 delete mode 100644 libdimension/dimension/future.h
 delete mode 100644 libdimension/dimension/geometry.h
 delete mode 100644 libdimension/dimension/gl.h
 delete mode 100644 libdimension/dimension/interior.h
 delete mode 100644 libdimension/dimension/light.h
 delete mode 100644 libdimension/dimension/lights.h
 delete mode 100644 libdimension/dimension/malloc.h
 delete mode 100644 libdimension/dimension/map.h
 create mode 100644 libdimension/dimension/math/aabb.h
 create mode 100644 libdimension/dimension/math/matrix.h
 create mode 100644 libdimension/dimension/math/ray.h
 create mode 100644 libdimension/dimension/math/scalar.h
 create mode 100644 libdimension/dimension/math/vector.h
 create mode 100644 libdimension/dimension/model.h
 create mode 100644 libdimension/dimension/model/camera.h
 create mode 100644 libdimension/dimension/model/cameras.h
 create mode 100644 libdimension/dimension/model/csg.h
 create mode 100644 libdimension/dimension/model/finish.h
 create mode 100644 libdimension/dimension/model/finishes.h
 create mode 100644 libdimension/dimension/model/interior.h
 create mode 100644 libdimension/dimension/model/light.h
 create mode 100644 libdimension/dimension/model/lights.h
 create mode 100644 libdimension/dimension/model/object.h
 create mode 100644 libdimension/dimension/model/objects.h
 create mode 100644 libdimension/dimension/model/pigment.h
 create mode 100644 libdimension/dimension/model/pigments.h
 create mode 100644 libdimension/dimension/model/scene.h
 create mode 100644 libdimension/dimension/model/texture.h
 delete mode 100644 libdimension/dimension/object.h
 delete mode 100644 libdimension/dimension/objects.h
 create mode 100644 libdimension/dimension/pattern/map.h
 create mode 100644 libdimension/dimension/pattern/pattern.h
 create mode 100644 libdimension/dimension/pattern/patterns.h
 delete mode 100644 libdimension/dimension/patterns.h
 delete mode 100644 libdimension/dimension/pigment.h
 delete mode 100644 libdimension/dimension/pigments.h
 create mode 100644 libdimension/dimension/platform.h
 create mode 100644 libdimension/dimension/platform/timer.h
 delete mode 100644 libdimension/dimension/png.h
 delete mode 100644 libdimension/dimension/polynomial.h
 delete mode 100644 libdimension/dimension/pool.h
 delete mode 100644 libdimension/dimension/ray_trace.h
 create mode 100644 libdimension/dimension/render.h
 create mode 100644 libdimension/dimension/render/render.h
 delete mode 100644 libdimension/dimension/scene.h
 delete mode 100644 libdimension/dimension/tcolor.h
 delete mode 100644 libdimension/dimension/texture.h
 delete mode 100644 libdimension/dimension/timer.h
 delete mode 100644 libdimension/error.c
 delete mode 100644 libdimension/finish.c
 delete mode 100644 libdimension/future-internal.h
 delete mode 100644 libdimension/future.c
 delete mode 100644 libdimension/geometry.c
 delete mode 100644 libdimension/gl-stubs.c
 delete mode 100644 libdimension/gl.c
 delete mode 100644 libdimension/gradient.c
 delete mode 100644 libdimension/inline.c
 delete mode 100644 libdimension/interior.c
 create mode 100644 libdimension/internal.h
 create mode 100644 libdimension/internal/bvh.h
 create mode 100644 libdimension/internal/compiler.h
 create mode 100644 libdimension/internal/concurrency.h
 create mode 100644 libdimension/internal/future.h
 create mode 100644 libdimension/internal/platform.h
 create mode 100644 libdimension/internal/polynomial.h
 create mode 100644 libdimension/internal/profile.h
 create mode 100644 libdimension/internal/prtree.h
 create mode 100644 libdimension/internal/rgba.h
 delete mode 100644 libdimension/lambertian.c
 delete mode 100644 libdimension/leopard.c
 delete mode 100644 libdimension/light.c
 delete mode 100644 libdimension/malloc.c
 delete mode 100644 libdimension/map.c
 create mode 100644 libdimension/math/matrix.c
 create mode 100644 libdimension/math/polynomial.c
 create mode 100644 libdimension/model/camera.c
 create mode 100644 libdimension/model/cameras/perspective.c
 create mode 100644 libdimension/model/finish.c
 create mode 100644 libdimension/model/finishes/lambertian.c
 create mode 100644 libdimension/model/finishes/phong.c
 create mode 100644 libdimension/model/finishes/reflection.c
 create mode 100644 libdimension/model/interior.c
 create mode 100644 libdimension/model/light.c
 create mode 100644 libdimension/model/lights/point_light.c
 create mode 100644 libdimension/model/object.c
 create mode 100644 libdimension/model/objects/cone.c
 create mode 100644 libdimension/model/objects/csg.c
 create mode 100644 libdimension/model/objects/cube.c
 create mode 100644 libdimension/model/objects/plane.c
 create mode 100644 libdimension/model/objects/sphere.c
 create mode 100644 libdimension/model/objects/torus.c
 create mode 100644 libdimension/model/objects/triangle.c
 create mode 100644 libdimension/model/objects/triangle_fan.c
 create mode 100644 libdimension/model/pigment.c
 create mode 100644 libdimension/model/pigments/canvas_pigment.c
 create mode 100644 libdimension/model/pigments/pigment_map.c
 create mode 100644 libdimension/model/pigments/solid_pigment.c
 create mode 100644 libdimension/model/scene.c
 create mode 100644 libdimension/model/texture.c
 delete mode 100644 libdimension/object.c
 delete mode 100644 libdimension/pattern.c
 create mode 100644 libdimension/pattern/checker.c
 create mode 100644 libdimension/pattern/gradient.c
 create mode 100644 libdimension/pattern/leopard.c
 create mode 100644 libdimension/pattern/map.c
 create mode 100644 libdimension/pattern/pattern.c
 delete mode 100644 libdimension/perspective.c
 delete mode 100644 libdimension/phong.c
 delete mode 100644 libdimension/pigment.c
 delete mode 100644 libdimension/pigment_map.c
 delete mode 100644 libdimension/plane.c
 delete mode 100644 libdimension/platform.c
 delete mode 100644 libdimension/platform.h
 create mode 100644 libdimension/platform/platform.c
 create mode 100644 libdimension/platform/timer.c
 delete mode 100644 libdimension/png-stubs.c
 delete mode 100644 libdimension/png.c
 delete mode 100644 libdimension/point_light.c
 delete mode 100644 libdimension/polynomial.c
 delete mode 100644 libdimension/pool.c
 delete mode 100644 libdimension/profile.c
 delete mode 100644 libdimension/profile.h
 delete mode 100644 libdimension/prtree.c
 delete mode 100644 libdimension/prtree.h
 delete mode 100644 libdimension/ray_trace.c
 delete mode 100644 libdimension/reflection.c
 create mode 100644 libdimension/render/render.c
 delete mode 100644 libdimension/rgba.c
 delete mode 100644 libdimension/rgba.h
 delete mode 100644 libdimension/scene.c
 delete mode 100644 libdimension/solid_pigment.c
 delete mode 100644 libdimension/sphere.c
 delete mode 100644 libdimension/texture.c
 delete mode 100644 libdimension/threads.c
 delete mode 100644 libdimension/threads.h
 delete mode 100644 libdimension/timer.c
 delete mode 100644 libdimension/torus.c
 delete mode 100644 libdimension/triangle.c
 delete mode 100644 libdimension/triangle_fan.c

(limited to 'libdimension')

diff --git a/libdimension/Makefile.am b/libdimension/Makefile.am
index c488016..5c14363 100644
--- a/libdimension/Makefile.am
+++ b/libdimension/Makefile.am
@@ -17,129 +17,144 @@
 ## along with this program.  If not, see <http://www.gnu.org/licenses/>. ##
 ###########################################################################
 
-SUBDIRS = .                                                                    \
-          bench                                                                \
-          tests
+SUBDIRS = . \
+  bench \
+  tests
 
 AM_CFLAGS = $(PTHREAD_CFLAGS)
 # Make dmnsn_error() backtraces useful
 AM_LDFLAGS = $(PTHREAD_LIBS) -rdynamic
 CC = $(PTHREAD_CC)
 
-nobase_include_HEADERS = dimension.h                                           \
-                         dimension/array.h                                     \
-                         dimension/camera.h                                    \
-                         dimension/cameras.h                                   \
-                         dimension/canvas.h                                    \
-                         dimension/color.h                                     \
-                         dimension/compiler.h                                  \
-                         dimension/csg.h                                       \
-                         dimension/dictionary.h                                \
-                         dimension/error.h                                     \
-                         dimension/finish.h                                    \
-                         dimension/finishes.h                                  \
-                         dimension/future.h                                    \
-                         dimension/geometry.h                                  \
-                         dimension/gl.h                                        \
-                         dimension/interior.h                                  \
-                         dimension/light.h                                     \
-                         dimension/lights.h                                    \
-                         dimension/malloc.h                                    \
-                         dimension/map.h                                       \
-                         dimension/object.h                                    \
-                         dimension/objects.h                                   \
-                         dimension/pattern.h                                   \
-                         dimension/patterns.h                                  \
-                         dimension/pigment.h                                   \
-                         dimension/pigments.h                                  \
-                         dimension/png.h                                       \
-                         dimension/polynomial.h                                \
-                         dimension/pool.h                                      \
-                         dimension/ray_trace.h                                 \
-                         dimension/scene.h                                     \
-                         dimension/tcolor.h                                    \
-                         dimension/texture.h                                   \
-                         dimension/timer.h
+nobase_include_HEADERS = dimension.h \
+  dimension/base.h \
+  dimension/base/compiler.h \
+  dimension/base/common.h \
+  dimension/base/error.h \
+  dimension/base/malloc.h \
+  dimension/base/pool.h \
+  dimension/base/array.h \
+  dimension/base/dictionary.h \
+  dimension/concurrency.h \
+  dimension/concurrency/future.h \
+  dimension/platform.h \
+  dimension/platform/timer.h \
+  dimension/math.h \
+  dimension/math/aabb.h \
+  dimension/math/matrix.h \
+  dimension/math/ray.h \
+  dimension/math/scalar.h \
+  dimension/math/vector.h \
+  dimension/color.h \
+  dimension/color/color.h \
+  dimension/color/tcolor.h \
+  dimension/canvas.h \
+  dimension/canvas/canvas.h \
+  dimension/canvas/gl.h \
+  dimension/canvas/png.h \
+  dimension/pattern.h \
+  dimension/pattern/pattern.h \
+  dimension/pattern/patterns.h \
+  dimension/pattern/map.h \
+  dimension/model.h \
+  dimension/model/camera.h \
+  dimension/model/cameras.h \
+  dimension/model/csg.h \
+  dimension/model/finishes.h \
+  dimension/model/finish.h \
+  dimension/model/interior.h \
+  dimension/model/light.h \
+  dimension/model/lights.h \
+  dimension/model/object.h \
+  dimension/model/objects.h \
+  dimension/model/pigment.h \
+  dimension/model/pigments.h \
+  dimension/model/scene.h \
+  dimension/model/texture.h \
+  dimension/render.h \
+  dimension/render/render.h
 
 lib_LTLIBRARIES = libdimension.la
 
-libdimension_la_SOURCES = $(nobase_include_HEADERS)                            \
-                          array.c                                              \
-                          bvh.c                                                \
-                          bvh.h                                                \
-                          camera.c                                             \
-                          canvas.c                                             \
-                          canvas_pigment.c                                     \
-                          checker.c                                            \
-                          compiler-internal.h                                  \
-                          cone.c                                               \
-                          cube.c                                               \
-                          csg.c                                                \
-                          dictionary.c                                         \
-                          dimension-internal.h                                 \
-                          error.c                                              \
-                          finish.c                                             \
-                          future.c                                             \
-                          future-internal.h                                    \
-                          geometry.c                                           \
-                          gradient.c                                           \
-                          inline.c                                             \
-                          interior.c                                           \
-                          lambertian.c                                         \
-                          leopard.c                                            \
-                          light.c                                              \
-                          malloc.c                                             \
-                          map.c                                                \
-                          object.c                                             \
-                          pattern.c                                            \
-                          perspective.c                                        \
-                          phong.c                                              \
-                          pigment.c                                            \
-                          pigment_map.c                                        \
-                          plane.c                                              \
-                          platform.c                                           \
-                          platform.h                                           \
-                          profile.h                                            \
-                          point_light.c                                        \
-                          polynomial.c                                         \
-                          pool.c                                               \
-                          prtree.c                                             \
-                          prtree.h                                             \
-                          ray_trace.c                                          \
-                          reflection.c                                         \
-                          rgba.c                                               \
-                          rgba.h                                               \
-                          scene.c                                              \
-                          solid_pigment.c                                      \
-                          sphere.c                                             \
-                          texture.c                                            \
-                          threads.c                                            \
-                          threads.h                                            \
-                          timer.c                                              \
-                          torus.c                                              \
-                          triangle.c                                           \
-                          triangle_fan.c
+libdimension_la_SOURCES = $(nobase_include_HEADERS) \
+  base/array.c \
+  base/dictionary.c \
+  base/error.c \
+  base/inline.c \
+  base/malloc.c \
+  base/pool.c \
+  bvh/bvh.c \
+  bvh/prtree.c \
+  canvas/canvas.c \
+  canvas/rgba.c \
+  concurrency/future.c \
+  concurrency/threads.c \
+  dimension.h \
+  internal.h \
+  internal/bvh.h \
+  internal/compiler.h \
+  internal/future.h \
+  internal/platform.h \
+  internal/polynomial.h \
+  internal/profile.h \
+  internal/prtree.h \
+  internal/rgba.h \
+  internal/threads.h \
+  math/matrix.c \
+  math/polynomial.c \
+  model/camera.c \
+  model/cameras/perspective.c \
+  model/finish.c \
+  model/finishes/lambertian.c \
+  model/finishes/phong.c \
+  model/finishes/reflection.c \
+  model/interior.c \
+  model/light.c \
+  model/lights/point_light.c \
+  model/object.c \
+  model/objects/cone.c \
+  model/objects/csg.c \
+  model/objects/cube.c \
+  model/objects/plane.c \
+  model/objects/sphere.c \
+  model/objects/torus.c \
+  model/objects/triangle.c \
+  model/objects/triangle_fan.c \
+  model/pigment.c \
+  model/pigments/canvas_pigment.c \
+  model/pigments/pigment_map.c \
+  model/pigments/solid_pigment.c \
+  model/scene.c \
+  model/texture.c \
+  pattern/checker.c \
+  pattern/gradient.c \
+  pattern/leopard.c \
+  pattern/map.c \
+  pattern/pattern.c \
+  platform/platform.c \
+  platform/timer.c \
+  render/render.c
 libdimension_la_CFLAGS  = $(AM_CFLAGS)
 libdimension_la_LDFLAGS = -version-info 0:0:0 -no-undefined $(AM_LDFLAGS)
 libdimension_la_LIBADD  =
 
 if PNG
-libdimension_la_SOURCES += png.c
+libdimension_la_SOURCES += canvas/png.c
 libdimension_la_CFLAGS  += $(libpng_CFLAGS)
 libdimension_la_LIBADD  += $(libpng_LIBS)
 else
-libdimension_la_SOURCES += png-stubs.c
+libdimension_la_SOURCES += canvas/png-stubs.c
 endif
 
 if GL
-libdimension_la_SOURCES += gl.c
+libdimension_la_SOURCES += canvas/gl.c
 libdimension_la_LIBADD  += -lGL
 else
-libdimension_la_SOURCES += gl-stubs.c
+libdimension_la_SOURCES += canvas/gl-stubs.c
 endif
 
 if PROFILE
-libdimension_la_SOURCES += profile.c
+libdimension_la_SOURCES += base/profile.c
 endif
 
 pkgconfigdir = $(libdir)/pkgconfig
diff --git a/libdimension/array.c b/libdimension/array.c
deleted file mode 100644
index deea035..0000000
--- a/libdimension/array.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Non-inline array functions.
- */
-
-#include "dimension.h"
-
-void
-dmnsn_array_cleanup(void *ptr)
-{
-  dmnsn_array *array = ptr;
-  dmnsn_free(array->ptr);
-}
diff --git a/libdimension/base/array.c b/libdimension/base/array.c
new file mode 100644
index 0000000..d8faed7
--- /dev/null
+++ b/libdimension/base/array.c
@@ -0,0 +1,33 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Non-inline array functions.
+ */
+
+#include "dimension/base.h"
+
+void
+dmnsn_array_cleanup(void *ptr)
+{
+  dmnsn_array *array = ptr;
+  dmnsn_free(array->ptr);
+}
diff --git a/libdimension/base/dictionary.c b/libdimension/base/dictionary.c
new file mode 100644
index 0000000..6e99abd
--- /dev/null
+++ b/libdimension/base/dictionary.c
@@ -0,0 +1,228 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Associative arrays, implemented with PATRICIA tries.
+ */
+
+#include "dimension/base.h"
+
+struct dmnsn_dictionary {
+  size_t obj_size;       ///< The size of the objects in the trie.
+  char *prefix;          ///< The local string prefix of the current node.
+  void *value;           ///< The node's stored object, if it's a leaf.
+  dmnsn_array *children; ///< The node's children.
+};
+
+dmnsn_dictionary *
+dmnsn_new_dictionary(size_t obj_size)
+{
+  dmnsn_dictionary *dict = DMNSN_MALLOC(dmnsn_dictionary);
+  dict->obj_size = obj_size;
+  dict->prefix   = dmnsn_strdup("");
+  dict->value    = NULL;
+  dict->children = DMNSN_NEW_ARRAY(dmnsn_dictionary *);
+  return dict;
+}
+
+void
+dmnsn_delete_dictionary(dmnsn_dictionary *dict)
+{
+  if (dict) {
+    dmnsn_free(dict->prefix);
+    dmnsn_free(dict->value);
+    DMNSN_ARRAY_FOREACH (dmnsn_dictionary **, subtrie, dict->children) {
+      dmnsn_delete_dictionary(*subtrie);
+    }
+    dmnsn_delete_array(dict->children);
+
+    dmnsn_free(dict);
+  }
+}
+
+bool
+dmnsn_dictionary_get(const dmnsn_dictionary *dict, const char *key, void *obj)
+{
+  const void *value = dmnsn_dictionary_at(dict, key);
+  if (value) {
+    memcpy(obj, value, dict->obj_size);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+void *
+dmnsn_dictionary_at(const dmnsn_dictionary *dict, const char *key)
+{
+  // PATRICIA trie search: O(k), where k is the length of the longest string in
+  // the trie.
+
+  size_t len = strlen(dict->prefix);
+  if (strncmp(key, dict->prefix, len) != 0)
+    return NULL;
+  key += len;
+
+  while (true) {
+    if (*key == '\0' && dict->value) {
+      return dict->value;
+    } else {
+      dmnsn_dictionary **first = dmnsn_array_first(dict->children), **subtrie;
+      ptrdiff_t size = dmnsn_array_size(dict->children);
+      for (subtrie = first; subtrie - first < size; ++subtrie) {
+        len = strlen((*subtrie)->prefix);
+        if (strncmp(key, (*subtrie)->prefix, len) == 0) {
+          dict = *subtrie;
+          key += len;
+          break;
+        }
+      }
+
+      if (subtrie - first == size)
+        break;
+    }
+  }
+
+  return NULL;
+}
+
+void
+dmnsn_dictionary_insert(dmnsn_dictionary *dict, const char *key,
+                        const void *obj)
+{
+  // PATRICIA trie insertion: O(k), where k is the length of the longest string
+  // in the trie.
+
+  while (true) {
+    if (dict->prefix[0] == '\0' && !dict->value
+        && dmnsn_array_size(dict->children) == 0)
+    {
+      // Replace an empty tree with a single-element tree
+      dict->prefix = dmnsn_realloc(dict->prefix, strlen(key) + 1);
+      strcpy(dict->prefix, key);
+
+      dict->value = dmnsn_malloc(dict->obj_size);
+      memcpy(dict->value, obj, dict->obj_size);
+      break;
+    }
+
+    char *prefix = dict->prefix;
+    while (*prefix == *key && *prefix && *key) {
+      ++prefix;
+      ++key;
+    }
+
+    if (*key == '\0' && *prefix == '\0') {
+      // Complete match
+      if (!dict->value) {
+        dict->value = dmnsn_malloc(dict->obj_size);
+      }
+      memcpy(dict->value, obj, dict->obj_size);
+      break;
+    } else if (*prefix == '\0') {
+      // Partial match; key starts with prefix
+      dmnsn_dictionary **first = dmnsn_array_first(dict->children), **subtrie;
+      ptrdiff_t size = dmnsn_array_size(dict->children);
+      for (subtrie = first; subtrie - first < size; ++subtrie) {
+        if ((*subtrie)->prefix[0] == key[0]) {
+          dict = *subtrie;
+          break;
+        }
+      }
+
+      if (subtrie - first == size) {
+        // No submatch found, add a new child
+        dmnsn_dictionary *child = dmnsn_new_dictionary(dict->obj_size);
+        dmnsn_array_push(dict->children, &child);
+        dict = child;
+      }
+    } else {
+      // Split the tree
+      dmnsn_dictionary *copy = dmnsn_new_dictionary(dict->obj_size);
+      copy->prefix = dmnsn_realloc(copy->prefix, strlen(prefix) + 1);
+      strcpy(copy->prefix, prefix);
+      *prefix = '\0';
+
+      copy->value = dict->value;
+      dict->value = NULL;
+
+      dmnsn_array *temp = copy->children;
+      copy->children = dict->children;
+      dict->children = temp;
+
+      dmnsn_dictionary *subtrie = dmnsn_new_dictionary(dict->obj_size);
+      dmnsn_array_push(dict->children, &copy);
+      dmnsn_array_push(dict->children, &subtrie);
+      dict = subtrie;
+    }
+  }
+}
+
+bool
+dmnsn_dictionary_remove(dmnsn_dictionary *dict, const char *key)
+{
+  // PATRICIA trie removal: O(k), where k is the length of the longest string
+  // in the trie.
+
+  // This implementation doesn't actually collapse the tree back upwards if a
+  // node is left with only one child, to reduce complexity.
+
+  size_t len = strlen(dict->prefix);
+  if (strncmp(key, dict->prefix, len) != 0)
+    return false;
+  key += len;
+
+  while (true) {
+    if (*key == '\0' && dict->value) {
+      dmnsn_free(dict->value);
+      dict->value = NULL;
+      return true;
+    } else {
+      dmnsn_dictionary **first = dmnsn_array_first(dict->children), **subtrie;
+      ptrdiff_t size = dmnsn_array_size(dict->children);
+      for (subtrie = first; subtrie - first < size; ++subtrie) {
+        len = strlen((*subtrie)->prefix);
+        if (strncmp(key, (*subtrie)->prefix, len) == 0) {
+          dict = *subtrie;
+          key += len;
+          break;
+        }
+      }
+
+      if (subtrie - first == size)
+        break;
+    }
+  }
+
+  return false;
+}
+
+void
+dmnsn_dictionary_apply(dmnsn_dictionary *dict, dmnsn_callback_fn *callback)
+{
+  if (dict->value) {
+    callback(dict->value);
+  }
+
+  DMNSN_ARRAY_FOREACH (dmnsn_dictionary **, subtrie, dict->children) {
+    dmnsn_dictionary_apply(*subtrie, callback);
+  }
+}
diff --git a/libdimension/base/error.c b/libdimension/base/error.c
new file mode 100644
index 0000000..6b8d18e
--- /dev/null
+++ b/libdimension/base/error.c
@@ -0,0 +1,144 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Error handling.
+ */
+
+#include "internal.h"
+#include "internal/platform.h"
+#include <errno.h>
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/// Report internal errors in this file.
+#define DMNSN_LOCAL_ERROR(str)                          \
+  do {                                                  \
+    fprintf(stderr, "Dimension ERROR: %s, %s:%u: %s\n", \
+            DMNSN_FUNC, __FILE__, __LINE__, (str));     \
+    abort();                                            \
+  } while (0)
+
+/// The default fatal error handler.
+static void dmnsn_default_fatal_error_fn(void);
+
+/// The current fatal error handler.
+static atomic(dmnsn_fatal_error_fn *) dmnsn_fatal = ATOMIC_VAR_INIT(dmnsn_default_fatal_error_fn);
+
+/// The current resilience.
+static atomic_bool dmnsn_always_die = ATOMIC_VAR_INIT(false);
+
+void
+dmnsn_report_impl(bool die, const char *func, const char *file, unsigned int line, const char *str)
+{
+  // Save the value of errno
+  int err = errno;
+
+  bool always_die = atomic_load(&dmnsn_always_die);
+
+  // Print the diagnostic string
+  fprintf(stderr, "Dimension %s: %s, %s:%u: %s\n",
+          die ? "ERROR" : "WARNING", func, file, line, str);
+
+  // Print the value of errno
+  if (err != 0) {
+    fprintf(stderr, "Last error: %d", err);
+#if DMNSN_STRERROR_R
+    char errbuf[256];
+    if (strerror_r(err, errbuf, 256) == 0) {
+      fprintf(stderr, " (%s)", errbuf);
+    }
+#elif DMNSN_SYS_ERRLIST
+    if (err >= 0 && err < sys_nerr) {
+      fprintf(stderr, " (%s)", sys_errlist[err]);
+    }
+#endif
+    fprintf(stderr, "\n");
+  }
+
+  // Print a stack trace to standard error
+  dmnsn_backtrace(stderr);
+
+  // Call the fatal error handler if needed
+  if (die || always_die) {
+    static __thread bool thread_exiting = false;
+
+    if (thread_exiting) {
+      if (die) {
+        // Prevent infinite recursion if the fatal error function itself calls
+        // dmnsn_error() (not dmnsn_warning())
+        DMNSN_LOCAL_ERROR("Error raised while in error handler, aborting.");
+      }
+    } else {
+      thread_exiting = true;
+
+      dmnsn_fatal_error_fn *fatal = dmnsn_get_fatal_error_fn();
+      fatal();
+      DMNSN_LOCAL_ERROR("Fatal error handler didn't exit.");
+    }
+  }
+}
+
+void
+dmnsn_report_warning(const char *func, const char *file, unsigned int line, const char *str)
+{
+  dmnsn_report_impl(false, func, file, line, str);
+}
+
+DMNSN_NORETURN
+dmnsn_report_error(const char *func, const char *file, unsigned int line, const char *str)
+{
+  dmnsn_report_impl(true, func, file, line, str);
+  DMNSN_UNREACHABLE();
+}
+
+void
+dmnsn_die_on_warnings(bool always_die)
+{
+  atomic_store(&dmnsn_always_die, always_die);
+}
+
+dmnsn_fatal_error_fn *
+dmnsn_get_fatal_error_fn(void)
+{
+  dmnsn_fatal_error_fn *fatal = atomic_load(&dmnsn_fatal);
+  return fatal;
+}
+
+void
+dmnsn_set_fatal_error_fn(dmnsn_fatal_error_fn *fatal)
+{
+  atomic_store(&dmnsn_fatal, fatal);
+}
+
+static void
+dmnsn_default_fatal_error_fn(void)
+{
+  if (dmnsn_is_main_thread()) {
+    exit(EXIT_FAILURE);
+  } else {
+    pthread_exit(NULL);
+  }
+}
diff --git a/libdimension/base/inline.c b/libdimension/base/inline.c
new file mode 100644
index 0000000..b0622fe
--- /dev/null
+++ b/libdimension/base/inline.c
@@ -0,0 +1,43 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Emit definitions of inline functions, if necessary.
+ */
+
+// Set DMNSN_INLINE to produce definitions of inline functions, emitted here,
+// if needed
+#ifdef __cplusplus
+  // C++ inline semantics
+  #define DMNSN_INLINE inline
+#elif __STDC_VERSION__ >= 199901L
+  // C99 inline semantics
+  #define DMNSN_INLINE
+#elif defined(__GNUC__)
+  // GCC inline semantics
+  #define DMNSN_INLINE __inline__
+#else
+  // Unknown C - mark functions static and hope the compiler is smart enough to
+  // inline them
+  #define DMNSN_INLINE static
+#endif
+
+#include "dimension.h"
diff --git a/libdimension/base/malloc.c b/libdimension/base/malloc.c
new file mode 100644
index 0000000..6aaf68c
--- /dev/null
+++ b/libdimension/base/malloc.c
@@ -0,0 +1,94 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Dynamic memory.
+ */
+
+#include "internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdatomic.h>
+
+#if DMNSN_DEBUG
+static atomic_size_t dmnsn_allocs = ATOMIC_VAR_INIT(0);
+#endif
+
+void *
+dmnsn_malloc(size_t size)
+{
+  void *ptr = malloc(size);
+  if (!ptr) {
+    dmnsn_error("Memory allocation failed.");
+  }
+
+#if DMNSN_DEBUG
+  atomic_fetch_add(&dmnsn_allocs, 1);
+#endif
+
+  return ptr;
+}
+
+void *
+dmnsn_realloc(void *ptr, size_t size)
+{
+#if DMNSN_DEBUG
+  if (!ptr) {
+    atomic_fetch_add(&dmnsn_allocs, 1);
+  }
+#endif
+
+  ptr = realloc(ptr, size);
+  if (!ptr) {
+    dmnsn_error("Memory allocation failed.");
+  }
+  return ptr;
+}
+
+char *
+dmnsn_strdup(const char *s)
+{
+  char *copy = dmnsn_malloc(strlen(s) + 1);
+  strcpy(copy, s);
+  return copy;
+}
+
+void
+dmnsn_free(void *ptr)
+{
+#if DMNSN_DEBUG
+  if (ptr) {
+    atomic_fetch_sub(&dmnsn_allocs, 1);
+  }
+#endif
+
+  free(ptr);
+}
+
+#if DMNSN_DEBUG
+DMNSN_LATE_DESTRUCTOR static void
+dmnsn_leak_check(void)
+{
+  if (atomic_load_explicit(&dmnsn_allocs, memory_order_relaxed) > 0) {
+    dmnsn_warning("Leaking memory.");
+  }
+}
+#endif
diff --git a/libdimension/base/pool.c b/libdimension/base/pool.c
new file mode 100644
index 0000000..4bfdd7d
--- /dev/null
+++ b/libdimension/base/pool.c
@@ -0,0 +1,177 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Memory pool implementation.
+ */
+
+#include "internal.h"
+#include "internal/concurrency.h"
+#include <stdatomic.h>
+
+/// Number of pointers per block, we want a block to fit in a single page.
+#define DMNSN_POOL_BLOCK_SIZE (4096/sizeof(void *) - 4)
+/// Number of pointers per tidy block
+#define DMNSN_TIDY_BLOCK_SIZE ((4096 - 4*sizeof(void *))/(sizeof(void *) + sizeof(dmnsn_callback_fn *)))
+
+/// A single block in a thread-specific pool.
+typedef struct dmnsn_pool_block {
+  /// Current index into allocs[].
+  size_t i;
+  /// All allocations in the current block.
+  void *allocs[DMNSN_POOL_BLOCK_SIZE];
+  /// Tail pointer to the previous block in the global chain.
+  struct dmnsn_pool_block *prev;
+} dmnsn_pool_block;
+
+/// A single tidy block in a thread-specific pool.
+typedef struct dmnsn_tidy_block {
+  /// Current index into allocs[].
+  size_t i;
+  /// All allocations in the current block.
+  void *allocs[DMNSN_TIDY_BLOCK_SIZE];
+  /// All cleanup callbacks in the current block.
+  dmnsn_callback_fn *cleanup_fns[DMNSN_TIDY_BLOCK_SIZE];
+  /// Tail pointer to the previous tidy block in the global chain.
+  struct dmnsn_tidy_block *prev;
+} dmnsn_tidy_block;
+
+/// dmnsn_pool implementation.
+struct dmnsn_pool {
+  /// Thread-local regular block.
+  pthread_key_t thread_block;
+  /// Thread-local tidy block.
+  pthread_key_t thread_tidy_block;
+
+  /// Global chain of regular blocks.
+  atomic(dmnsn_pool_block *) chain;
+  /// Global chain of tidy blocks.
+  atomic(dmnsn_tidy_block *) tidy_chain;
+};
+
+dmnsn_pool *
+dmnsn_new_pool(void)
+{
+  dmnsn_pool *pool = DMNSN_MALLOC(dmnsn_pool);
+
+  dmnsn_key_create(&pool->thread_block, NULL);
+  dmnsn_key_create(&pool->thread_tidy_block, NULL);
+
+  atomic_store_explicit(&pool->chain, NULL, memory_order_relaxed);
+  atomic_store_explicit(&pool->tidy_chain, NULL, memory_order_relaxed);
+
+  return pool;
+}
+
+void *
+dmnsn_palloc(dmnsn_pool *pool, size_t size)
+{
+  dmnsn_pool_block *old_block = pthread_getspecific(pool->thread_block);
+
+  dmnsn_pool_block *new_block = old_block;
+  if (dmnsn_unlikely(!old_block || old_block->i == DMNSN_POOL_BLOCK_SIZE)) {
+    new_block = DMNSN_MALLOC(dmnsn_pool_block);
+    new_block->i = 0;
+  }
+
+  void *result = dmnsn_malloc(size);
+  new_block->allocs[new_block->i++] = result;
+
+  if (dmnsn_unlikely(new_block != old_block)) {
+    dmnsn_setspecific(pool->thread_block, new_block);
+
+    // Atomically update pool->chain
+    dmnsn_pool_block *old_chain = atomic_exchange(&pool->chain, new_block);
+    new_block->prev = old_chain;
+  }
+
+  return result;
+}
+
+void *
+dmnsn_palloc_tidy(dmnsn_pool *pool, size_t size, dmnsn_callback_fn *cleanup_fn)
+{
+  dmnsn_assert(cleanup_fn != NULL, "NULL cleanup_fn");
+
+  dmnsn_tidy_block *old_block = pthread_getspecific(pool->thread_tidy_block);
+
+  dmnsn_tidy_block *new_block = old_block;
+  if (dmnsn_unlikely(!old_block || old_block->i == DMNSN_TIDY_BLOCK_SIZE)) {
+    new_block = DMNSN_MALLOC(dmnsn_tidy_block);
+    new_block->i = 0;
+  }
+
+  void *result = dmnsn_malloc(size);
+
+  size_t i = new_block->i;
+  new_block->allocs[i] = result;
+  new_block->cleanup_fns[i] = cleanup_fn;
+  ++new_block->i;
+
+  if (dmnsn_unlikely(new_block != old_block)) {
+    dmnsn_setspecific(pool->thread_tidy_block, new_block);
+
+    // Atomically update pool->tidy_chain
+    dmnsn_tidy_block *old_chain = atomic_exchange(&pool->tidy_chain, new_block);
+    new_block->prev = old_chain;
+  }
+
+  return result;
+}
+
+void
+dmnsn_delete_pool(dmnsn_pool *pool)
+{
+  if (!pool) {
+    return;
+  }
+
+  dmnsn_pool_block *block = atomic_load_explicit(&pool->chain, memory_order_relaxed);
+  while (block) {
+    // Free all the allocations
+    for (size_t i = block->i; i-- > 0;) {
+      dmnsn_free(block->allocs[i]);
+    }
+
+    // Free the block itself and go to the previous one
+    dmnsn_pool_block *saved = block;
+    block = block->prev;
+    dmnsn_free(saved);
+  }
+
+  dmnsn_tidy_block *tidy_block = atomic_load_explicit(&pool->tidy_chain, memory_order_relaxed);
+  while (tidy_block) {
+    // Free all the allocations
+    for (size_t i = tidy_block->i; i-- > 0;) {
+      void *ptr = tidy_block->allocs[i];
+      tidy_block->cleanup_fns[i](ptr);
+      dmnsn_free(ptr);
+    }
+
+    // Free the block itself and go to the previous one
+    dmnsn_tidy_block *saved = tidy_block;
+    tidy_block = tidy_block->prev;
+    dmnsn_free(saved);
+  }
+
+  dmnsn_key_delete(pool->thread_tidy_block);
+  dmnsn_free(pool);
+}
diff --git a/libdimension/base/profile.c b/libdimension/base/profile.c
new file mode 100644
index 0000000..87f27c1
--- /dev/null
+++ b/libdimension/base/profile.c
@@ -0,0 +1,168 @@
+/*************************************************************************
+ * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Branch profile accounting.
+ */
+
+#include "internal.h"
+#include "internal/concurrency.h"
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdio.h>
+
+/// Information on one predicted branch.
+typedef struct {
+  char *location;
+  uint64_t predicted, branches;
+} dmnsn_branch;
+
+/// Count of mispredicted branches.
+static dmnsn_dictionary *dmnsn_profile = NULL;
+/// Mutex which protects \c dmnsn_profile.
+static pthread_mutex_t dmnsn_profile_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/// Thread-local count of mispredicted branches.
+static pthread_key_t dmnsn_thread_profile;
+/// Initialize the thread-specific pointer exactly once.
+static pthread_once_t dmnsn_thread_profile_once = PTHREAD_ONCE_INIT;
+
+/// Add thread-specific profile data to the global counts.
+static void
+dmnsn_profile_globalize(void *ptr)
+{
+  dmnsn_branch *branch = ptr;
+  dmnsn_branch *global = dmnsn_dictionary_at(dmnsn_profile, branch->location);
+  if (global) {
+    global->predicted += branch->predicted;
+    global->branches  += branch->branches;
+    dmnsn_free(branch->location);
+  } else {
+    dmnsn_dictionary_insert(dmnsn_profile, branch->location, branch);
+  }
+}
+
+/// Destructor function for thread-specific profile data.
+static void
+dmnsn_delete_thread_profile(void *ptr)
+{
+  dmnsn_dictionary *thread_profile = ptr;
+
+  dmnsn_lock_mutex(&dmnsn_profile_mutex);
+    dmnsn_dictionary_apply(thread_profile, dmnsn_profile_globalize);
+  dmnsn_unlock_mutex(&dmnsn_profile_mutex);
+
+  dmnsn_delete_dictionary(thread_profile);
+}
+
+/// Initialize the thread-specific pointer.
+static void
+dmnsn_initialize_thread_profile(void)
+{
+  dmnsn_key_create(&dmnsn_thread_profile, dmnsn_delete_thread_profile);
+
+  dmnsn_lock_mutex(&dmnsn_profile_mutex);
+    dmnsn_profile = dmnsn_new_dictionary(sizeof(dmnsn_branch));
+  dmnsn_unlock_mutex(&dmnsn_profile_mutex);
+}
+
+/// Get the thread-specific profile data.
+static dmnsn_dictionary *
+dmnsn_get_thread_profile(void)
+{
+  dmnsn_once(&dmnsn_thread_profile_once, dmnsn_initialize_thread_profile);
+  return pthread_getspecific(dmnsn_thread_profile);
+}
+
+/// Set the thread-specific profile data.
+static void
+dmnsn_set_thread_profile(dmnsn_dictionary *thread_profile)
+{
+  dmnsn_setspecific(dmnsn_thread_profile, thread_profile);
+}
+
+bool
+dmnsn_expect(bool result, bool expected, const char *func, const char *file,
+             unsigned int line)
+{
+  int size = snprintf(NULL, 0, "%s:%s:%u", file, func, line) + 1;
+  if (size < 1) {
+    dmnsn_error("sprintf() failed.");
+  }
+
+  char key[size];
+  if (snprintf(key, size, "%s:%s:%u", file, func, line) < 0) {
+    dmnsn_error("sprintf() failed.");
+  }
+
+  dmnsn_dictionary *thread_profile = dmnsn_get_thread_profile();
+  if (!thread_profile) {
+    thread_profile = dmnsn_new_dictionary(sizeof(dmnsn_branch));
+    dmnsn_set_thread_profile(thread_profile);
+  }
+
+  dmnsn_branch *branch = dmnsn_dictionary_at(thread_profile, key);
+  if (branch) {
+    ++branch->branches;
+    if (result == expected) {
+      ++branch->predicted;
+    }
+  } else {
+    dmnsn_branch new_branch = {
+      .location  = dmnsn_strdup(key),
+      .predicted = (result == expected) ? 1 : 0,
+      .branches  = 1
+    };
+    dmnsn_dictionary_insert(thread_profile, key, &new_branch);
+  }
+
+  return result;
+}
+
+static void
+dmnsn_print_bad_prediction(void *ptr)
+{
+  dmnsn_branch *branch = ptr;
+  double rate = ((double)branch->predicted)/branch->branches;
+  if (rate < 0.75 || branch->branches < 100000) {
+    fprintf(stderr,
+            "Bad branch prediction: %s: %" PRIu64 "/%" PRIu64 " (%g%%)\n",
+            branch->location, branch->predicted, branch->branches, 100.0*rate);
+  }
+
+  dmnsn_free(branch->location);
+}
+
+DMNSN_DESTRUCTOR static void
+dmnsn_print_bad_predictions(void)
+{
+  dmnsn_dictionary *thread_profile = dmnsn_get_thread_profile();
+  if (thread_profile) {
+    dmnsn_delete_thread_profile(thread_profile);
+    dmnsn_set_thread_profile(NULL);
+  }
+
+  dmnsn_lock_mutex(&dmnsn_profile_mutex);
+    dmnsn_dictionary_apply(dmnsn_profile, dmnsn_print_bad_prediction);
+    dmnsn_delete_dictionary(dmnsn_profile);
+    dmnsn_profile = NULL;
+  dmnsn_unlock_mutex(&dmnsn_profile_mutex);
+}
diff --git a/libdimension/bench/future.c b/libdimension/bench/future.c
index fd26363..c8fb529 100644
--- a/libdimension/bench/future.c
+++ b/libdimension/bench/future.c
@@ -17,9 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  *************************************************************************/
 
-#include "../platform.c"
-#include "../future.c"
-#include "../threads.c"
+#include "../platform/platform.c"
+#include "../concurrency/future.c"
+#include "../concurrency/threads.c"
 #include <sandglass.h>
 #include <stdlib.h>
 
diff --git a/libdimension/bench/geometry.c b/libdimension/bench/geometry.c
index 68c9885..59d27e3 100644
--- a/libdimension/bench/geometry.c
+++ b/libdimension/bench/geometry.c
@@ -17,17 +17,18 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  *************************************************************************/
 
-#include "dimension.h"
+#include "dimension/math.h"
 #include <sandglass.h>
 #include <stdlib.h>
+#include <stdio.h>
 
 int
 main(void)
 {
   dmnsn_vector vector, vector2;
   dmnsn_matrix matrix, matrix2;
-  dmnsn_line line;
-  dmnsn_bounding_box box;
+  dmnsn_ray ray;
+  dmnsn_aabb box;
   double result;
 
   sandglass_t sandglass;
@@ -74,19 +75,19 @@ main(void)
   });
   printf("dmnsn_rotation_matrix(): %ld\n", sandglass.grains);
 
-  // dmnsn_new_line()
+  // dmnsn_new_ray()
   vector2 = dmnsn_new_vector(3.0, 2.0, 1.0);
   sandglass_bench_fine(&sandglass, {
-    line = dmnsn_new_line(vector, vector2);
+    ray = dmnsn_new_ray(vector, vector2);
   });
-  printf("dmnsn_new_line(): %ld\n", sandglass.grains);
+  printf("dmnsn_new_ray(): %ld\n", sandglass.grains);
 
-  // dmnsn_new_bounding_box()
+  // dmnsn_new_aabb()
   vector2 = dmnsn_new_vector(3.0, 4.0, 5.0);
   sandglass_bench_fine(&sandglass, {
-    box = dmnsn_new_bounding_box(vector, vector2);
+    box = dmnsn_new_aabb(vector, vector2);
   });
-  printf("dmnsn_new_bounding_box(): %ld\n", sandglass.grains);
+  printf("dmnsn_new_aabb(): %ld\n", sandglass.grains);
 
   // dmnsn_vector_add()
   sandglass_bench_fine(&sandglass, {
@@ -175,23 +176,23 @@ main(void)
   });
   printf("dmnsn_transform_normal(): %ld\n", sandglass.grains);
 
-  // dmnsn_transform_line()
+  // dmnsn_transform_ray()
   sandglass_bench_fine(&sandglass, {
-    line = dmnsn_transform_line(matrix, line);
+    ray = dmnsn_transform_ray(matrix, ray);
   });
-  printf("dmnsn_transform_line(): %ld\n", sandglass.grains);
+  printf("dmnsn_transform_ray(): %ld\n", sandglass.grains);
 
-  // dmnsn_transform_bounding_box()
+  // dmnsn_transform_aabb()
   sandglass_bench_fine(&sandglass, {
-    box = dmnsn_transform_bounding_box(matrix, box);
+    box = dmnsn_transform_aabb(matrix, box);
   });
-  printf("dmnsn_transform_bounding_box(): %ld\n", sandglass.grains);
+  printf("dmnsn_transform_aabb(): %ld\n", sandglass.grains);
 
-  // dmnsn_line_point()
+  // dmnsn_ray_point()
   sandglass_bench_fine(&sandglass, {
-    vector = dmnsn_line_point(line, result);
+    vector = dmnsn_ray_point(ray, result);
   });
-  printf("dmnsn_line_point(): %ld\n", sandglass.grains);
+  printf("dmnsn_ray_point(): %ld\n", sandglass.grains);
 
   return EXIT_SUCCESS;
 }
diff --git a/libdimension/bench/polynomial.c b/libdimension/bench/polynomial.c
index bf3d2b0..33fd390 100644
--- a/libdimension/bench/polynomial.c
+++ b/libdimension/bench/polynomial.c
@@ -17,7 +17,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  *************************************************************************/
 
-#include "dimension.h"
+#define DMNSN_INLINE extern inline
+#include "../math/polynomial.c"
 #include <sandglass.h>
 #include <stdlib.h>
 
diff --git a/libdimension/bench/prtree.c b/libdimension/bench/prtree.c
index 6c5d3ef..6b5e7c1 100644
--- a/libdimension/bench/prtree.c
+++ b/libdimension/bench/prtree.c
@@ -17,19 +17,19 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  *************************************************************************/
 
-#include "../platform.c"
-#include "../threads.c"
-#include "../future.c"
-#include "../bvh.c"
-#include "../prtree.c"
+#include "../platform/platform.c"
+#include "../concurrency/threads.c"
+#include "../concurrency/future.c"
+#include "../bvh/bvh.c"
+#include "../bvh/prtree.c"
 #include <sandglass.h>
 #include <stdlib.h>
 
 static bool
-dmnsn_fake_intersection_fn(const dmnsn_object *object, dmnsn_line line,
+dmnsn_fake_intersection_fn(const dmnsn_object *object, dmnsn_ray ray,
                            dmnsn_intersection *intersection)
 {
-  intersection->t = (object->bounding_box.min.z - line.x0.z)/line.n.z;
+  intersection->t = (object->aabb.min.z - ray.x0.z)/ray.n.z;
   intersection->normal = dmnsn_x;
   return true;
 }
@@ -40,7 +40,7 @@ dmnsn_fake_inside_fn(const dmnsn_object *object, dmnsn_vector point)
   return true;
 }
 
-static dmnsn_bounding_box
+static dmnsn_aabb
 dmnsn_fake_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
 {
   dmnsn_vector a, b;
@@ -53,7 +53,7 @@ dmnsn_fake_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
   b.y = 2.0*((double)rand())/RAND_MAX - 1.0;
   b.z = 2.0*((double)rand())/RAND_MAX - 1.0;
 
-  return dmnsn_new_bounding_box(dmnsn_vector_min(a, b), dmnsn_vector_max(a, b));
+  return dmnsn_new_aabb(dmnsn_vector_min(a, b), dmnsn_vector_max(a, b));
 }
 
 static dmnsn_object_vtable dmnsn_fake_vtable = {
@@ -99,7 +99,7 @@ main(void)
   printf("dmnsn_new_bvh(DMNSN_BVH_PRTREE): %ld\n", sandglass.grains);
 
   // dmnsn_bvh_intersection()
-  dmnsn_line ray = dmnsn_new_line(
+  dmnsn_ray ray = dmnsn_new_ray(
     dmnsn_new_vector( 1.0,  1.0, -2.0),
     dmnsn_new_vector(-0.5, -0.5,  1.0)
   );
diff --git a/libdimension/bench/triangle.c b/libdimension/bench/triangle.c
index b2b91d0..4a5a456 100644
--- a/libdimension/bench/triangle.c
+++ b/libdimension/bench/triangle.c
@@ -43,21 +43,21 @@ main(void)
   dmnsn_object_precompute(triangle);
 
   dmnsn_intersection intersection;
-  dmnsn_line line;
+  dmnsn_ray ray;
   bool intersected;
 
   // Intersecting case
-  line = dmnsn_new_line(dmnsn_new_vector(2.0, 1.0, -1.0), dmnsn_z);
+  ray = dmnsn_new_ray(dmnsn_new_vector(2.0, 1.0, -1.0), dmnsn_z);
   sandglass_bench_fine(&sandglass, {
-    intersected = dmnsn_object_intersection(triangle, line, &intersection);
+    intersected = dmnsn_object_intersection(triangle, ray, &intersection);
   });
   dmnsn_assert(intersected, "Didn't intersect");
   printf("dmnsn_triangle_intersection(true): %ld\n", sandglass.grains);
 
   // Non-intersecting case
-  line = dmnsn_new_line(dmnsn_new_vector(3.0, 3.0, -1.0), dmnsn_z);
+  ray = dmnsn_new_ray(dmnsn_new_vector(3.0, 3.0, -1.0), dmnsn_z);
   sandglass_bench_fine(&sandglass, {
-    intersected = dmnsn_object_intersection(triangle, line, &intersection);
+    intersected = dmnsn_object_intersection(triangle, ray, &intersection);
   });
   dmnsn_assert(!intersected, "Intersected");
   printf("dmnsn_triangle_intersection(false): %ld\n", sandglass.grains);
diff --git a/libdimension/bvh.c b/libdimension/bvh.c
deleted file mode 100644
index d1bd0fc..0000000
--- a/libdimension/bvh.c
+++ /dev/null
@@ -1,404 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2012-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * BVH implementation.  These are the hottest code paths in libdimension.
- */
-
-#include "dimension-internal.h"
-
-/// Implementation for DMNSN_BVH_NONE: just stick all objects in one node.
-static dmnsn_bvh_node *
-dmnsn_new_stupid_bvh(const dmnsn_array *objects)
-{
-  dmnsn_bvh_node *root = dmnsn_new_bvh_node(dmnsn_array_size(objects));
-
-  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, objects) {
-    dmnsn_bvh_node *leaf = dmnsn_new_bvh_leaf_node(*object);
-    dmnsn_bvh_node_add(root, leaf);
-  }
-
-  return root;
-}
-
-// Implementation of opaque dmnsn_bvh type.
-struct dmnsn_bvh {
-  dmnsn_array *unbounded;           ///< The unbounded objects.
-  dmnsn_array *bounded;             ///< The BVH of the bounded objects.
-  pthread_key_t intersection_cache; ///< The thread-local intersection cache.
-};
-
-/// A flat BVH node for storing in an array for fast pre-order traversal.
-typedef struct dmnsn_flat_bvh_node {
-  dmnsn_bounding_box bounding_box; // The bounding box of this node.
-  dmnsn_object *object;            // The referenced object, for leaf nodes.
-  ptrdiff_t skip;                  // Displacement to the next sibling.
-} dmnsn_flat_bvh_node;
-
-/// Add an object or its children, if any, to an array.
-static void
-dmnsn_split_add_object(dmnsn_array *objects, const dmnsn_object *object)
-{
-  if (object->split_children) {
-    DMNSN_ARRAY_FOREACH (const dmnsn_object **, child, object->children) {
-      dmnsn_split_add_object(objects, *child);
-    }
-  } else {
-    dmnsn_array_push(objects, &object);
-  }
-}
-
-/// Split unions to create the input for the BVH.
-static dmnsn_array *
-dmnsn_split_objects(const dmnsn_array *objects)
-{
-  dmnsn_array *split = DMNSN_NEW_ARRAY(dmnsn_object *);
-  DMNSN_ARRAY_FOREACH (const dmnsn_object **, object, objects) {
-    dmnsn_split_add_object(split, *object);
-  }
-  return split;
-}
-
-/// Split unbounded objects into a new array.
-static dmnsn_array *
-dmnsn_split_unbounded(dmnsn_array *objects)
-{
-  dmnsn_array *unbounded = DMNSN_NEW_ARRAY(dmnsn_object *);
-
-  dmnsn_object **array = dmnsn_array_first(objects);
-  size_t i, skip;
-  for (i = 0, skip = 0; i < dmnsn_array_size(objects); ++i) {
-    if (dmnsn_bounding_box_is_infinite(array[i]->bounding_box)) {
-      dmnsn_array_push(unbounded, &array[i]);
-      ++skip;
-    } else {
-      array[i - skip] = array[i];
-    }
-  }
-  dmnsn_array_resize(objects, i - skip);
-
-  return unbounded;
-}
-
-/// Recursively flatten a BVH into an array of flat nodes.
-static void
-dmnsn_flatten_bvh_recursive(dmnsn_bvh_node *node, dmnsn_array *flat)
-{
-  size_t currenti = dmnsn_array_size(flat);
-  dmnsn_array_resize(flat, currenti + 1);
-  dmnsn_flat_bvh_node *flatnode = dmnsn_array_at(flat, currenti);
-
-  flatnode->bounding_box = node->bounding_box;
-  flatnode->object       = node->object;
-
-  for (size_t i = 0; i < node->nchildren && node->children[i]; ++i) {
-    dmnsn_flatten_bvh_recursive(node->children[i], flat);
-  }
-
-  // Array could have been realloc()'d somewhere else above
-  flatnode = dmnsn_array_at(flat, currenti);
-  flatnode->skip = dmnsn_array_size(flat) - currenti;
-}
-
-/// Flatten a BVH into an array of flat nodes.
-static dmnsn_array *
-dmnsn_flatten_bvh(dmnsn_bvh_node *root)
-{
-  dmnsn_array *flat = DMNSN_NEW_ARRAY(dmnsn_flat_bvh_node);
-  if (root) {
-    dmnsn_flatten_bvh_recursive(root, flat);
-  }
-  return flat;
-}
-
-dmnsn_bvh *dmnsn_new_bvh(const dmnsn_array *objects, dmnsn_bvh_kind kind)
-{
-  dmnsn_bvh *bvh = DMNSN_MALLOC(dmnsn_bvh);
-
-  dmnsn_array *bounded = dmnsn_split_objects(objects);
-  bvh->unbounded = dmnsn_split_unbounded(bounded);
-
-  dmnsn_bvh_node *root = NULL;
-  if (dmnsn_array_size(bounded) > 0) {
-    switch (kind) {
-    case DMNSN_BVH_NONE:
-      root = dmnsn_new_stupid_bvh(bounded);
-      break;
-    case DMNSN_BVH_PRTREE:
-      root = dmnsn_new_prtree(bounded);
-      break;
-    default:
-      dmnsn_unreachable("Invalid BVH kind.");
-    }
-  }
-  bvh->bounded = dmnsn_flatten_bvh(root);
-
-  dmnsn_delete_bvh_node(root);
-  dmnsn_delete_array(bounded);
-
-  dmnsn_key_create(&bvh->intersection_cache, dmnsn_free);
-
-  return bvh;
-}
-
-void
-dmnsn_delete_bvh(dmnsn_bvh *bvh)
-{
-  if (bvh) {
-    dmnsn_free(pthread_getspecific(bvh->intersection_cache));
-    dmnsn_key_delete(bvh->intersection_cache);
-    dmnsn_delete_array(bvh->bounded);
-    dmnsn_delete_array(bvh->unbounded);
-    dmnsn_free(bvh);
-  }
-}
-
-/// A line with pre-calculated reciprocals to avoid divisions.
-typedef struct dmnsn_optimized_line {
-  dmnsn_vector x0;    ///< The origin of the line.
-  dmnsn_vector n_inv; ///< The inverse of each component of the line's slope
-} dmnsn_optimized_line;
-
-/// Precompute inverses for faster ray-box intersection tests.
-static inline dmnsn_optimized_line
-dmnsn_optimize_line(dmnsn_line line)
-{
-  dmnsn_optimized_line optline = {
-    .x0    = line.x0,
-    .n_inv = dmnsn_new_vector(1.0/line.n.x, 1.0/line.n.y, 1.0/line.n.z)
-  };
-  return optline;
-}
-
-/// Ray-AABB intersection test, by the slab method.  Highly optimized.
-static inline bool
-dmnsn_ray_box_intersection(dmnsn_optimized_line optline,
-                           dmnsn_bounding_box box, double t)
-{
-  // This is actually correct, even though it appears not to handle edge cases
-  // (line.n.{x,y,z} == 0).  It works because the infinities that result from
-  // dividing by zero will still behave correctly in the comparisons.  Lines
-  // which are parallel to an axis and outside the box will have tmin == inf
-  // or tmax == -inf, while lines inside the box will have tmin and tmax
-  // unchanged.
-
-  double tx1 = (box.min.x - optline.x0.x)*optline.n_inv.x;
-  double tx2 = (box.max.x - optline.x0.x)*optline.n_inv.x;
-
-  double tmin = dmnsn_min(tx1, tx2);
-  double tmax = dmnsn_max(tx1, tx2);
-
-  double ty1 = (box.min.y - optline.x0.y)*optline.n_inv.y;
-  double ty2 = (box.max.y - optline.x0.y)*optline.n_inv.y;
-
-  tmin = dmnsn_max(tmin, dmnsn_min(ty1, ty2));
-  tmax = dmnsn_min(tmax, dmnsn_max(ty1, ty2));
-
-  double tz1 = (box.min.z - optline.x0.z)*optline.n_inv.z;
-  double tz2 = (box.max.z - optline.x0.z)*optline.n_inv.z;
-
-  tmin = dmnsn_max(tmin, dmnsn_min(tz1, tz2));
-  tmax = dmnsn_min(tmax, dmnsn_max(tz1, tz2));
-
-  return tmax >= dmnsn_max(0.0, tmin) && tmin < t;
-}
-
-/// The number of intersections to cache.
-#define DMNSN_INTERSECTION_CACHE_SIZE 32
-
-/// An array of cached intersections.
-typedef struct dmnsn_intersection_cache {
-  size_t i;
-  dmnsn_object *objects[DMNSN_INTERSECTION_CACHE_SIZE];
-} dmnsn_intersection_cache;
-
-static dmnsn_intersection_cache *
-dmnsn_get_intersection_cache(const dmnsn_bvh *bvh)
-{
-  dmnsn_intersection_cache *cache
-    = pthread_getspecific(bvh->intersection_cache);
-
-  if (!cache) {
-    cache = DMNSN_MALLOC(dmnsn_intersection_cache);
-    cache->i = 0;
-    for (size_t i = 0; i < DMNSN_INTERSECTION_CACHE_SIZE; ++i) {
-      cache->objects[i] = NULL;
-    }
-    dmnsn_setspecific(bvh->intersection_cache, cache);
-  }
-
-  return cache;
-}
-
-/// Test for a closer object intersection than we've found so far.
-static inline bool
-dmnsn_closer_intersection(dmnsn_object *object, dmnsn_line ray,
-                          dmnsn_intersection *intersection, double *t)
-{
-  dmnsn_intersection local_intersection;
-  if (dmnsn_object_intersection(object, ray, &local_intersection)) {
-    if (local_intersection.t < *t) {
-      *intersection = local_intersection;
-      *t = local_intersection.t;
-      return true;
-    }
-  }
-  return false;
-}
-
-DMNSN_HOT bool
-dmnsn_bvh_intersection(const dmnsn_bvh *bvh, dmnsn_line ray,
-                       dmnsn_intersection *intersection, bool reset)
-{
-  double t = INFINITY;
-
-  // Search the unbounded objects
-  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, bvh->unbounded) {
-    dmnsn_closer_intersection(*object, ray, intersection, &t);
-  }
-
-  // Precalculate 1.0/ray.n.{x,y,z} to save time in intersection tests
-  dmnsn_optimized_line optline = dmnsn_optimize_line(ray);
-
-  // Search the intersection cache
-  dmnsn_intersection_cache *cache = dmnsn_get_intersection_cache(bvh);
-  if (dmnsn_unlikely(reset)) {
-    cache->i = 0;
-  }
-  dmnsn_object *cached = NULL, *found = NULL;
-  if (dmnsn_likely(cache->i < DMNSN_INTERSECTION_CACHE_SIZE)) {
-    cached = cache->objects[cache->i];
-  }
-  if (cached && dmnsn_ray_box_intersection(optline, cached->bounding_box, t)) {
-    if (dmnsn_closer_intersection(cached, ray, intersection, &t)) {
-      found = cached;
-    }
-  }
-
-  // Search the bounded objects
-  dmnsn_flat_bvh_node *node = dmnsn_array_first(bvh->bounded);
-  dmnsn_flat_bvh_node *last = dmnsn_array_last(bvh->bounded);
-  while (node <= last) {
-    if (dmnsn_ray_box_intersection(optline, node->bounding_box, t)) {
-      if (node->object && node->object != cached) {
-        if (dmnsn_closer_intersection(node->object, ray, intersection, &t)) {
-          found = node->object;
-        }
-      }
-      ++node;
-    } else {
-      node += node->skip;
-    }
-  }
-
-  // Update the cache
-  if (dmnsn_likely(cache->i < DMNSN_INTERSECTION_CACHE_SIZE)) {
-    cache->objects[cache->i] = found;
-    ++cache->i;
-  }
-
-  return !isinf(t);
-}
-
-DMNSN_HOT bool
-dmnsn_bvh_inside(const dmnsn_bvh *bvh, dmnsn_vector point)
-{
-  // Search the unbounded objects
-  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, bvh->unbounded) {
-    if (dmnsn_object_inside(*object, point))
-      return true;
-  }
-
-  // Search the bounded objects
-  dmnsn_flat_bvh_node *node = dmnsn_array_first(bvh->bounded);
-  dmnsn_flat_bvh_node *last = dmnsn_array_last(bvh->bounded);
-  while (node <= last) {
-    if (dmnsn_bounding_box_contains(node->bounding_box, point)) {
-      if (node->object && dmnsn_object_inside(node->object, point)) {
-        return true;
-      }
-      ++node;
-    } else {
-      node += node->skip;
-    }
-  }
-
-  return false;
-}
-
-dmnsn_bounding_box
-dmnsn_bvh_bounding_box(const dmnsn_bvh *bvh)
-{
-  if (dmnsn_array_size(bvh->unbounded) > 0) {
-    return dmnsn_infinite_bounding_box();
-  } else if (dmnsn_array_size(bvh->bounded) > 0) {
-    dmnsn_flat_bvh_node *root = dmnsn_array_first(bvh->bounded);
-    return root->bounding_box;
-  } else {
-    return dmnsn_zero_bounding_box();
-  }
-}
-
-dmnsn_bvh_node *
-dmnsn_new_bvh_node(unsigned int max_children)
-{
-  dmnsn_bvh_node *node = dmnsn_malloc(sizeof(dmnsn_bvh_node) + max_children*sizeof(dmnsn_bvh_node *));
-  node->bounding_box = dmnsn_zero_bounding_box();
-  node->object = NULL;
-  node->nchildren = 0;
-  node->max_children = max_children;
-  return node;
-}
-
-dmnsn_bvh_node *
-dmnsn_new_bvh_leaf_node(dmnsn_object *object)
-{
-  dmnsn_bvh_node *node = DMNSN_MALLOC(dmnsn_bvh_node);
-  node->bounding_box = object->bounding_box;
-  node->object = object;
-  node->nchildren = 0;
-  node->max_children = 0;
-  return node;
-}
-
-void
-dmnsn_delete_bvh_node(dmnsn_bvh_node *node)
-{
-  if (node) {
-    for (size_t i = 0; i < node->nchildren; ++i) {
-      dmnsn_delete_bvh_node(node->children[i]);
-    }
-    dmnsn_free(node);
-  }
-}
-
-void
-dmnsn_bvh_node_add(dmnsn_bvh_node *parent, dmnsn_bvh_node *child)
-{
-  dmnsn_assert(parent->nchildren < parent->max_children,
-               "Too many BVH children inserted.");
-
-  parent->bounding_box.min = dmnsn_vector_min(parent->bounding_box.min,
-                                              child->bounding_box.min);
-  parent->bounding_box.max = dmnsn_vector_max(parent->bounding_box.max,
-                                              child->bounding_box.max);
-  parent->children[parent->nchildren++] = child;
-}
diff --git a/libdimension/bvh.h b/libdimension/bvh.h
deleted file mode 100644
index f965632..0000000
--- a/libdimension/bvh.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2012-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file.
- * Bounding volume hierarchy.  This generic interface allows different data
- * structures to be represented in the same way, thus sharing code for their
- * traversal algorithm.
- */
-
-#include <stdbool.h>
-
-/// A bounding volume hierarchy.
-typedef struct dmnsn_bvh dmnsn_bvh;
-
-/// Available BVH implementations.
-typedef enum dmnsn_bvh_kind {
-  DMNSN_BVH_NONE,
-  DMNSN_BVH_PRTREE,
-} dmnsn_bvh_kind;
-
-/// Create a BVH.
-DMNSN_INTERNAL dmnsn_bvh *dmnsn_new_bvh(const dmnsn_array *objects,
-                                        dmnsn_bvh_kind kind);
-/// Delete a BVH.
-DMNSN_INTERNAL void dmnsn_delete_bvh(dmnsn_bvh *bvh);
-
-/// Find the closest ray-object intersection in the tree.
-DMNSN_INTERNAL bool dmnsn_bvh_intersection(const dmnsn_bvh *bvh,
-                                           dmnsn_line ray,
-                                           dmnsn_intersection *intersection,
-                                           bool reset);
-/// Determine whether a point is inside any object in the tree.
-DMNSN_INTERNAL bool dmnsn_bvh_inside(const dmnsn_bvh *bvh, dmnsn_vector point);
-/// Return the bounding box of the whole hierarchy.
-DMNSN_INTERNAL dmnsn_bounding_box dmnsn_bvh_bounding_box(const dmnsn_bvh *bvh);
-
-/// A non-flat BVH representation, used by BVH implementations.
-typedef struct dmnsn_bvh_node {
-  dmnsn_bounding_box bounding_box;   ///< The bounding box of this node.
-  dmnsn_object *object;              ///< The object, for leaf nodes.
-  unsigned int nchildren;            ///< How many children this node has.
-  unsigned int max_children;         ///< Maximum number of children.
-  struct dmnsn_bvh_node *children[]; ///< Flexible array of children.
-} dmnsn_bvh_node;
-
-/// Create a BVH node.
-DMNSN_INTERNAL dmnsn_bvh_node *dmnsn_new_bvh_node(unsigned int max_children);
-
-/// Create a BVH leaf node.
-DMNSN_INTERNAL dmnsn_bvh_node *dmnsn_new_bvh_leaf_node(dmnsn_object *object);
-
-/// Delete a BVH node.
-DMNSN_INTERNAL void dmnsn_delete_bvh_node(dmnsn_bvh_node *node);
-
-/// Add a child to a BVH node.
-DMNSN_INTERNAL void dmnsn_bvh_node_add(dmnsn_bvh_node *parent, dmnsn_bvh_node *child);
diff --git a/libdimension/bvh/bvh.c b/libdimension/bvh/bvh.c
new file mode 100644
index 0000000..eab2c28
--- /dev/null
+++ b/libdimension/bvh/bvh.c
@@ -0,0 +1,402 @@
+/*************************************************************************
+ * Copyright (C) 2012-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * BVH implementation.  These are the hottest code paths in libdimension.
+ */
+
+#include "internal/bvh.h"
+#include "internal/concurrency.h"
+#include "internal/prtree.h"
+#include <pthread.h>
+
+/// Implementation for DMNSN_BVH_NONE: just stick all objects in one node.
+static dmnsn_bvh_node *
+dmnsn_new_stupid_bvh(const dmnsn_array *objects)
+{
+  dmnsn_bvh_node *root = dmnsn_new_bvh_node(dmnsn_array_size(objects));
+
+  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, objects) {
+    dmnsn_bvh_node *leaf = dmnsn_new_bvh_leaf_node(*object);
+    dmnsn_bvh_node_add(root, leaf);
+  }
+
+  return root;
+}
+
+// Implementation of opaque dmnsn_bvh type.
+struct dmnsn_bvh {
+  dmnsn_array *unbounded;           ///< The unbounded objects.
+  dmnsn_array *bounded;             ///< The BVH of the bounded objects.
+  pthread_key_t intersection_cache; ///< The thread-local intersection cache.
+};
+
+/// A flat BVH node for storing in an array for fast pre-order traversal.
+typedef struct dmnsn_flat_bvh_node {
+  dmnsn_aabb aabb;      ///< The bounding box of this node.
+  dmnsn_object *object; ///< The referenced object, for leaf nodes.
+  ptrdiff_t skip;       ///< Displacement to the next sibling.
+} dmnsn_flat_bvh_node;
+
+/// Add an object or its children, if any, to an array.
+static void
+dmnsn_split_add_object(dmnsn_array *objects, const dmnsn_object *object)
+{
+  if (object->split_children) {
+    DMNSN_ARRAY_FOREACH (const dmnsn_object **, child, object->children) {
+      dmnsn_split_add_object(objects, *child);
+    }
+  } else {
+    dmnsn_array_push(objects, &object);
+  }
+}
+
+/// Split unions to create the input for the BVH.
+static dmnsn_array *
+dmnsn_split_objects(const dmnsn_array *objects)
+{
+  dmnsn_array *split = DMNSN_NEW_ARRAY(dmnsn_object *);
+  DMNSN_ARRAY_FOREACH (const dmnsn_object **, object, objects) {
+    dmnsn_split_add_object(split, *object);
+  }
+  return split;
+}
+
+/// Split unbounded objects into a new array.
+static dmnsn_array *
+dmnsn_split_unbounded(dmnsn_array *objects)
+{
+  dmnsn_array *unbounded = DMNSN_NEW_ARRAY(dmnsn_object *);
+
+  dmnsn_object **array = dmnsn_array_first(objects);
+  size_t i, skip;
+  for (i = 0, skip = 0; i < dmnsn_array_size(objects); ++i) {
+    if (dmnsn_aabb_is_infinite(array[i]->aabb)) {
+      dmnsn_array_push(unbounded, &array[i]);
+      ++skip;
+    } else {
+      array[i - skip] = array[i];
+    }
+  }
+  dmnsn_array_resize(objects, i - skip);
+
+  return unbounded;
+}
+
+/// Recursively flatten a BVH into an array of flat nodes.
+static void
+dmnsn_flatten_bvh_recursive(dmnsn_bvh_node *node, dmnsn_array *flat)
+{
+  size_t currenti = dmnsn_array_size(flat);
+  dmnsn_array_resize(flat, currenti + 1);
+  dmnsn_flat_bvh_node *flatnode = dmnsn_array_at(flat, currenti);
+
+  flatnode->aabb = node->aabb;
+  flatnode->object = node->object;
+
+  for (size_t i = 0; i < node->nchildren && node->children[i]; ++i) {
+    dmnsn_flatten_bvh_recursive(node->children[i], flat);
+  }
+
+  // Array could have been realloc()'d somewhere else above
+  flatnode = dmnsn_array_at(flat, currenti);
+  flatnode->skip = dmnsn_array_size(flat) - currenti;
+}
+
+/// Flatten a BVH into an array of flat nodes.
+static dmnsn_array *
+dmnsn_flatten_bvh(dmnsn_bvh_node *root)
+{
+  dmnsn_array *flat = DMNSN_NEW_ARRAY(dmnsn_flat_bvh_node);
+  if (root) {
+    dmnsn_flatten_bvh_recursive(root, flat);
+  }
+  return flat;
+}
+
+dmnsn_bvh *dmnsn_new_bvh(const dmnsn_array *objects, dmnsn_bvh_kind kind)
+{
+  dmnsn_bvh *bvh = DMNSN_MALLOC(dmnsn_bvh);
+
+  dmnsn_array *bounded = dmnsn_split_objects(objects);
+  bvh->unbounded = dmnsn_split_unbounded(bounded);
+
+  dmnsn_bvh_node *root = NULL;
+  if (dmnsn_array_size(bounded) > 0) {
+    switch (kind) {
+    case DMNSN_BVH_NONE:
+      root = dmnsn_new_stupid_bvh(bounded);
+      break;
+    case DMNSN_BVH_PRTREE:
+      root = dmnsn_new_prtree(bounded);
+      break;
+    default:
+      dmnsn_unreachable("Invalid BVH kind.");
+    }
+  }
+  bvh->bounded = dmnsn_flatten_bvh(root);
+
+  dmnsn_delete_bvh_node(root);
+  dmnsn_delete_array(bounded);
+
+  dmnsn_key_create(&bvh->intersection_cache, dmnsn_free);
+
+  return bvh;
+}
+
+void
+dmnsn_delete_bvh(dmnsn_bvh *bvh)
+{
+  if (bvh) {
+    dmnsn_free(pthread_getspecific(bvh->intersection_cache));
+    dmnsn_key_delete(bvh->intersection_cache);
+    dmnsn_delete_array(bvh->bounded);
+    dmnsn_delete_array(bvh->unbounded);
+    dmnsn_free(bvh);
+  }
+}
+
+/// A ray with pre-calculated reciprocals to avoid divisions.
+typedef struct dmnsn_optimized_ray {
+  dmnsn_vector x0;    ///< The origin of the ray.
+  dmnsn_vector n_inv; ///< The inverse of each component of the ray's slope
+} dmnsn_optimized_ray;
+
+/// Precompute inverses for faster ray-box intersection tests.
+static inline dmnsn_optimized_ray
+dmnsn_optimize_ray(dmnsn_ray ray)
+{
+  dmnsn_optimized_ray optray = {
+    .x0    = ray.x0,
+    .n_inv = dmnsn_new_vector(1.0/ray.n.x, 1.0/ray.n.y, 1.0/ray.n.z)
+  };
+  return optray;
+}
+
+/// Ray-AABB intersection test, by the slab method.  Highly optimized.
+static inline bool
+dmnsn_ray_box_intersection(dmnsn_optimized_ray optray, dmnsn_aabb box, double t)
+{
+  // This is actually correct, even though it appears not to handle edge cases
+  // (ray.n.{x,y,z} == 0).  It works because the infinities that result from
+  // dividing by zero will still behave correctly in the comparisons.  Rays
+  // which are parallel to an axis and outside the box will have tmin == inf
+  // or tmax == -inf, while rays inside the box will have tmin and tmax
+  // unchanged.
+
+  double tx1 = (box.min.x - optray.x0.x)*optray.n_inv.x;
+  double tx2 = (box.max.x - optray.x0.x)*optray.n_inv.x;
+
+  double tmin = dmnsn_min(tx1, tx2);
+  double tmax = dmnsn_max(tx1, tx2);
+
+  double ty1 = (box.min.y - optray.x0.y)*optray.n_inv.y;
+  double ty2 = (box.max.y - optray.x0.y)*optray.n_inv.y;
+
+  tmin = dmnsn_max(tmin, dmnsn_min(ty1, ty2));
+  tmax = dmnsn_min(tmax, dmnsn_max(ty1, ty2));
+
+  double tz1 = (box.min.z - optray.x0.z)*optray.n_inv.z;
+  double tz2 = (box.max.z - optray.x0.z)*optray.n_inv.z;
+
+  tmin = dmnsn_max(tmin, dmnsn_min(tz1, tz2));
+  tmax = dmnsn_min(tmax, dmnsn_max(tz1, tz2));
+
+  return tmax >= dmnsn_max(0.0, tmin) && tmin < t;
+}
+
+/// The number of intersections to cache.
+#define DMNSN_INTERSECTION_CACHE_SIZE 32
+
+/// An array of cached intersections.
+typedef struct dmnsn_intersection_cache {
+  size_t i;
+  dmnsn_object *objects[DMNSN_INTERSECTION_CACHE_SIZE];
+} dmnsn_intersection_cache;
+
+static dmnsn_intersection_cache *
+dmnsn_get_intersection_cache(const dmnsn_bvh *bvh)
+{
+  dmnsn_intersection_cache *cache
+    = pthread_getspecific(bvh->intersection_cache);
+
+  if (!cache) {
+    cache = DMNSN_MALLOC(dmnsn_intersection_cache);
+    cache->i = 0;
+    for (size_t i = 0; i < DMNSN_INTERSECTION_CACHE_SIZE; ++i) {
+      cache->objects[i] = NULL;
+    }
+    dmnsn_setspecific(bvh->intersection_cache, cache);
+  }
+
+  return cache;
+}
+
+/// Test for a closer object intersection than we've found so far.
+static inline bool
+dmnsn_closer_intersection(dmnsn_object *object, dmnsn_ray ray, dmnsn_intersection *intersection, double *t)
+{
+  dmnsn_intersection local_intersection;
+  if (dmnsn_object_intersection(object, ray, &local_intersection)) {
+    if (local_intersection.t < *t) {
+      *intersection = local_intersection;
+      *t = local_intersection.t;
+      return true;
+    }
+  }
+  return false;
+}
+
+DMNSN_HOT bool
+dmnsn_bvh_intersection(const dmnsn_bvh *bvh, dmnsn_ray ray, dmnsn_intersection *intersection, bool reset)
+{
+  double t = INFINITY;
+
+  // Search the unbounded objects
+  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, bvh->unbounded) {
+    dmnsn_closer_intersection(*object, ray, intersection, &t);
+  }
+
+  // Precalculate 1.0/ray.n.{x,y,z} to save time in intersection tests
+  dmnsn_optimized_ray optray = dmnsn_optimize_ray(ray);
+
+  // Search the intersection cache
+  dmnsn_intersection_cache *cache = dmnsn_get_intersection_cache(bvh);
+  if (dmnsn_unlikely(reset)) {
+    cache->i = 0;
+  }
+  dmnsn_object *cached = NULL, *found = NULL;
+  if (dmnsn_likely(cache->i < DMNSN_INTERSECTION_CACHE_SIZE)) {
+    cached = cache->objects[cache->i];
+  }
+  if (cached && dmnsn_ray_box_intersection(optray, cached->aabb, t)) {
+    if (dmnsn_closer_intersection(cached, ray, intersection, &t)) {
+      found = cached;
+    }
+  }
+
+  // Search the bounded objects
+  dmnsn_flat_bvh_node *node = dmnsn_array_first(bvh->bounded);
+  dmnsn_flat_bvh_node *last = dmnsn_array_last(bvh->bounded);
+  while (node <= last) {
+    if (dmnsn_ray_box_intersection(optray, node->aabb, t)) {
+      if (node->object && node->object != cached) {
+        if (dmnsn_closer_intersection(node->object, ray, intersection, &t)) {
+          found = node->object;
+        }
+      }
+      ++node;
+    } else {
+      node += node->skip;
+    }
+  }
+
+  // Update the cache
+  if (dmnsn_likely(cache->i < DMNSN_INTERSECTION_CACHE_SIZE)) {
+    cache->objects[cache->i] = found;
+    ++cache->i;
+  }
+
+  return !isinf(t);
+}
+
+DMNSN_HOT bool
+dmnsn_bvh_inside(const dmnsn_bvh *bvh, dmnsn_vector point)
+{
+  // Search the unbounded objects
+  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, bvh->unbounded) {
+    if (dmnsn_object_inside(*object, point))
+      return true;
+  }
+
+  // Search the bounded objects
+  dmnsn_flat_bvh_node *node = dmnsn_array_first(bvh->bounded);
+  dmnsn_flat_bvh_node *last = dmnsn_array_last(bvh->bounded);
+  while (node <= last) {
+    if (dmnsn_aabb_contains(node->aabb, point)) {
+      if (node->object && dmnsn_object_inside(node->object, point)) {
+        return true;
+      }
+      ++node;
+    } else {
+      node += node->skip;
+    }
+  }
+
+  return false;
+}
+
+dmnsn_aabb
+dmnsn_bvh_aabb(const dmnsn_bvh *bvh)
+{
+  if (dmnsn_array_size(bvh->unbounded) > 0) {
+    return dmnsn_infinite_aabb();
+  } else if (dmnsn_array_size(bvh->bounded) > 0) {
+    dmnsn_flat_bvh_node *root = dmnsn_array_first(bvh->bounded);
+    return root->aabb;
+  } else {
+    return dmnsn_zero_aabb();
+  }
+}
+
+dmnsn_bvh_node *
+dmnsn_new_bvh_node(unsigned int max_children)
+{
+  dmnsn_bvh_node *node = dmnsn_malloc(sizeof(dmnsn_bvh_node) + max_children*sizeof(dmnsn_bvh_node *));
+  node->aabb = dmnsn_zero_aabb();
+  node->object = NULL;
+  node->nchildren = 0;
+  node->max_children = max_children;
+  return node;
+}
+
+dmnsn_bvh_node *
+dmnsn_new_bvh_leaf_node(dmnsn_object *object)
+{
+  dmnsn_bvh_node *node = DMNSN_MALLOC(dmnsn_bvh_node);
+  node->aabb = object->aabb;
+  node->object = object;
+  node->nchildren = 0;
+  node->max_children = 0;
+  return node;
+}
+
+void
+dmnsn_delete_bvh_node(dmnsn_bvh_node *node)
+{
+  if (node) {
+    for (size_t i = 0; i < node->nchildren; ++i) {
+      dmnsn_delete_bvh_node(node->children[i]);
+    }
+    dmnsn_free(node);
+  }
+}
+
+void
+dmnsn_bvh_node_add(dmnsn_bvh_node *parent, dmnsn_bvh_node *child)
+{
+  dmnsn_assert(parent->nchildren < parent->max_children,
+               "Too many BVH children inserted.");
+
+  parent->aabb.min = dmnsn_vector_min(parent->aabb.min, child->aabb.min);
+  parent->aabb.max = dmnsn_vector_max(parent->aabb.max, child->aabb.max);
+  parent->children[parent->nchildren++] = child;
+}
diff --git a/libdimension/bvh/prtree.c b/libdimension/bvh/prtree.c
new file mode 100644
index 0000000..c8e4e54
--- /dev/null
+++ b/libdimension/bvh/prtree.c
@@ -0,0 +1,372 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Priority R-tree implementation.
+ */
+
+#include "internal/platform.h"
+#include "internal/prtree.h"
+#include "internal/concurrency.h"
+#include <stdlib.h>
+
+/// Number of children per PR-node.
+#define DMNSN_PRTREE_B 8
+/// Number of priority leaves per pseudo-PR-node (must be 2*ndimensions).
+#define DMNSN_PSEUDO_B 6
+
+/// The side of the split that a node ended up on.
+typedef enum dmnsn_prnode_color {
+  DMNSN_PRTREE_LEAF, ///< Priority leaf.
+  DMNSN_PRTREE_LEFT, ///< Left child.
+  DMNSN_PRTREE_RIGHT ///< Right child.
+} dmnsn_prnode_color;
+
+/**
+ * A BVH node with associated color.  Compared to storing the color in the
+ * \p dmnsn_bvh_node itself, this method has decreased cache performance during
+ * sorting (due to an extra pointer chase), but increased performance during
+ * tree building (because it's much smaller than a full \p dmnsn_bvh_node).
+ * Overall it gives about a 25% improvement.
+ */
+typedef struct dmnsn_colored_prnode {
+  dmnsn_prnode_color color;
+  dmnsn_bvh_node *node;
+} dmnsn_colored_prnode;
+
+/// Construct an empty PR-node.
+static inline dmnsn_bvh_node *
+dmnsn_new_prnode(void)
+{
+  return dmnsn_new_bvh_node(DMNSN_PRTREE_B);
+}
+
+/// Comparator types.
+enum {
+  DMNSN_XMIN,
+  DMNSN_YMIN,
+  DMNSN_ZMIN,
+  DMNSN_XMAX,
+  DMNSN_YMAX,
+  DMNSN_ZMAX
+};
+
+// List sorting comparators
+
+static int
+dmnsn_xmin_comp(const void *l, const void *r)
+{
+  double lval = (*(const dmnsn_colored_prnode **)l)->node->aabb.min.x;
+  double rval = (*(const dmnsn_colored_prnode **)r)->node->aabb.min.x;
+  return (lval > rval) - (lval < rval);
+}
+
+static int
+dmnsn_ymin_comp(const void *l, const void *r)
+{
+  double lval = (*(const dmnsn_colored_prnode **)l)->node->aabb.min.y;
+  double rval = (*(const dmnsn_colored_prnode **)r)->node->aabb.min.y;
+  return (lval > rval) - (lval < rval);
+}
+
+static int
+dmnsn_zmin_comp(const void *l, const void *r)
+{
+  double lval = (*(const dmnsn_colored_prnode **)l)->node->aabb.min.z;
+  double rval = (*(const dmnsn_colored_prnode **)r)->node->aabb.min.z;
+  return (lval > rval) - (lval < rval);
+}
+
+static int
+dmnsn_xmax_comp(const void *l, const void *r)
+{
+  double lval = (*(const dmnsn_colored_prnode **)l)->node->aabb.max.x;
+  double rval = (*(const dmnsn_colored_prnode **)r)->node->aabb.max.x;
+  return (lval < rval) - (lval > rval);
+}
+
+static int
+dmnsn_ymax_comp(const void *l, const void *r)
+{
+  double lval = (*(const dmnsn_colored_prnode **)l)->node->aabb.max.y;
+  double rval = (*(const dmnsn_colored_prnode **)r)->node->aabb.max.y;
+  return (lval < rval) - (lval > rval);
+}
+
+static int
+dmnsn_zmax_comp(const void *l, const void *r)
+{
+  double lval = (*(const dmnsn_colored_prnode **)l)->node->aabb.max.z;
+  double rval = (*(const dmnsn_colored_prnode **)r)->node->aabb.max.z;
+  return (lval < rval) - (lval > rval);
+}
+
+/// All comparators.
+static dmnsn_array_comparator_fn *const dmnsn_comparators[DMNSN_PSEUDO_B] = {
+  [DMNSN_XMIN] = dmnsn_xmin_comp,
+  [DMNSN_YMIN] = dmnsn_ymin_comp,
+  [DMNSN_ZMIN] = dmnsn_zmin_comp,
+  [DMNSN_XMAX] = dmnsn_xmax_comp,
+  [DMNSN_YMAX] = dmnsn_ymax_comp,
+  [DMNSN_ZMAX] = dmnsn_zmax_comp,
+};
+
+/// Add the priority leaves for this level.
+static void
+dmnsn_add_priority_leaves(dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B],
+                          size_t start, size_t end,
+                          dmnsn_array *new_leaves)
+{
+  for (size_t i = 0; i < DMNSN_PSEUDO_B; ++i) {
+    dmnsn_bvh_node *leaf = NULL;
+    dmnsn_colored_prnode **leaves = sorted_leaves[i];
+
+    for (size_t j = start;
+         j < end && (!leaf || leaf->nchildren < DMNSN_PRTREE_B);
+         ++j) {
+      // Skip all the previously found extreme nodes
+      if (leaves[j]->color == DMNSN_PRTREE_LEAF) {
+        continue;
+      }
+
+      if (!leaf) {
+        leaf = dmnsn_new_prnode();
+      }
+      leaves[j]->color = DMNSN_PRTREE_LEAF;
+      dmnsn_bvh_node_add(leaf, leaves[j]->node);
+    }
+
+    if (leaf) {
+      dmnsn_array_push(new_leaves, &leaf);
+    } else {
+      return;
+    }
+  }
+}
+
+/// Get rid of the extreme nodes.
+static void
+dmnsn_filter_priority_leaves(dmnsn_colored_prnode **leaves, size_t start, size_t *endp)
+{
+  size_t i, skip, end;
+  for (i = start, skip = 0, end = *endp; i < end; ++i) {
+    if (leaves[i]->color == DMNSN_PRTREE_LEAF) {
+      ++skip;
+    } else {
+      leaves[i - skip] = leaves[i];
+    }
+  }
+
+  *endp -= skip;
+}
+
+/// Split the leaves and mark the left and right child nodes.
+static void
+dmnsn_split_sorted_leaves_easy(dmnsn_colored_prnode **leaves, size_t start, size_t *midp, size_t end)
+{
+  size_t i, mid = start + (end - start + 1)/2;
+  for (i = start; i < mid; ++i) {
+    leaves[i]->color = DMNSN_PRTREE_LEFT;
+  }
+  for (; i < end; ++i) {
+    leaves[i]->color = DMNSN_PRTREE_RIGHT;
+  }
+
+  *midp = mid;
+}
+
+/// Split the leaves using the coloring from dmnsn_split_sorted_leaves_easy().
+static void
+dmnsn_split_sorted_leaves_hard(dmnsn_colored_prnode **leaves, dmnsn_colored_prnode **buffer, size_t start, size_t end)
+{
+  size_t i, j, skip;
+  for (i = start, j = 0, skip = 0; i < end; ++i) {
+    if (leaves[i]->color == DMNSN_PRTREE_LEFT) {
+      leaves[i - skip] = leaves[i];
+    } else {
+      if (leaves[i]->color == DMNSN_PRTREE_RIGHT) {
+        buffer[j] = leaves[i];
+        ++j;
+      }
+      ++skip;
+    }
+  }
+
+  size_t mid = i - skip;
+  for (i = 0; i < j; ++i) {
+    leaves[mid + i] = buffer[i];
+  }
+}
+
+/// Split the sorted lists into the left and right subtrees.
+static void
+dmnsn_split_sorted_leaves(dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B],
+                          size_t start, size_t *midp, size_t *endp,
+                          dmnsn_colored_prnode **buffer, int i)
+{
+  size_t orig_end = *endp;
+
+  // Filter the extreme nodes in the ith list
+  dmnsn_filter_priority_leaves(sorted_leaves[i], start, endp);
+
+  // Split the ith list
+  dmnsn_split_sorted_leaves_easy(sorted_leaves[i], start, midp, *endp);
+
+  // Split the rest of the lists
+  for (size_t j = 0; j < DMNSN_PSEUDO_B; ++j) {
+    if (j == i) {
+      continue;
+    }
+
+    dmnsn_split_sorted_leaves_hard(sorted_leaves[j], buffer, start, orig_end);
+  }
+}
+
+/// Recursively constructs an implicit pseudo-PR-tree and collects the priority
+/// leaves.
+static void
+dmnsn_priority_leaves_recursive(dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B],
+                                size_t start, size_t end,
+                                dmnsn_colored_prnode **buffer,
+                                dmnsn_array *new_leaves,
+                                int comparator)
+{
+  dmnsn_add_priority_leaves(sorted_leaves, start, end, new_leaves);
+
+  size_t mid;
+  dmnsn_split_sorted_leaves(sorted_leaves, start, &mid, &end, buffer, comparator);
+
+  int next = (comparator + 1)%DMNSN_PSEUDO_B;
+
+  if (start < mid) {
+    dmnsn_priority_leaves_recursive(sorted_leaves, start, mid, buffer, new_leaves, next);
+  }
+
+  if (mid < end) {
+    dmnsn_priority_leaves_recursive(sorted_leaves, mid, end, buffer, new_leaves, next);
+  }
+}
+
+/// Sort each dimension in parallel with more than this many leaves.
+#define DMNSN_PARALLEL_SORT_THRESHOLD 1024
+
+typedef struct {
+  dmnsn_colored_prnode *colored_leaves;
+  dmnsn_colored_prnode ***sorted_leaves;
+  size_t nleaves;
+} dmnsn_sort_leaves_payload;
+
+static dmnsn_colored_prnode **
+dmnsn_sort_leaf_array(dmnsn_colored_prnode *colored_leaves, size_t nleaves, int comparator)
+{
+  dmnsn_colored_prnode **sorted_leaves = dmnsn_malloc(nleaves*sizeof(dmnsn_colored_prnode *));
+
+  for (size_t i = 0; i < nleaves; ++i) {
+    sorted_leaves[i] = colored_leaves + i;
+  }
+
+  qsort(sorted_leaves, nleaves, sizeof(dmnsn_colored_prnode *), dmnsn_comparators[comparator]);
+
+  return sorted_leaves;
+}
+
+static int
+dmnsn_sort_leaves(void *ptr, unsigned int thread, unsigned int nthreads)
+{
+  dmnsn_sort_leaves_payload *payload = ptr;
+
+  for (unsigned int i = thread; i < DMNSN_PSEUDO_B; i += nthreads) {
+    payload->sorted_leaves[i] = dmnsn_sort_leaf_array(payload->colored_leaves, payload->nleaves, i);
+  }
+
+  return 0;
+}
+
+/// Constructs an implicit pseudo-PR-tree and returns the priority leaves.
+static dmnsn_array *
+dmnsn_priority_leaves(const dmnsn_array *leaves, unsigned int nthreads)
+{
+  dmnsn_bvh_node **leaves_arr = dmnsn_array_first(leaves);
+  size_t nleaves = dmnsn_array_size(leaves);
+
+  dmnsn_colored_prnode *colored_leaves = dmnsn_malloc(nleaves*sizeof(dmnsn_colored_prnode));
+  for (size_t i = 0; i < nleaves; ++i) {
+    colored_leaves[i].color = DMNSN_PRTREE_LEFT; // Mustn't be _LEAF
+    colored_leaves[i].node = leaves_arr[i];
+  }
+
+  dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B];
+
+  if (nleaves >= DMNSN_PARALLEL_SORT_THRESHOLD && nthreads > 1) {
+    dmnsn_sort_leaves_payload payload = {
+      .colored_leaves = colored_leaves,
+      .sorted_leaves = sorted_leaves,
+      .nleaves = nleaves,
+    };
+    dmnsn_execute_concurrently(NULL, dmnsn_sort_leaves, &payload, nthreads);
+  } else {
+    for (size_t i = 0; i < DMNSN_PSEUDO_B; ++i) {
+      sorted_leaves[i] = dmnsn_sort_leaf_array(colored_leaves, nleaves, i);
+    }
+  }
+
+  size_t buffer_size = nleaves/2;
+  dmnsn_colored_prnode **buffer = dmnsn_malloc(buffer_size*sizeof(dmnsn_colored_prnode *));
+
+  dmnsn_array *new_leaves = DMNSN_NEW_ARRAY(dmnsn_bvh_node *);
+
+  dmnsn_priority_leaves_recursive(sorted_leaves, 0, nleaves, buffer, new_leaves, 0);
+
+  dmnsn_free(buffer);
+  for (size_t i = 0; i < DMNSN_PSEUDO_B; ++i) {
+    dmnsn_free(sorted_leaves[i]);
+  }
+  dmnsn_free(colored_leaves);
+
+  return new_leaves;
+}
+
+dmnsn_bvh_node *
+dmnsn_new_prtree(const dmnsn_array *objects)
+{
+  if (dmnsn_array_size(objects) == 0) {
+    return NULL;
+  }
+
+  // Make the initial array of leaves
+  dmnsn_array *leaves = DMNSN_NEW_ARRAY(dmnsn_bvh_node *);
+  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, objects) {
+    dmnsn_bvh_node *node = dmnsn_new_bvh_leaf_node(*object);
+    dmnsn_array_push(leaves, &node);
+  }
+
+  unsigned int ncpus = dmnsn_ncpus();
+  unsigned int nthreads = ncpus < DMNSN_PSEUDO_B ? ncpus : DMNSN_PSEUDO_B;
+  while (dmnsn_array_size(leaves) > 1) {
+    dmnsn_array *new_leaves = dmnsn_priority_leaves(leaves, nthreads);
+    dmnsn_delete_array(leaves);
+    leaves = new_leaves;
+  }
+
+  dmnsn_bvh_node *root = *(dmnsn_bvh_node **)dmnsn_array_first(leaves);
+  dmnsn_delete_array(leaves);
+  return root;
+}
diff --git a/libdimension/camera.c b/libdimension/camera.c
deleted file mode 100644
index 550b41c..0000000
--- a/libdimension/camera.c
+++ /dev/null
@@ -1,51 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Cameras.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-// Allocate a new dummy camera
-dmnsn_camera *
-dmnsn_new_camera(dmnsn_pool *pool)
-{
-  dmnsn_camera *camera = DMNSN_PALLOC(pool, dmnsn_camera);
-  dmnsn_init_camera(camera);
-  return camera;
-}
-
-// Initialize a camera
-void
-dmnsn_init_camera(dmnsn_camera *camera)
-{
-  camera->trans = dmnsn_identity_matrix();
-}
-
-// Invoke the camera ray function
-dmnsn_line
-dmnsn_camera_ray(const dmnsn_camera *camera, double x, double y)
-{
-  dmnsn_line ray = camera->ray_fn(camera, x, y);
-  return dmnsn_transform_line(camera->trans, ray);
-}
diff --git a/libdimension/canvas.c b/libdimension/canvas.c
deleted file mode 100644
index 2aade47..0000000
--- a/libdimension/canvas.c
+++ /dev/null
@@ -1,92 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Canveses.
- */
-
-#include "dimension-internal.h"
-
-dmnsn_canvas *
-dmnsn_new_canvas(dmnsn_pool *pool, size_t width, size_t height)
-{
-  dmnsn_canvas *canvas = DMNSN_PALLOC(pool, dmnsn_canvas);
-  canvas->width = width;
-  canvas->height = height;
-  canvas->optimizers = DMNSN_PALLOC_ARRAY(pool, dmnsn_canvas_optimizer *);
-  canvas->pixels = dmnsn_palloc(pool, sizeof(dmnsn_tcolor)*width*height);
-  return canvas;
-}
-
-void
-dmnsn_init_canvas_optimizer(dmnsn_canvas_optimizer *optimizer)
-{
-  optimizer->optimizer_fn = NULL;
-}
-
-// Set a canvas optimizer
-void
-dmnsn_canvas_optimize(dmnsn_canvas *canvas, const dmnsn_canvas_optimizer *optimizer)
-{
-  dmnsn_array_push(canvas->optimizers, &optimizer);
-}
-
-// Find an optimizer if it's already installed
-dmnsn_canvas_optimizer *
-dmnsn_canvas_find_optimizer(const dmnsn_canvas *canvas, dmnsn_canvas_optimizer_fn *optimizer_fn)
-{
-  DMNSN_ARRAY_FOREACH (dmnsn_canvas_optimizer **, i, canvas->optimizers) {
-    if ((*i)->optimizer_fn == optimizer_fn) {
-      return *i;
-    }
-  }
-
-  return NULL;
-}
-
-// Set the value of a pixel
-void
-dmnsn_canvas_set_pixel(dmnsn_canvas *canvas, size_t x, size_t y,
-                       dmnsn_tcolor tcolor)
-{
-  dmnsn_assert(x < canvas->width && y < canvas->height,
-               "Canvas access out of bounds.");
-  dmnsn_assert(!dmnsn_tcolor_isnan(tcolor), "Pixel has NaN component.");
-
-  // Set the pixel
-  canvas->pixels[y*canvas->width + x] = tcolor;
-
-  // Call the optimizers
-  DMNSN_ARRAY_FOREACH (dmnsn_canvas_optimizer **, i, canvas->optimizers) {
-    (*i)->optimizer_fn(*i, canvas, x, y);
-  }
-}
-
-// Fill a canvas with a solid color
-void
-dmnsn_canvas_clear(dmnsn_canvas *canvas, dmnsn_tcolor tcolor)
-{
-  for (size_t x = 0; x < canvas->width; ++x) {
-    for (size_t y = 0; y < canvas->height; ++y) {
-      dmnsn_canvas_set_pixel(canvas, x, y, tcolor);
-    }
-  }
-}
diff --git a/libdimension/canvas/canvas.c b/libdimension/canvas/canvas.c
new file mode 100644
index 0000000..350cd60
--- /dev/null
+++ b/libdimension/canvas/canvas.c
@@ -0,0 +1,92 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Canveses.
+ */
+
+#include "dimension/canvas.h"
+
+dmnsn_canvas *
+dmnsn_new_canvas(dmnsn_pool *pool, size_t width, size_t height)
+{
+  dmnsn_canvas *canvas = DMNSN_PALLOC(pool, dmnsn_canvas);
+  canvas->width = width;
+  canvas->height = height;
+  canvas->optimizers = DMNSN_PALLOC_ARRAY(pool, dmnsn_canvas_optimizer *);
+  canvas->pixels = dmnsn_palloc(pool, sizeof(dmnsn_tcolor)*width*height);
+  return canvas;
+}
+
+void
+dmnsn_init_canvas_optimizer(dmnsn_canvas_optimizer *optimizer)
+{
+  optimizer->optimizer_fn = NULL;
+}
+
+// Set a canvas optimizer
+void
+dmnsn_canvas_optimize(dmnsn_canvas *canvas, const dmnsn_canvas_optimizer *optimizer)
+{
+  dmnsn_array_push(canvas->optimizers, &optimizer);
+}
+
+// Find an optimizer if it's already installed
+dmnsn_canvas_optimizer *
+dmnsn_canvas_find_optimizer(const dmnsn_canvas *canvas, dmnsn_canvas_optimizer_fn *optimizer_fn)
+{
+  DMNSN_ARRAY_FOREACH (dmnsn_canvas_optimizer **, i, canvas->optimizers) {
+    if ((*i)->optimizer_fn == optimizer_fn) {
+      return *i;
+    }
+  }
+
+  return NULL;
+}
+
+// Set the value of a pixel
+void
+dmnsn_canvas_set_pixel(dmnsn_canvas *canvas, size_t x, size_t y,
+                       dmnsn_tcolor tcolor)
+{
+  dmnsn_assert(x < canvas->width && y < canvas->height,
+               "Canvas access out of bounds.");
+  dmnsn_assert(!dmnsn_tcolor_isnan(tcolor), "Pixel has NaN component.");
+
+  // Set the pixel
+  canvas->pixels[y*canvas->width + x] = tcolor;
+
+  // Call the optimizers
+  DMNSN_ARRAY_FOREACH (dmnsn_canvas_optimizer **, i, canvas->optimizers) {
+    (*i)->optimizer_fn(*i, canvas, x, y);
+  }
+}
+
+// Fill a canvas with a solid color
+void
+dmnsn_canvas_clear(dmnsn_canvas *canvas, dmnsn_tcolor tcolor)
+{
+  for (size_t x = 0; x < canvas->width; ++x) {
+    for (size_t y = 0; y < canvas->height; ++y) {
+      dmnsn_canvas_set_pixel(canvas, x, y, tcolor);
+    }
+  }
+}
diff --git a/libdimension/canvas/gl-stubs.c b/libdimension/canvas/gl-stubs.c
new file mode 100644
index 0000000..df31308
--- /dev/null
+++ b/libdimension/canvas/gl-stubs.c
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Stubs for GL functions when compiled with --disable-gl.
+ */
+
+#include "dimension.h"
+#include <errno.h>
+
+int
+dmnsn_gl_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+int
+dmnsn_gl_write_canvas(const dmnsn_canvas *canvas)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+int
+dmnsn_gl_read_canvas(dmnsn_canvas *canvas, size_t x0, size_t y0)
+{
+  errno = ENOSYS;
+  return NULL;
+}
diff --git a/libdimension/canvas/gl.c b/libdimension/canvas/gl.c
new file mode 100644
index 0000000..900f53c
--- /dev/null
+++ b/libdimension/canvas/gl.c
@@ -0,0 +1,113 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * OpenGL import/export.
+ */
+
+#include "internal/rgba.h"
+#include "dimension/canvas.h"
+#include <GL/gl.h>
+#include <math.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+// Optimize canvas for GL drawing
+int
+dmnsn_gl_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
+{
+  dmnsn_rgba8_optimize_canvas(pool, canvas);
+  return 0;
+}
+
+// Write canvas to GL framebuffer.  Returns 0 on success, nonzero on failure
+int
+dmnsn_gl_write_canvas(const dmnsn_canvas *canvas)
+{
+  size_t width = canvas->width;
+  size_t height = canvas->height;
+
+  // Check if we can optimize this
+  dmnsn_rgba8_optimizer *rgba8 = (dmnsn_rgba8_optimizer *)dmnsn_canvas_find_optimizer(canvas, dmnsn_rgba8_optimizer_fn);
+  if (rgba8) {
+    glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, rgba8->data);
+    return glGetError() == GL_NO_ERROR ? 0 : 1;
+  }
+
+  // We couldn't, so transform the canvas to RGB now
+  GLubyte *pixels = dmnsn_malloc(4*width*height*sizeof(GLubyte));
+
+  for (size_t y = 0; y < height; ++y) {
+    for (size_t x = 0; x < width; ++x) {
+      GLubyte *pixel = pixels + 4*(y*width + x);
+
+      dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(canvas, x, y);
+      tcolor = dmnsn_tcolor_remove_filter(tcolor);
+      tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
+      tcolor = dmnsn_tcolor_clamp(tcolor);
+
+      pixel[0] = lround(tcolor.c.R*UINT8_MAX);
+      pixel[1] = lround(tcolor.c.G*UINT8_MAX);
+      pixel[2] = lround(tcolor.c.B*UINT8_MAX);
+      pixel[3] = lround(tcolor.T*UINT8_MAX);
+    }
+  }
+
+  glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
+  dmnsn_free(pixels);
+  return glGetError() == GL_NO_ERROR ? 0 : 1;
+}
+
+// Read a canvas from a GL framebuffer.  Returns NULL on failure.
+int
+dmnsn_gl_read_canvas(dmnsn_canvas *canvas, size_t x0, size_t y0)
+{
+  size_t width = canvas->width;
+  size_t height = canvas->height;
+
+  // Array of 16-bit ints in RGBA order
+  GLushort *pixels = dmnsn_malloc(4*width*height*sizeof(GLushort));
+  glReadPixels(x0, y0, width, height, GL_RGBA, GL_UNSIGNED_SHORT, pixels);
+  if (glGetError() != GL_NO_ERROR) {
+    dmnsn_free(pixels);
+    return -1;
+  }
+
+  for (size_t y = 0; y < height; ++y) {
+    for (size_t x = 0; x < width; ++x) {
+      GLushort *pixel = pixels + 4*(y*width + x);
+
+      dmnsn_tcolor tcolor = dmnsn_new_tcolor5(
+        (double)pixel[0]/UINT16_MAX,
+        (double)pixel[1]/UINT16_MAX,
+        (double)pixel[2]/UINT16_MAX,
+        (double)pixel[3]/UINT16_MAX,
+        0.0
+      );
+      tcolor.c = dmnsn_color_from_sRGB(tcolor.c);
+      dmnsn_canvas_set_pixel(canvas, x, y, tcolor);
+    }
+  }
+
+  dmnsn_free(pixels);
+  return 0;
+}
diff --git a/libdimension/canvas/png-stubs.c b/libdimension/canvas/png-stubs.c
new file mode 100644
index 0000000..9c752a5
--- /dev/null
+++ b/libdimension/canvas/png-stubs.c
@@ -0,0 +1,62 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Stubs for PNG functions when compiled with --disable-png.
+ */
+
+#include "dimension.h"
+#include <errno.h>
+
+int
+dmnsn_png_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+int
+dmnsn_png_write_canvas(const dmnsn_canvas *canvas, FILE *file)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+dmnsn_future *
+dmnsn_png_write_canvas_async(const dmnsn_canvas *canvas, FILE *file)
+{
+  errno = ENOSYS;
+  return NULL;
+}
+
+dmnsn_canvas *
+dmnsn_png_read_canvas(dmnsn_pool *pool, FILE *file)
+{
+  errno = ENOSYS;
+  return NULL;
+}
+
+dmnsn_future *
+dmnsn_png_read_canvas_async(dmnsn_canvas **canvas, dmnsn_pool *pool, FILE *file)
+{
+  errno = ENOSYS;
+  return NULL;
+}
diff --git a/libdimension/canvas/png.c b/libdimension/canvas/png.c
new file mode 100644
index 0000000..b1d29a3
--- /dev/null
+++ b/libdimension/canvas/png.c
@@ -0,0 +1,393 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * PNG import/export.
+ */
+
+#include "internal/concurrency.h"
+#include "internal/platform.h"
+#include "internal/rgba.h"
+#include "dimension/canvas.h"
+#include <png.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+int
+dmnsn_png_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
+{
+  dmnsn_rgba16_optimize_canvas(pool, canvas);
+  return 0;
+}
+
+/// Payload type for PNG write thread callback.
+typedef struct {
+  dmnsn_future *future;
+  const dmnsn_canvas *canvas;
+  FILE *file;
+} dmnsn_png_write_payload;
+
+/// Payload type for PNG read thread callback.
+typedef struct {
+  dmnsn_future *future;
+  dmnsn_canvas **canvas;
+  dmnsn_pool *pool;
+  FILE *file;
+} dmnsn_png_read_payload;
+
+/// PNG write thread callback.
+static int dmnsn_png_write_canvas_thread(void *ptr);
+/// PNG read thread callback.
+static int dmnsn_png_read_canvas_thread(void *ptr);
+
+int
+dmnsn_png_write_canvas(const dmnsn_canvas *canvas, FILE *file)
+{
+  dmnsn_future *future = dmnsn_png_write_canvas_async(canvas, file);
+  return dmnsn_future_join(future);
+}
+
+dmnsn_future *
+dmnsn_png_write_canvas_async(const dmnsn_canvas *canvas, FILE *file)
+{
+  dmnsn_future *future = dmnsn_new_future();
+
+  dmnsn_png_write_payload *payload = DMNSN_MALLOC(dmnsn_png_write_payload);
+  payload->future = future;
+  payload->canvas = canvas;
+  payload->file   = file;
+
+  // Create the worker thread
+  dmnsn_new_thread(future, dmnsn_png_write_canvas_thread, payload);
+
+  return future;
+}
+
+// Read a canvas from the PNG file `file'.  Return NULL on error.
+dmnsn_canvas *
+dmnsn_png_read_canvas(dmnsn_pool *pool, FILE *file)
+{
+  dmnsn_canvas *canvas;
+  dmnsn_future *future = dmnsn_png_read_canvas_async(&canvas, pool, file);
+  dmnsn_future_join(future);
+  return canvas;
+}
+
+// Read a canvas from a png file in the background
+dmnsn_future *
+dmnsn_png_read_canvas_async(dmnsn_canvas **canvas, dmnsn_pool *pool, FILE *file)
+{
+  dmnsn_future *future = dmnsn_new_future();
+  dmnsn_png_read_payload *payload = DMNSN_MALLOC(dmnsn_png_read_payload);
+
+  payload->future = future;
+  payload->canvas = canvas;
+  *payload->canvas = NULL;
+  payload->pool = pool;
+  payload->file = file;
+
+  // Create the worker thread
+  dmnsn_new_thread(future, dmnsn_png_read_canvas_thread, payload);
+
+  return future;
+}
+
+//////////////////////
+// Thread callbacks //
+//////////////////////
+
+// Write a PNG file
+static int
+dmnsn_png_write_canvas_thread(void *ptr)
+{
+  dmnsn_png_write_payload *payload = ptr;
+
+  png_uint_32 width = payload->canvas->width;
+  png_uint_32 height = payload->canvas->height;
+
+  dmnsn_future_set_total(payload->future, height);
+
+  png_structp png_ptr
+    = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+  if (!png_ptr) {
+    // Couldn't create libpng write struct
+    dmnsn_free(payload);
+    return -1;
+  }
+
+  png_infop info_ptr = png_create_info_struct(png_ptr);
+  if (!info_ptr) {
+    // Couldn't create libpng info struct
+    png_destroy_write_struct(&png_ptr, NULL);
+    dmnsn_free(payload);
+    return -1;
+  }
+
+  // libpng will longjmp here if it encounters an error from here on
+  uint16_t *row = NULL;
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    // libpng error
+    dmnsn_free(row);
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+    dmnsn_free(payload);
+    return -1;
+  }
+
+  // Associate file with the libpng write struct
+  png_init_io(png_ptr, payload->file);
+
+  // Set header correctly for 16-bit sRGB image
+  png_set_IHDR(png_ptr, info_ptr, width, height, 16,
+               PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+               PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+  png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_ABSOLUTE);
+
+  // We think of transparency in the opposite way that PNG does
+  png_set_invert_alpha(png_ptr);
+
+  // Write the info struct
+  png_write_info(png_ptr, info_ptr);
+
+  if (dmnsn_is_little_endian()) {
+    // We are little-endian; swap the byte order of the pixels
+    png_set_swap(png_ptr);
+  }
+
+  // Check if we can optimize this
+  dmnsn_rgba16_optimizer *rgba16 = (dmnsn_rgba16_optimizer *)dmnsn_canvas_find_optimizer(payload->canvas, dmnsn_rgba16_optimizer_fn);
+  if (rgba16) {
+    for (size_t y = 0; y < height; ++y) {
+      // Invert the rows.  PNG coordinates are fourth quadrant.
+      uint16_t *row_opt = rgba16->data + 4*(height - y - 1)*width;
+      png_write_row(png_ptr, (png_bytep)row_opt);
+      dmnsn_future_increment(payload->future);
+    }
+
+    // Finish the PNG file
+    png_write_end(png_ptr, info_ptr);
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+    dmnsn_free(payload);
+    return 0;
+  }
+
+  // Allocate the temporary row of RGBA values
+  row = dmnsn_malloc(4*sizeof(uint16_t)*width);
+
+  // Write the pixels
+  for (size_t y = 0; y < height; ++y) {
+    for (size_t x = 0; x < width; ++x) {
+      // Invert the rows.  PNG coordinates are fourth quadrant.
+      dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(payload->canvas,
+                                                   x, height - y - 1);
+      tcolor = dmnsn_tcolor_remove_filter(tcolor);
+      tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
+      tcolor = dmnsn_tcolor_clamp(tcolor);
+
+      row[4*x]     = lround(tcolor.c.R*UINT16_MAX);
+      row[4*x + 1] = lround(tcolor.c.G*UINT16_MAX);
+      row[4*x + 2] = lround(tcolor.c.B*UINT16_MAX);
+      row[4*x + 3] = lround(tcolor.T*UINT16_MAX);
+    }
+
+    // Write the row
+    png_write_row(png_ptr, (png_bytep)row);
+    dmnsn_future_increment(payload->future);
+  }
+
+  // Finish the PNG file
+  png_write_end(png_ptr, info_ptr);
+
+  dmnsn_free(row);
+  png_destroy_write_struct(&png_ptr, &info_ptr);
+  dmnsn_free(payload);
+  return 0;
+}
+
+/// Thread-specific pointer to the appropriate dmnsn_future* for
+/// dmnsn_png_read_row_callback.
+static __thread dmnsn_future *dmnsn_tl_png_read_future;
+
+/// Callback to increment the progress after a row has been read.
+static void
+dmnsn_png_read_row_callback(png_structp png_ptr, png_uint_32 row, int pass)
+{
+  dmnsn_future_increment(dmnsn_tl_png_read_future);
+}
+
+// Read a PNG file
+static int
+dmnsn_png_read_canvas_thread(void *ptr)
+{
+  dmnsn_png_read_payload *payload = ptr;
+  dmnsn_tl_png_read_future = payload->future;
+
+  png_byte header[8];
+  if (fread(header, 1, 8, payload->file) != 8) {
+    dmnsn_free(payload);
+    return -1;
+  }
+  if (png_sig_cmp(header, 0, 8)) {
+    // payload->file is not a PNG file
+    dmnsn_free(payload);
+    errno = EINVAL;
+    return -1;
+  }
+
+  // Create the libpng read struct
+  png_structp png_ptr
+    = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+  if (!png_ptr) {
+    dmnsn_free(payload);
+    return -1;
+  }
+
+  // Create the libpng info struct
+  png_infop info_ptr = png_create_info_struct(png_ptr);
+  if (!info_ptr) {
+    png_destroy_read_struct(&png_ptr, NULL, NULL);
+    dmnsn_free(payload);
+    return -1;
+  }
+
+  // libpng will longjmp here if it encounters an error from here on
+  png_bytep image = NULL;
+  png_bytep *row_pointers = NULL;
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    // libpng error
+    dmnsn_free(row_pointers);
+    dmnsn_free(image);
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    dmnsn_free(payload);
+    return -1;
+  }
+
+  // Associate the read struct with the file, and tell it we've already checked
+  // 8 bytes of signature
+  png_init_io(png_ptr, payload->file);
+  png_set_sig_bytes(png_ptr, 8);
+
+  // Read the PNG header into info struct
+  png_read_info(png_ptr, info_ptr);
+
+  // Get useful information from the info struct
+  png_uint_32 width, height;
+  int bit_depth, color_type, interlace_type, compression_type, filter_method;
+  png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+               &interlace_type, &compression_type, &filter_method);
+  int number_of_passes = png_set_interlace_handling(png_ptr);
+
+  dmnsn_future_set_total(payload->future, (number_of_passes + 1)*height);
+  png_set_read_status_fn(png_ptr, dmnsn_png_read_row_callback);
+
+  // - Convert paletted images to RGB.
+  // - Convert a tRNS chunk to an alpha channel
+  // - Convert grayscale to RGB
+  // - Invert the alpha channel
+  if (color_type == PNG_COLOR_TYPE_PALETTE) {
+    png_set_palette_to_rgb(png_ptr);
+  }
+  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+    png_set_tRNS_to_alpha(png_ptr);
+  }
+  if (color_type == PNG_COLOR_TYPE_GRAY
+      || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+  {
+    png_set_gray_to_rgb(png_ptr);
+  }
+  png_set_invert_alpha(png_ptr);
+
+  // Update the info struct
+  png_read_update_info(png_ptr, info_ptr);
+
+  // Get bytes/image row
+  png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
+
+  // Allocate the temporary image buffer
+  image = dmnsn_malloc(rowbytes*height);
+
+  // Allocate and set an array of pointers to rows in image
+  row_pointers = dmnsn_malloc(sizeof(png_bytep)*height);
+
+  for (size_t y = 0; y < height; ++y) {
+    row_pointers[y] = image + y*rowbytes;
+  }
+
+  // Read the image to memory all at once.  At the expense of greater memory
+  // use, this handles interlacing for us.
+  png_read_image(png_ptr, row_pointers);
+
+  // Allocate the canvas
+  *payload->canvas = dmnsn_new_canvas(payload->pool, width, height);
+
+  // Now we convert the image to our canvas format.  This depends on the image
+  // bit depth (which has been scaled up to at least 8 or 16), and the presence
+  // of an alpha channel.
+  for (size_t y = 0; y < height; ++y) {
+    for (size_t x = 0; x < width; ++x) {
+      dmnsn_tcolor tcolor;
+      tcolor.F = 0.0;
+
+      if (color_type & PNG_COLOR_MASK_ALPHA) {
+        if (bit_depth == 16) {
+          png_bytep png_pixel = image + 8*(y*width + x);
+          tcolor.c.R = (double)((png_pixel[0] << 8) + png_pixel[1])/UINT16_MAX;
+          tcolor.c.G = (double)((png_pixel[2] << 8) + png_pixel[3])/UINT16_MAX;
+          tcolor.c.B = (double)((png_pixel[4] << 8) + png_pixel[5])/UINT16_MAX;
+          tcolor.T   = (double)((png_pixel[6] << 8) + png_pixel[7])/UINT16_MAX;
+        } else {
+          png_bytep png_pixel = image + 4*(y*width + x);
+          tcolor.c.R = (double)png_pixel[0]/UINT8_MAX;
+          tcolor.c.G = (double)png_pixel[1]/UINT8_MAX;
+          tcolor.c.B = (double)png_pixel[2]/UINT8_MAX;
+          tcolor.T   = (double)png_pixel[3]/UINT8_MAX;
+        }
+      } else {
+        tcolor.T = 0.0;
+
+        if (bit_depth == 16) {
+          png_bytep png_pixel = image + 6*(y*width + x);
+          tcolor.c.R = (double)((png_pixel[0] << 8) + png_pixel[1])/UINT16_MAX;
+          tcolor.c.G = (double)((png_pixel[2] << 8) + png_pixel[3])/UINT16_MAX;
+          tcolor.c.B = (double)((png_pixel[4] << 8) + png_pixel[5])/UINT16_MAX;
+        } else {
+          png_bytep png_pixel = image + 3*(y*width + x);
+          tcolor.c.R = (double)png_pixel[0]/UINT8_MAX;
+          tcolor.c.G = (double)png_pixel[1]/UINT8_MAX;
+          tcolor.c.B = (double)png_pixel[2]/UINT8_MAX;
+        }
+      }
+
+      tcolor.c = dmnsn_color_from_sRGB(tcolor.c);
+      dmnsn_canvas_set_pixel(*payload->canvas, x, height - y - 1, tcolor);
+    }
+
+    dmnsn_future_increment(payload->future);
+  }
+
+  dmnsn_free(row_pointers);
+  dmnsn_free(image);
+  png_read_end(png_ptr, NULL);
+  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+  dmnsn_free(payload);
+  return 0;
+}
diff --git a/libdimension/canvas/rgba.c b/libdimension/canvas/rgba.c
new file mode 100644
index 0000000..4abf457
--- /dev/null
+++ b/libdimension/canvas/rgba.c
@@ -0,0 +1,95 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * 16-bit RGBA canvas optimizer.
+ */
+
+#include "internal/rgba.h"
+#include <stdint.h>
+
+void
+dmnsn_rgba8_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
+{
+  if (dmnsn_canvas_find_optimizer(canvas, dmnsn_rgba8_optimizer_fn)) {
+    return;
+  }
+
+  size_t ndata = 4*canvas->width*canvas->height;
+  dmnsn_rgba8_optimizer *rgba8 = dmnsn_palloc(pool, sizeof(dmnsn_rgba8_optimizer) + ndata*sizeof(uint8_t));
+
+  dmnsn_canvas_optimizer *optimizer = &rgba8->optimizer;
+  dmnsn_init_canvas_optimizer(optimizer);
+  optimizer->optimizer_fn = dmnsn_rgba8_optimizer_fn;
+
+  dmnsn_canvas_optimize(canvas, optimizer);
+}
+
+void
+dmnsn_rgba16_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
+{
+  if (dmnsn_canvas_find_optimizer(canvas, dmnsn_rgba16_optimizer_fn)) {
+    return;
+  }
+
+  size_t ndata = 4*canvas->width*canvas->height;
+  dmnsn_rgba16_optimizer *rgba16 = dmnsn_palloc(pool, sizeof(dmnsn_rgba16_optimizer) + ndata*sizeof(uint16_t));
+
+  dmnsn_canvas_optimizer *optimizer = &rgba16->optimizer;
+  dmnsn_init_canvas_optimizer(optimizer);
+  optimizer->optimizer_fn = dmnsn_rgba16_optimizer_fn;
+
+  dmnsn_canvas_optimize(canvas, optimizer);
+}
+
+void
+dmnsn_rgba8_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y)
+{
+  dmnsn_rgba8_optimizer *rgba8 = (dmnsn_rgba8_optimizer *)optimizer;
+
+  uint8_t *pixel = rgba8->data + 4*(y*canvas->width + x);
+  dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(canvas, x, y);
+  tcolor = dmnsn_tcolor_remove_filter(tcolor);
+  tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
+  tcolor = dmnsn_tcolor_clamp(tcolor);
+
+  pixel[0] = lround(tcolor.c.R*UINT8_MAX);
+  pixel[1] = lround(tcolor.c.G*UINT8_MAX);
+  pixel[2] = lround(tcolor.c.B*UINT8_MAX);
+  pixel[3] = lround(tcolor.T*UINT8_MAX);
+}
+
+void
+dmnsn_rgba16_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y)
+{
+  dmnsn_rgba16_optimizer *rgba16 = (dmnsn_rgba16_optimizer *)optimizer;
+
+  uint16_t *pixel = rgba16->data + 4*(y*canvas->width + x);
+  dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(canvas, x, y);
+  tcolor = dmnsn_tcolor_remove_filter(tcolor);
+  tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
+  tcolor = dmnsn_tcolor_clamp(tcolor);
+
+  pixel[0] = lround(tcolor.c.R*UINT16_MAX);
+  pixel[1] = lround(tcolor.c.G*UINT16_MAX);
+  pixel[2] = lround(tcolor.c.B*UINT16_MAX);
+  pixel[3] = lround(tcolor.T*UINT16_MAX);
+}
diff --git a/libdimension/canvas_pigment.c b/libdimension/canvas_pigment.c
deleted file mode 100644
index 2b1e2b6..0000000
--- a/libdimension/canvas_pigment.c
+++ /dev/null
@@ -1,57 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Image maps.
- */
-
-#include "dimension.h"
-
-/// Canvas pigment type.
-typedef struct dmnsn_canvas_pigment {
-  dmnsn_pigment pigment;
-  dmnsn_canvas *canvas;
-} dmnsn_canvas_pigment;
-
-/// Canvas pigment color callback.
-static dmnsn_tcolor
-dmnsn_canvas_pigment_fn(const dmnsn_pigment *pigment, dmnsn_vector v)
-{
-  const dmnsn_canvas_pigment *canvas_pigment = (const dmnsn_canvas_pigment *)pigment;
-  dmnsn_canvas *canvas = canvas_pigment->canvas;
-
-  size_t x = llround((fmod(v.x, 1.0) + 1.0)*(canvas->width  - 1));
-  size_t y = llround((fmod(v.y, 1.0) + 1.0)*(canvas->height - 1));
-  return dmnsn_canvas_get_pixel(canvas, x%canvas->width, y%canvas->height);
-}
-
-// Create a canvas color
-dmnsn_pigment *
-dmnsn_new_canvas_pigment(dmnsn_pool *pool, dmnsn_canvas *canvas)
-{
-  dmnsn_canvas_pigment *canvas_pigment = DMNSN_PALLOC(pool, dmnsn_canvas_pigment);
-  canvas_pigment->canvas = canvas;
-
-  dmnsn_pigment *pigment = &canvas_pigment->pigment;
-  dmnsn_init_pigment(pigment);
-  pigment->pigment_fn = dmnsn_canvas_pigment_fn;
-  return pigment;
-}
diff --git a/libdimension/checker.c b/libdimension/checker.c
deleted file mode 100644
index 740abe8..0000000
--- a/libdimension/checker.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Checker pattern.
- */
-
-#include "dimension-internal.h"
-
-/// Checker pattern callback.
-static double
-dmnsn_checker_pattern_fn(const dmnsn_pattern *checker, dmnsn_vector v)
-{
-  double xmod = fmod(v.x, 2.0);
-  double ymod = fmod(v.y, 2.0);
-  double zmod = fmod(v.z, 2.0);
-
-  if (xmod < -dmnsn_epsilon)
-    xmod += 2.0;
-  if (ymod < -dmnsn_epsilon)
-    ymod += 2.0;
-  if (zmod < -dmnsn_epsilon)
-    zmod += 2.0;
-
-  // Return 0 when an even number of coordinates are in [0, 1), 1 otherwise
-  unsigned int n = 0;
-  if (xmod >= 1.0)
-    ++n;
-  if (ymod >= 1.0)
-    ++n;
-  if (zmod >= 1.0)
-    ++n;
-  return (n%2 == 0) ? 0.0 : 1.0;
-}
-
-/// The singleton instance.
-static dmnsn_pattern dmnsn_checker_instance = {
-  .pattern_fn = dmnsn_checker_pattern_fn,
-};
-
-dmnsn_pattern *
-dmnsn_new_checker_pattern(dmnsn_pool *pool)
-{
-  return &dmnsn_checker_instance;
-}
diff --git a/libdimension/compiler-internal.h b/libdimension/compiler-internal.h
deleted file mode 100644
index 40e749d..0000000
--- a/libdimension/compiler-internal.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Internally-used compiler abstractions.
- */
-
-#include <stdalign.h>
-
-/**
- * @def dmnsn_likely
- * Indicate that a test is likely to succeed.
- * @param test  The test to perform.
- * @return The truth value of \p test.
- */
-/**
- * @def dmnsn_unlikely
- * Indicate that a test is unlikely to succeed.
- * @param test  The test to perform.
- * @return The truth value of \p test.
- */
-#ifdef DMNSN_PROFILE
-  #define dmnsn_likely(test)                                      \
-    dmnsn_expect(!!(test), true, DMNSN_FUNC, __FILE__, __LINE__)
-  #define dmnsn_unlikely(test)                                    \
-    dmnsn_expect(!!(test), false, DMNSN_FUNC, __FILE__, __LINE__)
-#elif DMNSN_GNUC
-  #define dmnsn_likely(test)   __builtin_expect(!!(test), true)
-  #define dmnsn_unlikely(test) __builtin_expect(!!(test), false)
-#else
-  #define dmnsn_likely(test)   (!!(test))
-  #define dmnsn_unlikely(test) (!!(test))
-#endif
-
-/**
- * @def DMNSN_HOT
- * Mark a function as a hot path.
- */
-/**
- * @def DMNSN_INTERNAL
- * Mark a function as internal linkage.
- */
-/**
- * @def DMNSN_DESTRUCTOR
- * Queue a function to run at program termination.
- */
-/**
- * @def DMNSN_LATE_DESTRUCTOR
- * Queue a function to run at program termination, after those labeled
- * DMNSN_DESTRUCTOR.
- */
-#if DMNSN_GNUC
-  #define DMNSN_HOT             __attribute__((hot))
-  #define DMNSN_INTERNAL        __attribute__((visibility("hidden")))
-  #define DMNSN_DESTRUCTOR      __attribute__((destructor(102)))
-  #define DMNSN_LATE_DESTRUCTOR __attribute__((destructor(101)))
-#else
-  #define DMNSN_HOT
-  #define DMNSN_INTERNAL
-  #define DMNSN_DESTRUCTOR
-  #define DMNSN_LATE_DESTRUCTOR
-#endif
-
-/// Synonym for _Atomic that stdatomic.h doesn't define for some reason
-#define atomic _Atomic
-
-/// C11-compliant alloca variant
-#define DMNSN_ALLOCA(var, size) DMNSN_ALLOCA_IMPL(var, size, __LINE__)
-
-#define DMNSN_ALLOCA_IMPL(var, size, ctr) DMNSN_ALLOCA_IMPL2(var, size, ctr)
-
-#define DMNSN_ALLOCA_IMPL2(var, size, ctr)              \
-  alignas(max_align_t) char dmnsn_alloca##ctr[size];    \
-  var = (void *)dmnsn_alloca##ctr
diff --git a/libdimension/concurrency/future.c b/libdimension/concurrency/future.c
new file mode 100644
index 0000000..90ffa24
--- /dev/null
+++ b/libdimension/concurrency/future.c
@@ -0,0 +1,281 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Future objects.
+ */
+
+#include "internal.h"
+#include "internal/concurrency.h"
+#include "internal/future.h"
+#include <pthread.h>
+
+/**
+ * Since C doesn't support anything like C++'s mutable, we fake it by casting
+ * away the constness.  This is okay since all valid dmnsn_futures live on the
+ * heap, so cannot be const.
+ */
+#define MUTATE(future) ((dmnsn_future *)(future))
+
+// Allocate a new dmnsn_future*
+dmnsn_future *
+dmnsn_new_future(void)
+{
+  dmnsn_future *future = DMNSN_MALLOC(dmnsn_future);
+  future->progress = 0;
+  future->total    = 1;
+
+  dmnsn_initialize_mutex(&future->mutex);
+  dmnsn_initialize_cond(&future->cond);
+
+  future->min_wait = 1.0;
+
+  future->nthreads = future->nrunning = 1;
+  future->npaused = 0;
+  dmnsn_initialize_cond(&future->none_running_cond);
+  dmnsn_initialize_cond(&future->all_running_cond);
+  dmnsn_initialize_cond(&future->resume_cond);
+
+  return future;
+}
+
+static void
+dmnsn_delete_future(dmnsn_future *future)
+{
+  if (future) {
+    dmnsn_destroy_cond(&future->resume_cond);
+    dmnsn_destroy_cond(&future->all_running_cond);
+    dmnsn_destroy_cond(&future->none_running_cond);
+    dmnsn_destroy_cond(&future->cond);
+    dmnsn_destroy_mutex(&future->mutex);
+    dmnsn_free(future);
+  }
+}
+
+// Join the worker thread and delete `future'.
+int
+dmnsn_future_join(dmnsn_future *future)
+{
+  void *ptr;
+  int retval = -1;
+
+  if (future) {
+    dmnsn_assert(future->npaused == 0, "Attempt to join future while paused");
+
+    // Get the thread's return value
+    dmnsn_join_thread(future->thread, &ptr);
+    if (ptr && ptr != PTHREAD_CANCELED) {
+      retval = *(int *)ptr;
+      dmnsn_free(ptr);
+    }
+
+    // Free the future object
+    dmnsn_delete_future(future);
+  }
+
+  return retval;
+}
+
+// Cancel a background thread
+void
+dmnsn_future_cancel(dmnsn_future *future)
+{
+  pthread_cancel(future->thread);
+}
+
+/**
+ * Get the current progress, without locking anything.
+ *
+ * future->mutex must be locked for this call to be safe.
+ */
+static inline double
+dmnsn_future_progress_unlocked(const dmnsn_future *future)
+{
+  return (double)future->progress/future->total;
+}
+
+// Get the current progress of the worker thread, in [0.0, 1.0]
+double
+dmnsn_future_progress(const dmnsn_future *future)
+{
+  dmnsn_future *mfuture = MUTATE(future);
+  double progress;
+
+  dmnsn_lock_mutex(&mfuture->mutex);
+    progress = dmnsn_future_progress_unlocked(mfuture);
+  dmnsn_unlock_mutex(&mfuture->mutex);
+
+  return progress;
+}
+
+// Find out whether the task is complete.
+bool
+dmnsn_future_is_done(const dmnsn_future *future)
+{
+  dmnsn_future *mfuture = MUTATE(future);
+  bool result;
+
+  dmnsn_lock_mutex(&mfuture->mutex);
+    result = future->progress == future->total;
+  dmnsn_unlock_mutex(&mfuture->mutex);
+
+  return result;
+}
+
+// Wait until dmnsn_future_progress(future) >= progress
+void
+dmnsn_future_wait(const dmnsn_future *future, double progress)
+{
+  dmnsn_future *mfuture = MUTATE(future);
+
+  dmnsn_lock_mutex(&mfuture->mutex);
+    while (dmnsn_future_progress_unlocked(mfuture) < progress) {
+      // Set the minimum waited-on value
+      if (progress < mfuture->min_wait) {
+        mfuture->min_wait = progress;
+      }
+
+      dmnsn_cond_wait_safely(&mfuture->cond, &mfuture->mutex);
+    }
+  dmnsn_unlock_mutex(&mfuture->mutex);
+}
+
+// Pause all threads working on a future.
+void
+dmnsn_future_pause(dmnsn_future *future)
+{
+  dmnsn_lock_mutex(&future->mutex);
+    while (future->nrunning < future->nthreads) {
+      dmnsn_cond_wait_safely(&future->all_running_cond, &future->mutex);
+    }
+    ++future->npaused;
+    while (future->nrunning > 0) {
+      dmnsn_cond_wait_safely(&future->none_running_cond, &future->mutex);
+    }
+  dmnsn_unlock_mutex(&future->mutex);
+}
+
+// Resume all threads working on a future.
+void
+dmnsn_future_resume(dmnsn_future *future)
+{
+  dmnsn_lock_mutex(&future->mutex);
+    dmnsn_assert(future->npaused > 0, "dmnsn_future_resume() without matching dmnsn_future_pause()");
+    if (--future->npaused == 0) {
+      dmnsn_cond_broadcast(&future->resume_cond);
+    }
+  dmnsn_unlock_mutex(&future->mutex);
+}
+
+// Set the total number of loop iterations
+void
+dmnsn_future_set_total(dmnsn_future *future, size_t total)
+{
+  dmnsn_lock_mutex(&future->mutex);
+    future->total = total;
+  dmnsn_unlock_mutex(&future->mutex);
+}
+
+static void
+dmnsn_future_increment_cleanup(void *ptr)
+{
+  dmnsn_future *future = ptr;
+  ++future->nrunning;
+  dmnsn_unlock_mutex_impl(&future->mutex);
+}
+
+// Increment the number of completed loop iterations
+void
+dmnsn_future_increment(dmnsn_future *future)
+{
+  // Allow a thread to be canceled whenever it increments a future object --
+  // this is close to PTHREAD_CANCEL_ASYNCHRONOUS but allows consistent state
+  // on cancellation
+  pthread_testcancel();
+
+  dmnsn_lock_mutex(&future->mutex);
+    ++future->progress;
+
+    if (dmnsn_future_progress_unlocked(future) >= future->min_wait) {
+      future->min_wait = 1.0;
+      dmnsn_cond_broadcast(&future->cond);
+    }
+
+    if (future->npaused > 0) {
+      dmnsn_assert(future->nrunning > 0, "More worker threads than expected");
+
+      if (--future->nrunning == 0) {
+        dmnsn_cond_broadcast(&future->none_running_cond);
+      }
+
+      pthread_cleanup_push(dmnsn_future_increment_cleanup, future);
+        do {
+          dmnsn_cond_wait(&future->resume_cond, &future->mutex);
+        } while (future->npaused > 0);
+      pthread_cleanup_pop(false);
+
+      if (++future->nrunning == future->nthreads) {
+        dmnsn_cond_broadcast(&future->all_running_cond);
+      }
+    }
+  dmnsn_unlock_mutex(&future->mutex);
+}
+
+// Immediately set to 100% completion
+void
+dmnsn_future_finish(dmnsn_future *future)
+{
+  dmnsn_lock_mutex(&future->mutex);
+    future->progress = future->total;
+    future->nthreads = future->nrunning = 0;
+    dmnsn_cond_broadcast(&future->cond);
+    dmnsn_cond_broadcast(&future->none_running_cond);
+    dmnsn_cond_broadcast(&future->all_running_cond);
+  dmnsn_unlock_mutex(&future->mutex);
+}
+
+// Set the number of threads
+void
+dmnsn_future_set_nthreads(dmnsn_future *future, unsigned int nthreads)
+{
+  dmnsn_lock_mutex(&future->mutex);
+    dmnsn_assert(future->nrunning == future->nthreads,
+                 "dmnsn_future_set_nthreads() called with paused threads");
+    future->nthreads = future->nrunning = nthreads;
+  dmnsn_unlock_mutex(&future->mutex);
+}
+
+// Notify completion of a worker thread
+void
+dmnsn_future_finish_thread(dmnsn_future *future)
+{
+  dmnsn_lock_mutex(&future->mutex);
+    dmnsn_assert(future->nthreads > 0,
+                 "dmnsn_future_finish_thread() called with no threads");
+    --future->nthreads;
+
+    dmnsn_assert(future->nrunning > 0,
+                 "dmnsn_future_finish_thread() called with no running threads");
+    if (--future->nrunning == 0) {
+      dmnsn_cond_broadcast(&future->none_running_cond);
+    }
+  dmnsn_unlock_mutex(&future->mutex);
+}
diff --git a/libdimension/concurrency/threads.c b/libdimension/concurrency/threads.c
new file mode 100644
index 0000000..93d2ea9
--- /dev/null
+++ b/libdimension/concurrency/threads.c
@@ -0,0 +1,326 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Background threading.
+ */
+
+#include "internal.h"
+#include "internal/concurrency.h"
+#include "internal/future.h"
+#include <pthread.h>
+
+/// The payload to pass to the pthread callback.
+typedef struct dmnsn_thread_payload {
+  dmnsn_thread_fn *thread_fn;
+  void *arg;
+  dmnsn_future *future;
+} dmnsn_thread_payload;
+
+/// Clean up after a thread.
+static void
+dmnsn_thread_cleanup(void *arg)
+{
+  dmnsn_thread_payload *payload = arg;
+  dmnsn_future *future = payload->future;
+  dmnsn_free(payload);
+
+  dmnsn_future_finish(future);
+}
+
+/// pthread callback -- call the real thread callback.
+static void *
+dmnsn_thread(void *arg)
+{
+  dmnsn_thread_payload *payload = arg;
+  int *ret;
+
+  pthread_cleanup_push(dmnsn_thread_cleanup, payload);
+    ret  = DMNSN_MALLOC(int);
+    *ret = payload->thread_fn(payload->arg);
+  pthread_cleanup_pop(true);
+  return ret;
+}
+
+void
+dmnsn_new_thread(dmnsn_future *future, dmnsn_thread_fn *thread_fn, void *arg)
+{
+  dmnsn_thread_payload *payload = DMNSN_MALLOC(dmnsn_thread_payload);
+  payload->thread_fn = thread_fn;
+  payload->arg       = arg;
+  payload->future    = future;
+
+  if (pthread_create(&future->thread, NULL, dmnsn_thread, payload) != 0) {
+    dmnsn_error("Couldn't start thread.");
+  }
+}
+
+/// Payload for threads executed by dmnsn_execute_concurrently().
+typedef struct dmnsn_ccthread_payload {
+  dmnsn_future *future;
+  dmnsn_ccthread_fn *ccthread_fn;
+  void *arg;
+  unsigned int thread, nthreads;
+  int ret;
+  bool running;
+} dmnsn_ccthread_payload;
+
+static void *
+dmnsn_concurrent_thread(void *ptr)
+{
+  dmnsn_ccthread_payload *payload = ptr;
+  payload->ret = payload->ccthread_fn(payload->arg, payload->thread,
+                                      payload->nthreads);
+  if (payload->future) {
+    dmnsn_future_finish_thread(payload->future);
+  }
+  return NULL;
+}
+
+typedef struct dmnsn_ccthread_cleanup_payload {
+  dmnsn_future *future;
+  pthread_t *threads;
+  dmnsn_ccthread_payload *payloads;
+  unsigned int nthreads;
+} dmnsn_ccthread_cleanup_payload;
+
+static void
+dmnsn_ccthread_cleanup(void *ptr)
+{
+  dmnsn_ccthread_cleanup_payload *payload = ptr;
+
+  for (unsigned int i = 0; i < payload->nthreads; ++i) {
+    if (payload->payloads[i].running) {
+      pthread_cancel(payload->threads[i]);
+    }
+  }
+
+  for (unsigned int i = 0; i < payload->nthreads; ++i) {
+    if (payload->payloads[i].running) {
+      dmnsn_join_thread(payload->threads[i], NULL);
+    }
+  }
+
+  if (payload->future) {
+    dmnsn_future_set_nthreads(payload->future, 1);
+  }
+}
+
+int
+dmnsn_execute_concurrently(dmnsn_future *future, dmnsn_ccthread_fn *ccthread_fn,
+                           void *arg, unsigned int nthreads)
+{
+  dmnsn_assert(nthreads > 0, "Attempt to execute using 0 concurrent threads.");
+
+  if (future) {
+    dmnsn_future_set_nthreads(future, nthreads);
+  }
+
+  pthread_t threads[nthreads];
+  dmnsn_ccthread_payload payloads[nthreads];
+  for (unsigned int i = 0; i < nthreads; ++i) {
+    payloads[i].running = false;
+  }
+
+  int ret = 0;
+  dmnsn_ccthread_cleanup_payload cleanup_payload = {
+    .future = future,
+    .threads = threads,
+    .payloads = payloads,
+    .nthreads = nthreads,
+  };
+  pthread_cleanup_push(dmnsn_ccthread_cleanup, &cleanup_payload);
+    for (unsigned int i = 0; i < nthreads; ++i) {
+      payloads[i].future      = future;
+      payloads[i].ccthread_fn = ccthread_fn;
+      payloads[i].arg         = arg;
+      payloads[i].thread      = i;
+      payloads[i].nthreads    = nthreads;
+      payloads[i].ret         = -1;
+      if (pthread_create(&threads[i], NULL, dmnsn_concurrent_thread,
+                         &payloads[i]) != 0)
+      {
+        dmnsn_error("Couldn't start worker thread.");
+      }
+      payloads[i].running = true;
+    }
+
+    for (unsigned int i = 0; i < nthreads; ++i) {
+      dmnsn_join_thread(threads[i], NULL);
+      payloads[i].running = false;
+      if (payloads[i].ret != 0) {
+        ret = payloads[i].ret;
+      }
+    }
+  pthread_cleanup_pop(false);
+
+  if (future) {
+    dmnsn_future_set_nthreads(future, 1);
+  }
+
+  return ret;
+}
+
+// pthread wrappers
+
+void
+dmnsn_initialize_mutex(pthread_mutex_t *mutex)
+{
+  if (pthread_mutex_init(mutex, NULL) != 0) {
+    dmnsn_error("Couldn't initialize mutex.");
+  }
+}
+
+void
+dmnsn_lock_mutex_impl(pthread_mutex_t *mutex)
+{
+  if (pthread_mutex_lock(mutex) != 0) {
+    dmnsn_error("Couldn't lock mutex.");
+  }
+}
+
+void
+dmnsn_unlock_mutex_impl(void *mutex)
+{
+  if (pthread_mutex_unlock(mutex) != 0) {
+    dmnsn_error("Couldn't unlock mutex.");
+  }
+}
+
+void
+dmnsn_destroy_mutex(pthread_mutex_t *mutex)
+{
+  if (pthread_mutex_destroy(mutex) != 0) {
+    dmnsn_warning("Couldn't destroy mutex.");
+  }
+}
+
+void
+dmnsn_initialize_rwlock(pthread_rwlock_t *rwlock)
+{
+  if (pthread_rwlock_init(rwlock, NULL) != 0) {
+    dmnsn_error("Couldn't initialize read-write lock.");
+  }
+}
+
+void
+dmnsn_read_lock_impl(pthread_rwlock_t *rwlock)
+{
+  if (pthread_rwlock_rdlock(rwlock) != 0) {
+    dmnsn_error("Couldn't acquire read lock.");
+  }
+}
+
+void
+dmnsn_write_lock_impl(pthread_rwlock_t *rwlock)
+{
+  if (pthread_rwlock_wrlock(rwlock) != 0) {
+    dmnsn_error("Couldn't acquire write lock.");
+  }
+}
+
+void
+dmnsn_unlock_rwlock_impl(pthread_rwlock_t *rwlock)
+{
+  if (pthread_rwlock_unlock(rwlock) != 0) {
+    dmnsn_error("Couldn't unlock read-write lock.");
+  }
+}
+
+void
+dmnsn_destroy_rwlock(pthread_rwlock_t *rwlock)
+{
+  if (pthread_rwlock_destroy(rwlock) != 0) {
+    dmnsn_warning("Couldn't destroy read-write lock.");
+  }
+}
+
+void
+dmnsn_initialize_cond(pthread_cond_t *cond)
+{
+  if (pthread_cond_init(cond, NULL) != 0) {
+    dmnsn_error("Couldn't initialize condition variable.");
+  }
+}
+
+void
+dmnsn_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+  if (pthread_cond_wait(cond, mutex) != 0) {
+    dmnsn_error("Couldn't wait on condition variable.");
+  }
+}
+
+void
+dmnsn_cond_broadcast(pthread_cond_t *cond)
+{
+  if (pthread_cond_broadcast(cond) != 0) {
+    dmnsn_error("Couldn't signal condition variable.");
+  }
+}
+
+void
+dmnsn_destroy_cond(pthread_cond_t *cond)
+{
+  if (pthread_cond_destroy(cond) != 0) {
+    dmnsn_warning("Couldn't destroy condition variable.");
+  }
+}
+
+void
+dmnsn_once(pthread_once_t *once, dmnsn_once_fn *once_fn)
+{
+  if (pthread_once(once, once_fn) != 0) {
+    dmnsn_error("Couldn't call one-shot function.");
+  }
+}
+
+void
+dmnsn_key_create(pthread_key_t *key, dmnsn_callback_fn *destructor)
+{
+  if (pthread_key_create(key, destructor) != 0) {
+    dmnsn_error("Couldn't initialize thread-specific pointer.");
+  }
+}
+
+void
+dmnsn_setspecific(pthread_key_t key, const void *value)
+{
+  if (pthread_setspecific(key, value) != 0) {
+    dmnsn_error("Couldn't set thread-specific pointer.");
+  }
+}
+
+void
+dmnsn_key_delete(pthread_key_t key)
+{
+  if (pthread_key_delete(key) != 0) {
+    dmnsn_warning("Couldn't destroy thread-specific pointer.");
+  }
+}
+
+void
+dmnsn_join_thread(pthread_t thread, void **retval)
+{
+  if (pthread_join(thread, retval) != 0) {
+    dmnsn_error("Couldn't join thread.");
+  }
+}
diff --git a/libdimension/cone.c b/libdimension/cone.c
deleted file mode 100644
index 1e95b0a..0000000
--- a/libdimension/cone.c
+++ /dev/null
@@ -1,206 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Cones/cylinders.
- */
-
-#include "dimension.h"
-#include <math.h>
-
-/// Cone type.
-typedef struct dmnsn_cone {
-  dmnsn_object object;
-  double r1, r2;
-} dmnsn_cone;
-
-/// Intersection callback for a cone.
-static bool
-dmnsn_cone_intersection_fn(const dmnsn_object *object, dmnsn_line l,
-                           dmnsn_intersection *intersection)
-{
-  const dmnsn_cone *cone = (const dmnsn_cone *)object;
-  double r1 = cone->r1, r2 = cone->r2;
-
-  // Solve (x0 + nx*t)^2 + (z0 + nz*t)^2 == (((r2 - r1)*(y0 + ny*t) + r1 + r2)/2)^2
-  double poly[3], x[2];
-  poly[2] = l.n.x*l.n.x + l.n.z*l.n.z - l.n.y*l.n.y*(r2 - r1)*(r2 - r1)/4.0;
-  poly[1] = 2.0*(l.n.x*l.x0.x + l.n.z*l.x0.z)
-            - l.n.y*(r2 - r1)*(l.x0.y*(r2 - r1) + r2 + r1)/2.0;
-  poly[0] = l.x0.x*l.x0.x + l.x0.z*l.x0.z
-            - (l.x0.y*(r2 - r1) + r2 + r1)*(l.x0.y*(r2 - r1) + r2 + r1)/4.0;
-
-  size_t n = dmnsn_polynomial_solve(poly, 2, x);
-
-  if (n > 0) {
-    double t = x[0];
-    dmnsn_vector p;
-    if (n == 2) {
-      t = dmnsn_min(t, x[1]);
-      p = dmnsn_line_point(l, t);
-
-      if (p.y <= -1.0 || p.y >= 1.0) {
-        t = dmnsn_max(x[0], x[1]);
-        p = dmnsn_line_point(l, t);
-      }
-    } else {
-      p = dmnsn_line_point(l, t);
-    }
-
-    if (t >= 0.0 && p.y >= -1.0 && p.y <= 1.0) {
-      double r = ((r2 - r1)*p.y + r1 + r2)/2.0;
-      dmnsn_vector norm = dmnsn_new_vector(p.x, -r*(r2 - r1)/2.0, p.z);
-      intersection->t      = t;
-      intersection->normal = norm;
-      return true;
-    }
-  }
-
-  return false;
-}
-
-/// Inside callback for a cone.
-static bool
-dmnsn_cone_inside_fn(const dmnsn_object *object, dmnsn_vector point)
-{
-  const dmnsn_cone *cone = (const dmnsn_cone *)object;
-  double r1 = cone->r1, r2 = cone->r2;
-  double r = (point.y*(r2 - r1) + r1 + r2)/2.0;
-  return point.x*point.x + point.z*point.z < r*r
-         && point.y > -1.0 && point.y < 1.0;
-}
-
-/// Cone bounding callback.
-static dmnsn_bounding_box
-dmnsn_cone_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  const dmnsn_cone *cone = (const dmnsn_cone *)object;
-
-  double rmax = dmnsn_max(cone->r1, cone->r2);
-  dmnsn_bounding_box box = dmnsn_symmetric_bounding_box(dmnsn_new_vector(rmax, 1.0, rmax));
-  return dmnsn_transform_bounding_box(trans, box);
-}
-
-/// Cone vtable.
-static const dmnsn_object_vtable dmnsn_cone_vtable = {
-  .intersection_fn = dmnsn_cone_intersection_fn,
-  .inside_fn = dmnsn_cone_inside_fn,
-  .bounding_fn = dmnsn_cone_bounding_fn,
-};
-
-/// Cone cap type.
-typedef struct dmnsn_cone_cap {
-  dmnsn_object object;
-  double r;
-} dmnsn_cone_cap;
-
-/// Cone cap intersection function.
-static bool
-dmnsn_cone_cap_intersection_fn(const dmnsn_object *object, dmnsn_line l,
-                               dmnsn_intersection *intersection)
-{
-  if (l.n.y != 0.0) {
-    const dmnsn_cone_cap *cap = (const dmnsn_cone_cap *)object;
-    double r = cap->r;
-    double t = -l.x0.y/l.n.y;
-    dmnsn_vector p = dmnsn_line_point(l, t);
-    if (t >= 0.0 && p.x*p.x + p.z*p.z <= r*r) {
-      intersection->t      = t;
-      intersection->normal = dmnsn_new_vector(0.0, -1.0, 0.0);
-      return true;
-    }
-  }
-
-  return false;
-}
-
-/// Inside callback for a cone cap.
-static bool
-dmnsn_cone_cap_inside_fn(const dmnsn_object *object, dmnsn_vector point)
-{
-  return false;
-}
-
-/// Cone cap bounding callback.
-static dmnsn_bounding_box
-dmnsn_cone_cap_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  const dmnsn_cone_cap *cap = (const dmnsn_cone_cap *)object;
-  dmnsn_bounding_box box = dmnsn_symmetric_bounding_box(dmnsn_new_vector(cap->r, 0.0, cap->r));
-  return dmnsn_transform_bounding_box(trans, box);
-}
-
-/// Cone cap vtable.
-static const dmnsn_object_vtable dmnsn_cone_cap_vtable = {
-  .intersection_fn = dmnsn_cone_cap_intersection_fn,
-  .inside_fn = dmnsn_cone_cap_inside_fn,
-  .bounding_fn = dmnsn_cone_cap_bounding_fn,
-};
-
-/// Allocate a new cone cap.
-dmnsn_object *
-dmnsn_new_cone_cap(dmnsn_pool *pool, double r)
-{
-  dmnsn_cone_cap *cap = DMNSN_PALLOC(pool, dmnsn_cone_cap);
-  cap->r = r;
-
-  dmnsn_object *object = &cap->object;
-  dmnsn_init_object(object);
-  object->vtable = &dmnsn_cone_cap_vtable;
-  return object;
-}
-
-// Allocate a new cone object
-dmnsn_object *
-dmnsn_new_cone(dmnsn_pool *pool, double r1, double r2, bool open)
-{
-  dmnsn_cone *cone = DMNSN_PALLOC(pool, dmnsn_cone);
-  cone->r1 = r1;
-  cone->r2 = r2;
-
-  dmnsn_object *object = &cone->object;
-  dmnsn_init_object(object);
-  object->vtable = &dmnsn_cone_vtable;
-
-  if (open) {
-    return object;
-  }
-
-  // Implement closed cones as a union with the caps
-  dmnsn_object *cap1 = dmnsn_new_cone_cap(pool, r1);
-  dmnsn_object *cap2 = dmnsn_new_cone_cap(pool, r2);
-  cap1->intrinsic_trans = dmnsn_translation_matrix(
-    dmnsn_new_vector(0.0, -1.0, 0.0)
-  );
-  cap2->intrinsic_trans = dmnsn_translation_matrix(
-    dmnsn_new_vector(0.0, +1.0, 0.0)
-  );
-  // Flip the normal around for the top cap
-  cap2->intrinsic_trans.n[1][1] = -1.0;
-
-  dmnsn_array *withcaps = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
-  dmnsn_array_push(withcaps, &cone);
-  dmnsn_array_push(withcaps, &cap1);
-  dmnsn_array_push(withcaps, &cap2);
-  dmnsn_object *cone_cap_union = dmnsn_new_csg_union(pool, withcaps);
-
-  return cone_cap_union;
-}
diff --git a/libdimension/csg.c b/libdimension/csg.c
deleted file mode 100644
index e31cb0e..0000000
--- a/libdimension/csg.c
+++ /dev/null
@@ -1,332 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Constructive solid geometry.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-////////////
-// Unions //
-////////////
-
-typedef struct {
-  dmnsn_object object;
-  dmnsn_bvh *bvh;
-} dmnsn_csg_union;
-
-/// CSG union intersection callback.
-static bool
-dmnsn_csg_union_intersection_fn(const dmnsn_object *object,
-                                dmnsn_line line,
-                                dmnsn_intersection *intersection)
-{
-  const dmnsn_csg_union *csg = (const dmnsn_csg_union *)object;
-  return dmnsn_bvh_intersection(csg->bvh, line, intersection, true);
-}
-
-/// CSG union inside callback.
-static bool
-dmnsn_csg_union_inside_fn(const dmnsn_object *object, dmnsn_vector point)
-{
-  const dmnsn_csg_union *csg = (const dmnsn_csg_union *)object;
-  return dmnsn_bvh_inside(csg->bvh, point);
-}
-
-/// CSG union precomputation callback.
-static void
-dmnsn_csg_union_precompute_fn(dmnsn_object *object)
-{
-  dmnsn_csg_union *csg = (dmnsn_csg_union *)object;
-  csg->object.trans_inv = dmnsn_identity_matrix();
-
-  dmnsn_bvh *bvh = dmnsn_new_bvh(csg->object.children, DMNSN_BVH_PRTREE);
-  csg->bvh = bvh;
-  csg->object.bounding_box = dmnsn_bvh_bounding_box(bvh);
-}
-
-/// CSG union vtable.
-static const dmnsn_object_vtable dmnsn_csg_union_vtable = {
-  .intersection_fn = dmnsn_csg_union_intersection_fn,
-  .inside_fn = dmnsn_csg_union_inside_fn,
-  .precompute_fn = dmnsn_csg_union_precompute_fn,
-};
-
-/// CSG union destruction callback.
-static void
-dmnsn_csg_union_cleanup(void *ptr)
-{
-  dmnsn_csg_union *csg = ptr;
-  dmnsn_delete_bvh(csg->bvh);
-}
-
-// Bulk-load a union
-dmnsn_object *
-dmnsn_new_csg_union(dmnsn_pool *pool, dmnsn_array *objects)
-{
-  dmnsn_csg_union *csg = DMNSN_PALLOC_TIDY(pool, dmnsn_csg_union, dmnsn_csg_union_cleanup);
-  csg->bvh = NULL;
-
-  dmnsn_object *object = &csg->object;
-  dmnsn_init_object(object);
-
-  object->vtable = &dmnsn_csg_union_vtable;
-  object->children = objects;
-  object->split_children = true;
-
-  return object;
-}
-
-/**
- * Generic CSG intersection callback.
- * @param[in]  csg           The CSG object.
- * @param[in]  line          The intersection ray.
- * @param[out] intersection  The intersection data.
- * @param[in]  inside1       Whether the first object is allowed inside the
- *                           second object.
- * @param[in]  inside2       Whether the second object is allowed inside the
- *                           first object.
- * @return Whether \p line intersected \p csg.
- */
-static bool
-dmnsn_csg_intersection_fn(const dmnsn_object *csg, dmnsn_line line,
-                          dmnsn_intersection *intersection,
-                          bool inside1, bool inside2)
-{
-  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
-  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
-
-  dmnsn_intersection i1, i2;
-  bool is_i1 = dmnsn_object_intersection(A, line, &i1);
-  bool is_i2 = dmnsn_object_intersection(B, line, &i2);
-
-  double oldt = 0.0;
-  while (is_i1) {
-    i1.ray = line;
-    i1.t += oldt;
-    oldt = i1.t + dmnsn_epsilon;
-
-    dmnsn_vector point = dmnsn_line_point(i1.ray, i1.t);
-    if (inside2 ^ dmnsn_object_inside(B, point)) {
-      dmnsn_line newline = line;
-      newline.x0 = dmnsn_line_point(line, i1.t);
-      newline    = dmnsn_line_add_epsilon(newline);
-      is_i1 = dmnsn_object_intersection(A, newline, &i1);
-    } else {
-      break;
-    }
-  }
-
-  oldt = 0.0;
-  while (is_i2) {
-    i2.ray = line;
-    i2.t += oldt;
-    oldt = i2.t + dmnsn_epsilon;
-
-    dmnsn_vector point = dmnsn_line_point(i2.ray, i2.t);
-    if (inside1 ^ dmnsn_object_inside(A, point)) {
-      dmnsn_line newline = line;
-      newline.x0 = dmnsn_line_point(line, i2.t);
-      newline    = dmnsn_line_add_epsilon(newline);
-      is_i2 = dmnsn_object_intersection(B, newline, &i2);
-    } else {
-      break;
-    }
-  }
-
-  if (is_i1 && is_i2) {
-    if (i1.t < i2.t) {
-      *intersection = i1;
-    } else {
-      *intersection = i2;
-    }
-  } else if (is_i1) {
-    *intersection = i1;
-  } else if (is_i2) {
-    *intersection = i2;
-  } else {
-    return false;
-  }
-
-  return true;
-}
-
-///////////////////
-// Intersections //
-///////////////////
-
-/// CSG intersection intersection callback.
-static bool
-dmnsn_csg_intersection_intersection_fn(const dmnsn_object *csg,
-                                       dmnsn_line line,
-                                       dmnsn_intersection *intersection)
-{
-  return dmnsn_csg_intersection_fn(csg, line, intersection, true, true);
-}
-
-/// CSG intersection inside callback.
-static bool
-dmnsn_csg_intersection_inside_fn(const dmnsn_object *csg, dmnsn_vector point)
-{
-  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
-  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
-  return dmnsn_object_inside(A, point) && dmnsn_object_inside(B, point);
-}
-
-/// CSG intersection precomputation callback.
-static void
-dmnsn_csg_intersection_precompute_fn(dmnsn_object *csg)
-{
-  dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
-  dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
-
-  csg->trans_inv = dmnsn_identity_matrix();
-  csg->bounding_box.min = dmnsn_vector_max(A->bounding_box.min, B->bounding_box.min);
-  csg->bounding_box.max = dmnsn_vector_min(A->bounding_box.max, B->bounding_box.max);
-}
-
-/// CSG intersection vtable.
-static const dmnsn_object_vtable dmnsn_csg_intersection_vtable = {
-  .intersection_fn = dmnsn_csg_intersection_intersection_fn,
-  .inside_fn = dmnsn_csg_intersection_inside_fn,
-  .precompute_fn = dmnsn_csg_intersection_precompute_fn,
-};
-
-dmnsn_object *
-dmnsn_new_csg_intersection(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B)
-{
-  dmnsn_object *csg = dmnsn_new_object(pool);
-  csg->vtable = &dmnsn_csg_intersection_vtable;
-
-  csg->children = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
-  dmnsn_array_push(csg->children, &A);
-  dmnsn_array_push(csg->children, &B);
-
-  return csg;
-}
-
-/////////////////
-// Differences //
-/////////////////
-
-/// CSG difference intersection callback.
-static bool
-dmnsn_csg_difference_intersection_fn(const dmnsn_object *csg,
-                                     dmnsn_line line,
-                                     dmnsn_intersection *intersection)
-{
-  return dmnsn_csg_intersection_fn(csg, line, intersection, true, false);
-}
-
-/// CSG difference inside callback.
-static bool
-dmnsn_csg_difference_inside_fn(const dmnsn_object *csg, dmnsn_vector point)
-{
-  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
-  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
-  return dmnsn_object_inside(A, point)  && !dmnsn_object_inside(B, point);
-}
-
-/// CSG difference precomputation callback.
-static void
-dmnsn_csg_difference_precompute_fn(dmnsn_object *csg)
-{
-  dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
-
-  csg->trans_inv = dmnsn_identity_matrix();
-  csg->bounding_box = A->bounding_box;
-}
-
-/// CSG difference vtable.
-static const dmnsn_object_vtable dmnsn_csg_difference_vtable = {
-  .intersection_fn = dmnsn_csg_difference_intersection_fn,
-  .inside_fn = dmnsn_csg_difference_inside_fn,
-  .precompute_fn = dmnsn_csg_difference_precompute_fn,
-};
-
-dmnsn_object *
-dmnsn_new_csg_difference(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B)
-{
-  dmnsn_object *csg = dmnsn_new_object(pool);
-  csg->vtable = &dmnsn_csg_difference_vtable;
-
-  csg->children = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
-  dmnsn_array_push(csg->children, &A);
-  dmnsn_array_push(csg->children, &B);
-
-  return csg;
-}
-
-////////////
-// Merges //
-////////////
-
-/// CSG merge intersection callback.
-static bool
-dmnsn_csg_merge_intersection_fn(const dmnsn_object *csg,
-                                dmnsn_line line,
-                                dmnsn_intersection *intersection)
-{
-  return dmnsn_csg_intersection_fn(csg, line, intersection, false, false);
-}
-
-/// CSG merge inside callback.
-static bool
-dmnsn_csg_merge_inside_fn(const dmnsn_object *csg, dmnsn_vector point)
-{
-  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
-  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
-  return dmnsn_object_inside(A, point) || dmnsn_object_inside(B, point);
-}
-
-/// CSG merge precomputation callback.
-static void
-dmnsn_csg_merge_precompute_fn(dmnsn_object *csg)
-{
-  dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
-  dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
-
-  csg->trans_inv = dmnsn_identity_matrix();
-  csg->bounding_box.min = dmnsn_vector_min(A->bounding_box.min, B->bounding_box.min);
-  csg->bounding_box.max = dmnsn_vector_max(A->bounding_box.max, B->bounding_box.max);
-}
-
-/// CSG merge vtable.
-static const dmnsn_object_vtable dmnsn_csg_merge_vtable = {
-  .intersection_fn = dmnsn_csg_merge_intersection_fn,
-  .inside_fn = dmnsn_csg_merge_inside_fn,
-  .precompute_fn = dmnsn_csg_merge_precompute_fn,
-};
-
-dmnsn_object *
-dmnsn_new_csg_merge(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B)
-{
-  dmnsn_object *csg = dmnsn_new_object(pool);
-  csg->vtable = &dmnsn_csg_merge_vtable;
-
-  csg->children = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
-  dmnsn_array_push(csg->children, &A);
-  dmnsn_array_push(csg->children, &B);
-
-  return csg;
-}
diff --git a/libdimension/cube.c b/libdimension/cube.c
deleted file mode 100644
index c455467..0000000
--- a/libdimension/cube.c
+++ /dev/null
@@ -1,154 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Cubes.
- */
-
-#include "dimension.h"
-#include <math.h>
-
-/// Intersection callback for a cube.
-static bool
-dmnsn_cube_intersection_fn(const dmnsn_object *cube, dmnsn_line line,
-                           dmnsn_intersection *intersection)
-{
-  // Clip the given line against the X, Y, and Z slabs
-
-  dmnsn_vector nmin, nmax;
-  double tmin, tmax;
-
-  double tx1 = (-1.0 - line.x0.x)/line.n.x;
-  double tx2 = (+1.0 - line.x0.x)/line.n.x;
-
-  if (tx1 < tx2) {
-    tmin = tx1;
-    tmax = tx2;
-    nmin = dmnsn_new_vector(-1.0, 0.0, 0.0);
-    nmax = dmnsn_new_vector(+1.0, 0.0, 0.0);
-  } else {
-    tmin = tx2;
-    tmax = tx1;
-    nmin = dmnsn_new_vector(+1.0, 0.0, 0.0);
-    nmax = dmnsn_new_vector(-1.0, 0.0, 0.0);
-  }
-
-  if (tmin > tmax)
-    return false;
-
-  double ty1 = (-1.0 - line.x0.y)/line.n.y;
-  double ty2 = (+1.0 - line.x0.y)/line.n.y;
-
-  if (ty1 < ty2) {
-    if (ty1 > tmin) {
-      tmin = ty1;
-      nmin = dmnsn_new_vector(0.0, -1.0, 0.0);
-    }
-    if (ty2 < tmax) {
-      tmax = ty2;
-      nmax = dmnsn_new_vector(0.0, +1.0, 0.0);
-    }
-  } else {
-    if (ty2 > tmin) {
-      tmin = ty2;
-      nmin = dmnsn_new_vector(0.0, +1.0, 0.0);
-    }
-    if (ty1 < tmax) {
-      tmax = ty1;
-      nmax = dmnsn_new_vector(0.0, -1.0, 0.0);
-    }
-  }
-
-  if (tmin > tmax)
-    return false;
-
-  double tz1 = (-1.0 - line.x0.z)/line.n.z;
-  double tz2 = (+1.0 - line.x0.z)/line.n.z;
-
-  if (tz1 < tz2) {
-    if (tz1 > tmin) {
-      tmin = tz1;
-      nmin = dmnsn_new_vector(0.0, 0.0, -1.0);
-    }
-    if (tz2 < tmax) {
-      tmax = tz2;
-      nmax = dmnsn_new_vector(0.0, 0.0, +1.0);
-    }
-  } else {
-    if (tz2 > tmin) {
-      tmin = tz2;
-      nmin = dmnsn_new_vector(0.0, 0.0, +1.0);
-    }
-    if (tz1 < tmax) {
-      tmax = tz1;
-      nmax = dmnsn_new_vector(0.0, 0.0, -1.0);
-    }
-  }
-
-  if (tmin > tmax)
-    return false;
-
-  if (tmin < 0.0) {
-    tmin = tmax;
-    nmin = nmax;
-  }
-
-  if (tmin >= 0.0) {
-    intersection->t      = tmin;
-    intersection->normal = nmin;
-    return true;
-  } else {
-    return false;
-  }
-}
-
-/// Inside callback for a cube.
-static bool
-dmnsn_cube_inside_fn(const dmnsn_object *cube, dmnsn_vector point)
-{
-  return point.x > -1.0 && point.x < 1.0
-      && point.y > -1.0 && point.y < 1.0
-      && point.z > -1.0 && point.z < 1.0;
-}
-
-/// Boundary callback for a cube.
-static dmnsn_bounding_box
-dmnsn_cube_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  dmnsn_bounding_box box = dmnsn_symmetric_bounding_box(dmnsn_new_vector(1.0, 1.0, 1.0));
-  return dmnsn_transform_bounding_box(trans, box);
-}
-
-/// Cube vtable.
-static const dmnsn_object_vtable dmnsn_cube_vtable = {
-  .intersection_fn = dmnsn_cube_intersection_fn,
-  .inside_fn = dmnsn_cube_inside_fn,
-  .bounding_fn = dmnsn_cube_bounding_fn,
-};
-
-// Allocate a new cube object
-dmnsn_object *
-dmnsn_new_cube(dmnsn_pool *pool)
-{
-  dmnsn_object *cube = dmnsn_new_object(pool);
-  cube->vtable = &dmnsn_cube_vtable;
-  return cube;
-}
diff --git a/libdimension/dictionary.c b/libdimension/dictionary.c
deleted file mode 100644
index 84ebedf..0000000
--- a/libdimension/dictionary.c
+++ /dev/null
@@ -1,228 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Associative arrays, implemented with PATRICIA tries.
- */
-
-#include "dimension.h"
-
-struct dmnsn_dictionary {
-  size_t obj_size;       ///< The size of the objects in the trie.
-  char *prefix;          ///< The local string prefix of the current node.
-  void *value;           ///< The node's stored object, if it's a leaf.
-  dmnsn_array *children; ///< The node's children.
-};
-
-dmnsn_dictionary *
-dmnsn_new_dictionary(size_t obj_size)
-{
-  dmnsn_dictionary *dict = DMNSN_MALLOC(dmnsn_dictionary);
-  dict->obj_size = obj_size;
-  dict->prefix   = dmnsn_strdup("");
-  dict->value    = NULL;
-  dict->children = DMNSN_NEW_ARRAY(dmnsn_dictionary *);
-  return dict;
-}
-
-void
-dmnsn_delete_dictionary(dmnsn_dictionary *dict)
-{
-  if (dict) {
-    dmnsn_free(dict->prefix);
-    dmnsn_free(dict->value);
-    DMNSN_ARRAY_FOREACH (dmnsn_dictionary **, subtrie, dict->children) {
-      dmnsn_delete_dictionary(*subtrie);
-    }
-    dmnsn_delete_array(dict->children);
-
-    dmnsn_free(dict);
-  }
-}
-
-bool
-dmnsn_dictionary_get(const dmnsn_dictionary *dict, const char *key, void *obj)
-{
-  const void *value = dmnsn_dictionary_at(dict, key);
-  if (value) {
-    memcpy(obj, value, dict->obj_size);
-    return true;
-  } else {
-    return false;
-  }
-}
-
-void *
-dmnsn_dictionary_at(const dmnsn_dictionary *dict, const char *key)
-{
-  // PATRICIA trie search: O(k), where k is the length of the longest string in
-  // the trie.
-
-  size_t len = strlen(dict->prefix);
-  if (strncmp(key, dict->prefix, len) != 0)
-    return NULL;
-  key += len;
-
-  while (true) {
-    if (*key == '\0' && dict->value) {
-      return dict->value;
-    } else {
-      dmnsn_dictionary **first = dmnsn_array_first(dict->children), **subtrie;
-      ptrdiff_t size = dmnsn_array_size(dict->children);
-      for (subtrie = first; subtrie - first < size; ++subtrie) {
-        len = strlen((*subtrie)->prefix);
-        if (strncmp(key, (*subtrie)->prefix, len) == 0) {
-          dict = *subtrie;
-          key += len;
-          break;
-        }
-      }
-
-      if (subtrie - first == size)
-        break;
-    }
-  }
-
-  return NULL;
-}
-
-void
-dmnsn_dictionary_insert(dmnsn_dictionary *dict, const char *key,
-                        const void *obj)
-{
-  // PATRICIA trie insertion: O(k), where k is the length of the longest string
-  // in the trie.
-
-  while (true) {
-    if (dict->prefix[0] == '\0' && !dict->value
-        && dmnsn_array_size(dict->children) == 0)
-    {
-      // Replace an empty tree with a single-element tree
-      dict->prefix = dmnsn_realloc(dict->prefix, strlen(key) + 1);
-      strcpy(dict->prefix, key);
-
-      dict->value = dmnsn_malloc(dict->obj_size);
-      memcpy(dict->value, obj, dict->obj_size);
-      break;
-    }
-
-    char *prefix = dict->prefix;
-    while (*prefix == *key && *prefix && *key) {
-      ++prefix;
-      ++key;
-    }
-
-    if (*key == '\0' && *prefix == '\0') {
-      // Complete match
-      if (!dict->value) {
-        dict->value = dmnsn_malloc(dict->obj_size);
-      }
-      memcpy(dict->value, obj, dict->obj_size);
-      break;
-    } else if (*prefix == '\0') {
-      // Partial match; key starts with prefix
-      dmnsn_dictionary **first = dmnsn_array_first(dict->children), **subtrie;
-      ptrdiff_t size = dmnsn_array_size(dict->children);
-      for (subtrie = first; subtrie - first < size; ++subtrie) {
-        if ((*subtrie)->prefix[0] == key[0]) {
-          dict = *subtrie;
-          break;
-        }
-      }
-
-      if (subtrie - first == size) {
-        // No submatch found, add a new child
-        dmnsn_dictionary *child = dmnsn_new_dictionary(dict->obj_size);
-        dmnsn_array_push(dict->children, &child);
-        dict = child;
-      }
-    } else {
-      // Split the tree
-      dmnsn_dictionary *copy = dmnsn_new_dictionary(dict->obj_size);
-      copy->prefix = dmnsn_realloc(copy->prefix, strlen(prefix) + 1);
-      strcpy(copy->prefix, prefix);
-      *prefix = '\0';
-
-      copy->value = dict->value;
-      dict->value = NULL;
-
-      dmnsn_array *temp = copy->children;
-      copy->children = dict->children;
-      dict->children = temp;
-
-      dmnsn_dictionary *subtrie = dmnsn_new_dictionary(dict->obj_size);
-      dmnsn_array_push(dict->children, &copy);
-      dmnsn_array_push(dict->children, &subtrie);
-      dict = subtrie;
-    }
-  }
-}
-
-bool
-dmnsn_dictionary_remove(dmnsn_dictionary *dict, const char *key)
-{
-  // PATRICIA trie removal: O(k), where k is the length of the longest string
-  // in the trie.
-
-  // This implementation doesn't actually collapse the tree back upwards if a
-  // node is left with only one child, to reduce complexity.
-
-  size_t len = strlen(dict->prefix);
-  if (strncmp(key, dict->prefix, len) != 0)
-    return false;
-  key += len;
-
-  while (true) {
-    if (*key == '\0' && dict->value) {
-      dmnsn_free(dict->value);
-      dict->value = NULL;
-      return true;
-    } else {
-      dmnsn_dictionary **first = dmnsn_array_first(dict->children), **subtrie;
-      ptrdiff_t size = dmnsn_array_size(dict->children);
-      for (subtrie = first; subtrie - first < size; ++subtrie) {
-        len = strlen((*subtrie)->prefix);
-        if (strncmp(key, (*subtrie)->prefix, len) == 0) {
-          dict = *subtrie;
-          key += len;
-          break;
-        }
-      }
-
-      if (subtrie - first == size)
-        break;
-    }
-  }
-
-  return false;
-}
-
-void
-dmnsn_dictionary_apply(dmnsn_dictionary *dict, dmnsn_callback_fn *callback)
-{
-  if (dict->value) {
-    callback(dict->value);
-  }
-
-  DMNSN_ARRAY_FOREACH (dmnsn_dictionary **, subtrie, dict->children) {
-    dmnsn_dictionary_apply(*subtrie, callback);
-  }
-}
diff --git a/libdimension/dimension-internal.h b/libdimension/dimension-internal.h
deleted file mode 100644
index 53fa24c..0000000
--- a/libdimension/dimension-internal.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * The internal libdimension API.  These functions and types are used to
- * implement libdimension, but are not part of its public API.
- */
-
-#ifndef DIMENSION_INTERNAL_H
-#define DIMENSION_INTERNAL_H
-
-#include "dimension.h"
-#include "compiler-internal.h"
-#include "profile.h"
-#include "platform.h"
-#include "future-internal.h"
-#include "threads.h"
-#include "bvh.h"
-#include "prtree.h"
-#include "rgba.h"
-
-#endif // DIMENSION_INTERNAL_H
diff --git a/libdimension/dimension.h b/libdimension/dimension.h
index fc3a681..db67a48 100644
--- a/libdimension/dimension.h
+++ b/libdimension/dimension.h
@@ -22,6 +22,9 @@
  * @file
  * The main \#include file for the Dimension library.  This file declares all
  * of its public functions and types.
+ *
+ * To import only a subset of the libdimension's functionality, you can include
+ * the headers for submodules directly.
  */
 
 /**
@@ -31,55 +34,18 @@
  * all rendering-related tasks for Dimension.
  */
 
-#ifndef DIMENSION_H
-#define DIMENSION_H
-
-/* Include compiler.h first for DMNSN_CXX */
-#include <dimension/compiler.h>
+#ifndef DMNSN_H
+#define DMNSN_H
 
-#if DMNSN_CXX
-/* We've been included from a C++ file; mark everything here as extern "C" */
-extern "C" {
-#endif
-
-/* Include all the libdimension headers */
-#include <dimension/common.h>
-#include <dimension/error.h>
-#include <dimension/malloc.h>
-#include <dimension/pool.h>
-#include <dimension/array.h>
-#include <dimension/dictionary.h>
-#include <dimension/future.h>
-#include <dimension/timer.h>
+/* Include all modules. */
+#include <dimension/base.h>
+#include <dimension/platform.h>
+#include <dimension/concurrency.h>
 #include <dimension/math.h>
-#include <dimension/geometry.h>
-#include <dimension/polynomial.h>
 #include <dimension/color.h>
-#include <dimension/tcolor.h>
 #include <dimension/canvas.h>
-#include <dimension/gl.h>
-#include <dimension/png.h>
 #include <dimension/pattern.h>
-#include <dimension/patterns.h>
-#include <dimension/map.h>
-#include <dimension/pigment.h>
-#include <dimension/pigments.h>
-#include <dimension/finish.h>
-#include <dimension/finishes.h>
-#include <dimension/texture.h>
-#include <dimension/interior.h>
-#include <dimension/object.h>
-#include <dimension/objects.h>
-#include <dimension/csg.h>
-#include <dimension/light.h>
-#include <dimension/lights.h>
-#include <dimension/camera.h>
-#include <dimension/cameras.h>
-#include <dimension/scene.h>
-#include <dimension/ray_trace.h>
-
-#if DMNSN_CXX
-}
-#endif
+#include <dimension/model.h>
+#include <dimension/render.h>
 
-#endif /* DIMENSION_H */
+#endif /* DMNSN_H */
diff --git a/libdimension/dimension/array.h b/libdimension/dimension/array.h
deleted file mode 100644
index 9261a0e..0000000
--- a/libdimension/dimension/array.h
+++ /dev/null
@@ -1,357 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Simple dynamic arrays.
- */
-
-#include <stddef.h> /* For size_t */
-#include <stdlib.h> /* For qsort() */
-#include <string.h> /* For memcpy() */
-
-/** Dynamic array type. */
-typedef struct dmnsn_array {
-  void *ptr;       /**< @internal The actual memory. */
-  size_t obj_size; /**< @internal The size of each object. */
-  size_t length;   /**< @internal The current size of the array. */
-  size_t capacity; /**< @internal The size of the allocated space. */
-} dmnsn_array;
-
-/**
- * @internal
- * Initialize a new array.
- * @param[out] array  The array to initialize.
- * @param[in] obj_size  The size of the objects to store in the array.
- */
-DMNSN_INLINE void
-dmnsn_init_array(dmnsn_array *array, size_t obj_size)
-{
-  array->obj_size = obj_size;
-  array->length   = 0;
-  array->capacity = 2; /* Start with capacity of 2 */
-
-  /* Allocate the memory */
-  array->ptr = dmnsn_malloc(array->capacity*array->obj_size);
-}
-
-/**
- * Allocate an array.
- * @param[in] obj_size  The size of the objects to store in the array.
- * @return An empty array.
- */
-DMNSN_INLINE dmnsn_array *
-dmnsn_new_array(size_t obj_size)
-{
-  dmnsn_array *array = DMNSN_MALLOC(dmnsn_array);
-  dmnsn_init_array(array, obj_size);
-  return array;
-}
-
-/**
- * Allocate an array.
- * @param[in] type  Type type of element to store in the array.
- * @return An empty array.
- */
-#define DMNSN_NEW_ARRAY(type) (dmnsn_new_array(sizeof(type)))
-
-/**
- * Delete an array.
- * @param[in,out] array  The array to delete.
- */
-DMNSN_INLINE void
-dmnsn_delete_array(dmnsn_array *array)
-{
-  if (array) {
-    dmnsn_free(array->ptr);
-    dmnsn_free(array);
-  }
-}
-
-/**
- * @internal
- * Free a pool-allocated array.
- * @param[in,out] ptr  The array to clean up.
- */
-void dmnsn_array_cleanup(void *ptr);
-
-/**
- * Allocate an array from a pool.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] obj_size  The size of the objects to store in the array.
- * @return An empty array.
- */
-DMNSN_INLINE dmnsn_array *
-dmnsn_palloc_array(dmnsn_pool *pool, size_t obj_size)
-{
-  dmnsn_array *array = DMNSN_PALLOC_TIDY(pool, dmnsn_array, dmnsn_array_cleanup);
-  dmnsn_init_array(array, obj_size);
-  return array;
-}
-
-/**
- * Allocate an array from a pool.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] type  Type type of element to store in the array.
- * @return An empty array.
- */
-#define DMNSN_PALLOC_ARRAY(pool, type) (dmnsn_palloc_array(pool, sizeof(type)))
-
-/**
- * Get the size of the array.
- * @param[in] array  The array in question.
- * @return The number of elements in the array.
- */
-DMNSN_INLINE size_t
-dmnsn_array_size(const dmnsn_array *array)
-{
-  return array->length;
-}
-
-/**
- * Set the size of the array.
- * @param[in,out] array   The array to resize.
- * @param[in]     length  The new length of the array.
- */
-DMNSN_INLINE void
-dmnsn_array_resize(dmnsn_array *array, size_t length)
-{
-  if (length > array->capacity) {
-    /* Resize if we don't have enough capacity */
-    array->capacity = length*2; /* We are greedy */
-    array->ptr = dmnsn_realloc(array->ptr, array->obj_size*array->capacity);
-  }
-
-  array->length = length;
-}
-
-/**
- * Copy an array.
- * @param[in] array  The array to copy.
- * @return A copy of the array.
- */
-DMNSN_INLINE dmnsn_array *
-dmnsn_array_copy(const dmnsn_array *array)
-{
-  dmnsn_array *copy = dmnsn_new_array(array->obj_size);
-  dmnsn_array_resize(copy, dmnsn_array_size(array));
-  memcpy(copy->ptr, array->ptr, dmnsn_array_size(array)*array->obj_size);
-  return copy;
-}
-
-/**
- * Split an array in half.
- * @param[in,out] array  The array to split.
- * @return The second half of the array.
- */
-DMNSN_INLINE dmnsn_array *
-dmnsn_array_split(dmnsn_array *array)
-{
-  dmnsn_array *half = dmnsn_new_array(array->obj_size);
-  size_t new_size = dmnsn_array_size(array)/2;
-  size_t old_size = dmnsn_array_size(array) - new_size;
-  dmnsn_array_resize(half, new_size);
-  memcpy(half->ptr, (char *)array->ptr + old_size*array->obj_size,
-         new_size*array->obj_size);
-  dmnsn_array_resize(array, old_size);
-  return half;
-}
-
-/**
- * Get the i'th element.
- * @param[in]  array  The array to access.
- * @param[in]  i      The index of the element to extract.
- * @param[out] obj    The location to store the extracted object.
- */
-DMNSN_INLINE void
-dmnsn_array_get(const dmnsn_array *array, size_t i, void *obj)
-{
-  dmnsn_assert(i < dmnsn_array_size(array), "Array index out of bounds.");
-  memcpy(obj, (char *)array->ptr + array->obj_size*i, array->obj_size);
-}
-
-/**
- * Set the i'th object, expanding the array if necessary.
- * @param[in,out] array  The array to modify.
- * @param[in]     i      The index to update.
- * @param[in]     obj    The location of the object to copy into the array.
- */
-DMNSN_INLINE void
-dmnsn_array_set(dmnsn_array *array, size_t i, const void *obj)
-{
-  if (i >= dmnsn_array_size(array)) {
-    /* Resize if i is out of range */
-    dmnsn_array_resize(array, i + 1);
-  }
-  memcpy((char *)array->ptr + array->obj_size*i, obj, array->obj_size);
-}
-
-/**
- * First element.
- * @param[in] array  The array to index.
- * @return The address of the first element of the array.
- */
-DMNSN_INLINE void *
-dmnsn_array_first(const dmnsn_array *array)
-{
-  return array->ptr;
-}
-
-/**
- * Last element.
- * @param[in] array  The array to index.
- * @return The address of the last element of the array.
- */
-DMNSN_INLINE void *
-dmnsn_array_last(const dmnsn_array *array)
-{
-  return (char *)array->ptr + array->obj_size*(array->length - 1);
-}
-
-/**
- * Arbitrary element access.
- * @param[in] array  The array to index.
- * @param[in] i      The index to access.
- * @return The address of the i'th element of the array.
- */
-DMNSN_INLINE void *
-dmnsn_array_at(const dmnsn_array *array, size_t i)
-{
-  dmnsn_assert(i < dmnsn_array_size(array), "Array index out of bounds.");
-  return (char *)array->ptr + array->obj_size*i;
-}
-
-/**
- * Push an object to the end of the array.
- * @param[in,out] array  The array to append to.
- * @param[in]     obj    The location of the object to push.
- */
-DMNSN_INLINE void
-dmnsn_array_push(dmnsn_array *array, const void *obj)
-{
-  dmnsn_array_set(array, dmnsn_array_size(array), obj);
-}
-
-/**
- * Pop an object from the end of the array.
- * @param[in,out] array  The array to pop from.
- * @param[out]    obj    The location to store the extracted object.
- */
-DMNSN_INLINE void
-dmnsn_array_pop(dmnsn_array *array, void *obj)
-{
-  size_t size = dmnsn_array_size(array);
-  dmnsn_assert(size > 0, "Array is empty.");
-  dmnsn_array_get(array, size - 1, obj); /* Copy the object */
-  dmnsn_array_resize(array, size - 1);   /* Shrink the array */
-}
-
-/**
- * Insert an item into the middle of the array.  This is O(n).
- * @param[in,out] array  The array to insert to.
- * @param[in]     i      The index at which to insert.
- * @param[in]     obj    The object to insert.
- */
-DMNSN_INLINE void
-dmnsn_array_insert(dmnsn_array *array, size_t i, const void *obj)
-{
-  size_t size = dmnsn_array_size(array) + 1;
-  if (i >= size)
-    size = i + 1;
-  dmnsn_array_resize(array, size);
-
-  /* Move the elements at and after `i' 1 to the right */
-  memmove((char *)array->ptr + array->obj_size*(i + 1),
-          (char *)array->ptr + array->obj_size*i,
-          array->obj_size*(size - i - 1));
-  /* Insert `obj' at `i' */
-  memcpy((char *)array->ptr + array->obj_size*i, obj, array->obj_size);
-}
-
-/**
- * Remove an item from the middle of the array.  This is O(n).
- * @param[in,out] array  The array to remove from.
- * @param[in]     i      The index to remove.
- */
-DMNSN_INLINE void
-dmnsn_array_remove(dmnsn_array *array, size_t i)
-{
-  size_t size = dmnsn_array_size(array);
-  dmnsn_assert(i < size, "Array index out of bounds.");
-  /* Move the array elements after `i' 1 to the left */
-  memmove((char *)array->ptr + array->obj_size*i,
-          (char *)array->ptr + array->obj_size*(i + 1),
-          array->obj_size*(size - i - 1));
-  /* Decrease the size by 1 */
-  dmnsn_array_resize(array, size - 1);
-}
-
-/**
- * Apply a callback to each element of an array.
- * @param[in,out] array     The array.
- * @param[in]     callback  The callback to apply to the elements.
- */
-DMNSN_INLINE void
-dmnsn_array_apply(dmnsn_array *array, dmnsn_callback_fn *callback)
-{
-  char *i, *last = (char *)dmnsn_array_last(array);
-  for (i = (char *)dmnsn_array_first(array); i <= last; i += array->obj_size) {
-    callback((void *)i);
-  }
-}
-
-/**
- * Callback type for array sorting.
- * @param[in] a  The first element.
- * @param[in] b  The second element.
- * @return A negative value iff a < b, zero iff a == b, and a positive value iff
- *         a > b.
- */
-typedef int dmnsn_array_comparator_fn(const void *a, const void *b);
-
-/**
- * Sort an array.
- * @param[in,out] array       The array to sort.
- * @param[in]     comparator  The sorting comparator to use.
- */
-DMNSN_INLINE void
-dmnsn_array_sort(dmnsn_array *array, dmnsn_array_comparator_fn *comparator)
-{
-  qsort(array->ptr, dmnsn_array_size(array), array->obj_size, comparator);
-}
-
-/* Macros to shorten array iteration */
-
-/**
- * Iterate over an array.  For example,
- * @code
- * DMNSN_ARRAY_FOREACH (int *, i, array) {
- *   printf("%d\n", *i);
- * }
- * @endcode
- *
- * @param     type   The (pointer) type to use as an iterator.
- * @param     i      The name of the iterator within the loop body.
- * @param[in] array  The array to loop over.
- */
-#define DMNSN_ARRAY_FOREACH(type, i, array)                                    \
-  for (type i = dmnsn_array_first(array);                                      \
-       (size_t)(i - (type)dmnsn_array_first(array)) < dmnsn_array_size(array); \
-       ++i)
diff --git a/libdimension/dimension/base.h b/libdimension/dimension/base.h
new file mode 100644
index 0000000..d9f063f
--- /dev/null
+++ b/libdimension/dimension/base.h
@@ -0,0 +1,46 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Basic functionality: compiler abstractions, error reporting, memory
+ * management, data structures, etc.
+ */
+
+#ifndef DMNSN_BASE_H
+#define DMNSN_BASE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <dimension/base/compiler.h>
+#include <dimension/base/common.h>
+#include <dimension/base/error.h>
+#include <dimension/base/malloc.h>
+#include <dimension/base/pool.h>
+#include <dimension/base/array.h>
+#include <dimension/base/dictionary.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DMNSN_BASE_H */
diff --git a/libdimension/dimension/base/array.h b/libdimension/dimension/base/array.h
new file mode 100644
index 0000000..a54d0e5
--- /dev/null
+++ b/libdimension/dimension/base/array.h
@@ -0,0 +1,361 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Simple dynamic arrays.
+ */
+
+#ifndef DMNSN_BASE_H
+#error "Please include <dimension/base.h> instead of this header directly."
+#endif
+
+#include <stddef.h> /* For size_t */
+#include <stdlib.h> /* For qsort() */
+#include <string.h> /* For memcpy() */
+
+/** Dynamic array type. */
+typedef struct dmnsn_array {
+  void *ptr;       /**< @internal The actual memory. */
+  size_t obj_size; /**< @internal The size of each object. */
+  size_t length;   /**< @internal The current size of the array. */
+  size_t capacity; /**< @internal The size of the allocated space. */
+} dmnsn_array;
+
+/**
+ * @internal
+ * Initialize a new array.
+ * @param[out] array  The array to initialize.
+ * @param[in] obj_size  The size of the objects to store in the array.
+ */
+DMNSN_INLINE void
+dmnsn_init_array(dmnsn_array *array, size_t obj_size)
+{
+  array->obj_size = obj_size;
+  array->length   = 0;
+  array->capacity = 2; /* Start with capacity of 2 */
+
+  /* Allocate the memory */
+  array->ptr = dmnsn_malloc(array->capacity*array->obj_size);
+}
+
+/**
+ * Allocate an array.
+ * @param[in] obj_size  The size of the objects to store in the array.
+ * @return An empty array.
+ */
+DMNSN_INLINE dmnsn_array *
+dmnsn_new_array(size_t obj_size)
+{
+  dmnsn_array *array = DMNSN_MALLOC(dmnsn_array);
+  dmnsn_init_array(array, obj_size);
+  return array;
+}
+
+/**
+ * Allocate an array.
+ * @param[in] type  Type type of element to store in the array.
+ * @return An empty array.
+ */
+#define DMNSN_NEW_ARRAY(type) (dmnsn_new_array(sizeof(type)))
+
+/**
+ * Delete an array.
+ * @param[in,out] array  The array to delete.
+ */
+DMNSN_INLINE void
+dmnsn_delete_array(dmnsn_array *array)
+{
+  if (array) {
+    dmnsn_free(array->ptr);
+    dmnsn_free(array);
+  }
+}
+
+/**
+ * @internal
+ * Free a pool-allocated array.
+ * @param[in,out] ptr  The array to clean up.
+ */
+void dmnsn_array_cleanup(void *ptr);
+
+/**
+ * Allocate an array from a pool.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] obj_size  The size of the objects to store in the array.
+ * @return An empty array.
+ */
+DMNSN_INLINE dmnsn_array *
+dmnsn_palloc_array(dmnsn_pool *pool, size_t obj_size)
+{
+  dmnsn_array *array = DMNSN_PALLOC_TIDY(pool, dmnsn_array, dmnsn_array_cleanup);
+  dmnsn_init_array(array, obj_size);
+  return array;
+}
+
+/**
+ * Allocate an array from a pool.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] type  Type type of element to store in the array.
+ * @return An empty array.
+ */
+#define DMNSN_PALLOC_ARRAY(pool, type) (dmnsn_palloc_array(pool, sizeof(type)))
+
+/**
+ * Get the size of the array.
+ * @param[in] array  The array in question.
+ * @return The number of elements in the array.
+ */
+DMNSN_INLINE size_t
+dmnsn_array_size(const dmnsn_array *array)
+{
+  return array->length;
+}
+
+/**
+ * Set the size of the array.
+ * @param[in,out] array   The array to resize.
+ * @param[in]     length  The new length of the array.
+ */
+DMNSN_INLINE void
+dmnsn_array_resize(dmnsn_array *array, size_t length)
+{
+  if (length > array->capacity) {
+    /* Resize if we don't have enough capacity */
+    array->capacity = length*2; /* We are greedy */
+    array->ptr = dmnsn_realloc(array->ptr, array->obj_size*array->capacity);
+  }
+
+  array->length = length;
+}
+
+/**
+ * Copy an array.
+ * @param[in] array  The array to copy.
+ * @return A copy of the array.
+ */
+DMNSN_INLINE dmnsn_array *
+dmnsn_array_copy(const dmnsn_array *array)
+{
+  dmnsn_array *copy = dmnsn_new_array(array->obj_size);
+  dmnsn_array_resize(copy, dmnsn_array_size(array));
+  memcpy(copy->ptr, array->ptr, dmnsn_array_size(array)*array->obj_size);
+  return copy;
+}
+
+/**
+ * Split an array in half.
+ * @param[in,out] array  The array to split.
+ * @return The second half of the array.
+ */
+DMNSN_INLINE dmnsn_array *
+dmnsn_array_split(dmnsn_array *array)
+{
+  dmnsn_array *half = dmnsn_new_array(array->obj_size);
+  size_t new_size = dmnsn_array_size(array)/2;
+  size_t old_size = dmnsn_array_size(array) - new_size;
+  dmnsn_array_resize(half, new_size);
+  memcpy(half->ptr, (char *)array->ptr + old_size*array->obj_size,
+         new_size*array->obj_size);
+  dmnsn_array_resize(array, old_size);
+  return half;
+}
+
+/**
+ * Get the i'th element.
+ * @param[in]  array  The array to access.
+ * @param[in]  i      The index of the element to extract.
+ * @param[out] obj    The location to store the extracted object.
+ */
+DMNSN_INLINE void
+dmnsn_array_get(const dmnsn_array *array, size_t i, void *obj)
+{
+  dmnsn_assert(i < dmnsn_array_size(array), "Array index out of bounds.");
+  memcpy(obj, (char *)array->ptr + array->obj_size*i, array->obj_size);
+}
+
+/**
+ * Set the i'th object, expanding the array if necessary.
+ * @param[in,out] array  The array to modify.
+ * @param[in]     i      The index to update.
+ * @param[in]     obj    The location of the object to copy into the array.
+ */
+DMNSN_INLINE void
+dmnsn_array_set(dmnsn_array *array, size_t i, const void *obj)
+{
+  if (i >= dmnsn_array_size(array)) {
+    /* Resize if i is out of range */
+    dmnsn_array_resize(array, i + 1);
+  }
+  memcpy((char *)array->ptr + array->obj_size*i, obj, array->obj_size);
+}
+
+/**
+ * First element.
+ * @param[in] array  The array to index.
+ * @return The address of the first element of the array.
+ */
+DMNSN_INLINE void *
+dmnsn_array_first(const dmnsn_array *array)
+{
+  return array->ptr;
+}
+
+/**
+ * Last element.
+ * @param[in] array  The array to index.
+ * @return The address of the last element of the array.
+ */
+DMNSN_INLINE void *
+dmnsn_array_last(const dmnsn_array *array)
+{
+  return (char *)array->ptr + array->obj_size*(array->length - 1);
+}
+
+/**
+ * Arbitrary element access.
+ * @param[in] array  The array to index.
+ * @param[in] i      The index to access.
+ * @return The address of the i'th element of the array.
+ */
+DMNSN_INLINE void *
+dmnsn_array_at(const dmnsn_array *array, size_t i)
+{
+  dmnsn_assert(i < dmnsn_array_size(array), "Array index out of bounds.");
+  return (char *)array->ptr + array->obj_size*i;
+}
+
+/**
+ * Push an object to the end of the array.
+ * @param[in,out] array  The array to append to.
+ * @param[in]     obj    The location of the object to push.
+ */
+DMNSN_INLINE void
+dmnsn_array_push(dmnsn_array *array, const void *obj)
+{
+  dmnsn_array_set(array, dmnsn_array_size(array), obj);
+}
+
+/**
+ * Pop an object from the end of the array.
+ * @param[in,out] array  The array to pop from.
+ * @param[out]    obj    The location to store the extracted object.
+ */
+DMNSN_INLINE void
+dmnsn_array_pop(dmnsn_array *array, void *obj)
+{
+  size_t size = dmnsn_array_size(array);
+  dmnsn_assert(size > 0, "Array is empty.");
+  dmnsn_array_get(array, size - 1, obj); /* Copy the object */
+  dmnsn_array_resize(array, size - 1);   /* Shrink the array */
+}
+
+/**
+ * Insert an item into the middle of the array.  This is O(n).
+ * @param[in,out] array  The array to insert to.
+ * @param[in]     i      The index at which to insert.
+ * @param[in]     obj    The object to insert.
+ */
+DMNSN_INLINE void
+dmnsn_array_insert(dmnsn_array *array, size_t i, const void *obj)
+{
+  size_t size = dmnsn_array_size(array) + 1;
+  if (i >= size)
+    size = i + 1;
+  dmnsn_array_resize(array, size);
+
+  /* Move the elements at and after `i' 1 to the right */
+  memmove((char *)array->ptr + array->obj_size*(i + 1),
+          (char *)array->ptr + array->obj_size*i,
+          array->obj_size*(size - i - 1));
+  /* Insert `obj' at `i' */
+  memcpy((char *)array->ptr + array->obj_size*i, obj, array->obj_size);
+}
+
+/**
+ * Remove an item from the middle of the array.  This is O(n).
+ * @param[in,out] array  The array to remove from.
+ * @param[in]     i      The index to remove.
+ */
+DMNSN_INLINE void
+dmnsn_array_remove(dmnsn_array *array, size_t i)
+{
+  size_t size = dmnsn_array_size(array);
+  dmnsn_assert(i < size, "Array index out of bounds.");
+  /* Move the array elements after `i' 1 to the left */
+  memmove((char *)array->ptr + array->obj_size*i,
+          (char *)array->ptr + array->obj_size*(i + 1),
+          array->obj_size*(size - i - 1));
+  /* Decrease the size by 1 */
+  dmnsn_array_resize(array, size - 1);
+}
+
+/**
+ * Apply a callback to each element of an array.
+ * @param[in,out] array     The array.
+ * @param[in]     callback  The callback to apply to the elements.
+ */
+DMNSN_INLINE void
+dmnsn_array_apply(dmnsn_array *array, dmnsn_callback_fn *callback)
+{
+  char *i, *last = (char *)dmnsn_array_last(array);
+  for (i = (char *)dmnsn_array_first(array); i <= last; i += array->obj_size) {
+    callback((void *)i);
+  }
+}
+
+/**
+ * Callback type for array sorting.
+ * @param[in] a  The first element.
+ * @param[in] b  The second element.
+ * @return A negative value iff a < b, zero iff a == b, and a positive value iff
+ *         a > b.
+ */
+typedef int dmnsn_array_comparator_fn(const void *a, const void *b);
+
+/**
+ * Sort an array.
+ * @param[in,out] array       The array to sort.
+ * @param[in]     comparator  The sorting comparator to use.
+ */
+DMNSN_INLINE void
+dmnsn_array_sort(dmnsn_array *array, dmnsn_array_comparator_fn *comparator)
+{
+  qsort(array->ptr, dmnsn_array_size(array), array->obj_size, comparator);
+}
+
+/* Macros to shorten array iteration */
+
+/**
+ * Iterate over an array.  For example,
+ * @code
+ * DMNSN_ARRAY_FOREACH (int *, i, array) {
+ *   printf("%d\n", *i);
+ * }
+ * @endcode
+ *
+ * @param     type   The (pointer) type to use as an iterator.
+ * @param     i      The name of the iterator within the loop body.
+ * @param[in] array  The array to loop over.
+ */
+#define DMNSN_ARRAY_FOREACH(type, i, array)                                    \
+  for (type i = dmnsn_array_first(array);                                      \
+       (size_t)(i - (type)dmnsn_array_first(array)) < dmnsn_array_size(array); \
+       ++i)
diff --git a/libdimension/dimension/base/common.h b/libdimension/dimension/base/common.h
new file mode 100644
index 0000000..b947b4a
--- /dev/null
+++ b/libdimension/dimension/base/common.h
@@ -0,0 +1,34 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Common types and utilities.
+ */
+
+#ifndef DMNSN_BASE_H
+#error "Please include <dimension/base.h> instead of this header directly."
+#endif
+
+/**
+ * Generic callback type.
+ * @param[in,out] ptr  A pointer to an object to act on.
+ */
+typedef void dmnsn_callback_fn(void *ptr);
diff --git a/libdimension/dimension/base/compiler.h b/libdimension/dimension/base/compiler.h
new file mode 100644
index 0000000..a83f1b9
--- /dev/null
+++ b/libdimension/dimension/base/compiler.h
@@ -0,0 +1,139 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Compiler abstractions.
+ */
+
+#ifndef DMNSN_BASE_H
+#error "Please include <dimension/base.h> instead of this header directly."
+#endif
+
+/**
+ * @internal
+ * @def DMNSN_C_VERSION
+ * The C version according to \p __STDC_VERSION__ if available, otherwise 0.
+ */
+#ifdef __STDC_VERSION__
+  #define DMNSN_C_VERSION __STDC_VERSION__
+#else
+  #define DMNSN_C_VERSION 0L
+#endif
+
+/**
+ * @internal
+ * @def DMNSN_CXX_VERSION
+ * The C++ version according to \p __cplusplus if available, otherwise 0.
+ */
+#ifdef __cplusplus
+  #define DMNSN_CXX_VERSION __cplusplus
+#else
+  #define DMNSN_CXX_VERSION 0L
+#endif
+
+/**
+ * @internal
+ * Whether we're being compiled as C++.
+ */
+#define DMNSN_CXX (DMNSN_CXX_VERSION > 0)
+
+/**
+ * @internal
+ * Whether C++11 features are supported.
+ */
+#define DMNSN_CXX11 (DMNSN_CXX_VERSION >= 201103L)
+
+/**
+ * @internal
+ * Whether C99 features are supported.
+ */
+#define DMNSN_C99 (DMNSN_C_VERSION >= 199901L || DMNSN_CXX11)
+
+/**
+ * @internal
+ * Whether C11 features are supported.
+ */
+#define DMNSN_C11 (DMNSN_C_VERSION >= 201112L)
+
+/**
+ * @internal
+ * Whether GNU C features are supported.
+ */
+#define DMNSN_GNUC defined(__GNUC__)
+
+/**
+ * @def DMNSN_INLINE
+ * A portable inline specifier. Expands to the correct method of declaring
+ * inline functions for the version of C you are using.
+ */
+#ifndef DMNSN_INLINE
+  #if DMNSN_CXX
+    /* C++ inline semantics */
+    #define DMNSN_INLINE inline
+  #elif DMNSN_C99
+    /* C99 inline semantics */
+    #define DMNSN_INLINE inline
+  #elif DMNSN_GNUC
+    /* GCC inline semantics */
+    #define DMNSN_INLINE __extension__ extern __inline__
+  #else
+    /* Unknown C - mark functions static and hope the compiler is smart enough
+       to inline them */
+    #define DMNSN_INLINE static
+  #endif
+#endif
+
+/**
+ * @def DMNSN_NORETURN
+ * A portable noreturn attribute.
+ */
+#if DMNSN_CXX11
+  #define DMNSN_NORETURN [[noreturn]] void
+#elif DMNSN_C11
+  #define DMNSN_NORETURN _Noreturn void
+#elif DMNSN_GNUC
+  #define DMNSN_NORETURN __attribute__((noreturn)) void
+#else
+  #define DMNSN_NORETURN void
+#endif
+
+/**
+ * @internal
+ * @def DMNSN_FUNC
+ * @brief Expands to the name of the current function
+ */
+#if DMNSN_GNUC
+  #define DMNSN_FUNC __PRETTY_FUNCTION__
+#elif DMNSN_C99
+  #define DMNSN_FUNC __func__
+#else
+  #define DMNSN_FUNC "<unknown function>"
+#endif
+
+/**
+ * @internal
+ * An unreachable statement.
+ */
+#if DMNSN_GNUC
+  #define DMNSN_UNREACHABLE() __builtin_unreachable()
+#else
+  #define DMNSN_UNREACHABLE() ((void)0)
+#endif
diff --git a/libdimension/dimension/base/dictionary.h b/libdimension/dimension/base/dictionary.h
new file mode 100644
index 0000000..7360b45
--- /dev/null
+++ b/libdimension/dimension/base/dictionary.h
@@ -0,0 +1,87 @@
+/*************************************************************************
+ * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Simple associative arrays.
+ */
+
+#ifndef DMNSN_BASE_H
+#error "Please include <dimension/base.h> instead of this header directly."
+#endif
+
+/** A string-object associative array. */
+typedef struct dmnsn_dictionary dmnsn_dictionary;
+
+/**
+ * Allocate a dictionary.
+ * @param[in] obj_size  The size of the objects to store in the dictionary.
+ * @return An empty dictionary.
+ */
+dmnsn_dictionary *dmnsn_new_dictionary(size_t obj_size);
+
+/**
+ * Delete a dictionary.
+ * @param[in,out] dict  The dictionary to delete.
+ */
+void dmnsn_delete_dictionary(dmnsn_dictionary *dict);
+
+/**
+ * Find an element in a dictionary.
+ * @param[in]  dict  The dictionary to search.
+ * @param[in]  key   The key to search for.
+ * @param[out] obj   The location to store the found object.
+ * @return Whether the element was found.
+ */
+bool dmnsn_dictionary_get(const dmnsn_dictionary *dict, const char *key,
+                          void *obj);
+
+/**
+ * Access an element in a dictionary.
+ * @param[in]  dict  The dictionary to search.
+ * @param[in]  key   The key to search for.
+ * @return A pointer to the element if found, otherwise NULL.
+ */
+void *dmnsn_dictionary_at(const dmnsn_dictionary *dict, const char *key);
+
+/**
+ * Insert a (key, value) pair into a dictionary.
+ * @param[in,out] dict  The dictionary to modify.
+ * @param[in]     key   The key to insert.
+ * @param[in]     obj   The object to insert.
+ */
+void dmnsn_dictionary_insert(dmnsn_dictionary *dict, const char *key,
+                             const void *obj);
+
+/**
+ * Remove a (key, value) pair from a dictionary.
+ * @param[in,out] dict  The dictionary to modify.
+ * @param[in]     key   The key to remove.
+ * @return Whether the key existed in the dictionary.
+ */
+bool dmnsn_dictionary_remove(dmnsn_dictionary *dict, const char *key);
+
+/**
+ * Apply a callback to all elements in a dictionary.
+ * @param[in,out] dict      The dictionary.
+ * @param[in]     callback  The callback to apply to the elements.
+ */
+void dmnsn_dictionary_apply(dmnsn_dictionary *dict,
+                            dmnsn_callback_fn *callback);
diff --git a/libdimension/dimension/base/error.h b/libdimension/dimension/base/error.h
new file mode 100644
index 0000000..d039081
--- /dev/null
+++ b/libdimension/dimension/base/error.h
@@ -0,0 +1,118 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Error reporting.
+ */
+
+#ifndef DMNSN_BASE_H
+#error "Please include <dimension/base.h> instead of this header directly."
+#endif
+
+#include <stdbool.h>
+
+/**
+ * Report a warning.
+ * @param[in] str  A string to print explaining the warning.
+ */
+#define dmnsn_warning(str)                                      \
+  dmnsn_report_warning(DMNSN_FUNC, __FILE__, __LINE__, str)
+
+/**
+ * Report an error.
+ * @param[in] str  A string to print explaining the error.
+ */
+#define dmnsn_error(str)                                        \
+  dmnsn_report_error(DMNSN_FUNC, __FILE__, __LINE__, str)
+
+/**
+ * @def dmnsn_assert
+ * Make an assertion.
+ * @param[in] expr  The expression to assert.
+ * @param[in] str   A string to print if the assertion fails.
+ */
+#if DMNSN_DEBUG
+  #define dmnsn_assert(expr, str)                 \
+    do {                                          \
+      if (!(expr)) {                              \
+        dmnsn_error((str));                       \
+      }                                           \
+    } while (0)
+#else
+  #define dmnsn_assert(expr, str) ((void)0)
+#endif
+
+/**
+ * @def dmnsn_unreachable
+ * Express that a line of code is unreachable.
+ * @param[in] str  A string to print if the line is reached.
+ */
+#if DMNSN_DEBUG
+  #define dmnsn_unreachable(str) dmnsn_error((str))
+#else
+  #define dmnsn_unreachable(str) DMNSN_UNREACHABLE()
+#endif
+
+/**
+ * @internal
+ * Called by dmnsn_warning(); don't call directly.
+ * @param[in] func  The name of the function where the error originated.
+ * @param[in] file  The file where the error originated.
+ * @param[in] line  The line number where the error originated.
+ * @param[in] str   A string describing the error.
+ */
+void dmnsn_report_warning(const char *func, const char *file, unsigned int line, const char *str);
+
+/**
+ * @internal
+ * Called by dmnsn_error(); don't call directly.
+ * @param[in] func  The name of the function where the error originated.
+ * @param[in] file  The file where the error originated.
+ * @param[in] line  The line number where the error originated.
+ * @param[in] str   A string describing the error.
+ */
+DMNSN_NORETURN dmnsn_report_error(const char *func, const char *file, unsigned int line, const char *str);
+
+/**
+ * Treat warnings as errors.
+ * @param[in] always_die  Whether to die on warnings.
+ */
+void dmnsn_die_on_warnings(bool always_die);
+
+/**
+ * Fatal error callback type.  This function should never return.
+ */
+typedef void dmnsn_fatal_error_fn(void);
+
+/**
+ * Get the libdimension fatal error handler, thread-safely.  The default fatal
+ * error handler terminates the current thread, or the entire program if the
+ * current thread is the main thread.
+ * @return The current fatal error handler.
+ */
+dmnsn_fatal_error_fn *dmnsn_get_fatal_error_fn(void);
+
+/**
+ * Set the libdimension fatal error handler, thread-safely.
+ * @param[in] fatal  The new fatal error handler.  This function must never
+ *                   return.
+ */
+void dmnsn_set_fatal_error_fn(dmnsn_fatal_error_fn *fatal);
diff --git a/libdimension/dimension/base/malloc.h b/libdimension/dimension/base/malloc.h
new file mode 100644
index 0000000..fcc492e
--- /dev/null
+++ b/libdimension/dimension/base/malloc.h
@@ -0,0 +1,70 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Dynamic memory.  dmnsn_malloc() and friends behave like their
+ * non-dmnsn_-prefixed counterparts, but never return NULL.  If allocation
+ * fails, they instead call dmnsn_error().
+ */
+
+#ifndef DMNSN_BASE_H
+#error "Please include <dimension/base.h> instead of this header directly."
+#endif
+
+#include <stddef.h> /* For size_t */
+
+/**
+ * Allocate some memory.  Always use dmnsn_free() to free this memory, never
+ * free().
+ * @param[in] size  The size of the memory block to allocate.
+ * @return The allocated memory area.
+ */
+void *dmnsn_malloc(size_t size);
+
+/**
+ * Allocate some memory.  Always use dmnsn_free() to free this memory, never
+ * free().
+ * @param[in] type  The type of the memory block to allocate.
+ * @return The allocated memory area.
+ */
+#define DMNSN_MALLOC(type) ((type *)dmnsn_malloc(sizeof(type)))
+
+/**
+ * Expand or shrink an allocation created by dmnsn_malloc().
+ * @param[in] ptr   The block to resize.
+ * @param[in] size  The new size.
+ * @return The resized memory area.
+ */
+void *dmnsn_realloc(void *ptr, size_t size);
+
+/**
+ * Duplicate a string.
+ * @param[in] s  The string to duplicate.
+ * @return A string with the same contents as \p s, suitable for release by
+ *         dmnsn_free().
+ */
+char *dmnsn_strdup(const char *s);
+
+/**
+ * Free memory allocated by dmnsn_malloc() or dmnsn_strdup().
+ * @param[in] ptr  The memory block to free, or NULL.
+ */
+void dmnsn_free(void *ptr);
diff --git a/libdimension/dimension/base/pool.h b/libdimension/dimension/base/pool.h
new file mode 100644
index 0000000..9983f1d
--- /dev/null
+++ b/libdimension/dimension/base/pool.h
@@ -0,0 +1,81 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Memory pools.  Rather than more complicated garbage collection methods like
+ * reference counting, objects are allocated out of pools which are freed all at
+ * once once a scene is rendered (for example).
+ */
+
+#ifndef DMNSN_BASE_H
+#error "Please include <dimension/base.h> instead of this header directly."
+#endif
+
+#include <stddef.h> /* For size_t */
+
+/* Forward-declare dmnsn_pool. */
+typedef struct dmnsn_pool dmnsn_pool;
+
+/**
+ * Create a new memory pool.
+ * @return The new pool.
+ */
+dmnsn_pool *dmnsn_new_pool(void);
+
+/**
+ * Allocate some memory from a pool.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] size  The size of the memory block to allocate.
+ * @return The allocated memory area.
+ */
+void *dmnsn_palloc(dmnsn_pool *pool, size_t size);
+
+/**
+ * Allocate some memory from a pool.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] size  The size of the memory block to allocate.
+ * @param[in] cleanup_fn  A callback to invoke before the memory is freed.
+ * @return The allocated memory area.
+ */
+void *dmnsn_palloc_tidy(dmnsn_pool *pool, size_t size, dmnsn_callback_fn *cleanup_fn);
+
+/**
+ * Allocate some memory from a pool.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] type  The type of the memory block to allocate.
+ * @return The allocated memory area.
+ */
+#define DMNSN_PALLOC(pool, type) ((type *)dmnsn_palloc((pool), sizeof(type)))
+
+/**
+ * Allocate some memory from a pool.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] type  The type of the memory block to allocate.
+ * @param[in] cleanup_fn  A callback to invoke before the memory is freed.
+ * @return The allocated memory area.
+ */
+#define DMNSN_PALLOC_TIDY(pool, type, cleanup_fn) ((type *)dmnsn_palloc_tidy((pool), sizeof(type), (cleanup_fn)))
+
+/**
+ * Free a memory pool and all associated allocations.
+ * @param[in] pool  The memory pool to free.
+ */
+void dmnsn_delete_pool(dmnsn_pool *pool);
diff --git a/libdimension/dimension/camera.h b/libdimension/dimension/camera.h
deleted file mode 100644
index 3c6494c..0000000
--- a/libdimension/dimension/camera.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Cameras.
- */
-
-/* Forward-declare dmnsn_camera */
-typedef struct dmnsn_camera dmnsn_camera;
-
-/**
- * Camera ray callback.
- * @param[in] camera  The camera itself.
- * @param[in] x       The x coordinate of the pixel (in [0, 1]).
- * @param[in] y       The y coordinate of the pixel (in [0, 1]).
- * @return The ray through (\p x, \p y).
- */
-typedef dmnsn_line dmnsn_camera_ray_fn(const dmnsn_camera *camera,
-                                       double x, double y);
-
-/** A camera. */
-struct dmnsn_camera {
-  /* Callback functions */
-  dmnsn_camera_ray_fn *ray_fn; /**< Camera ray callback. */
-
-  dmnsn_matrix trans; /**< Transformation matrix. */
-};
-
-/**
- * Create a dummy camera.
- * @param[in] pool  The memory pool to allocate from.
- * @return The allocated camera.
- */
-dmnsn_camera *dmnsn_new_camera(dmnsn_pool *pool);
-
-/**
- * Initialize a dmnsn_camera field.
- * @param[out] camera  The camera to initialize.
- */
-void dmnsn_init_camera(dmnsn_camera *camera);
-
-/**
- * Invoke the camera ray callback, then correctly transform the ray.
- * @param[in] camera  The camera itself.
- * @param[in] x       The x coordinate of the pixel (in [0, 1]).
- * @param[in] y       The y coordinate of the pixel (in [0, 1]).
- * @return The ray through (\p x, \p y).
- */
-dmnsn_line dmnsn_camera_ray(const dmnsn_camera *camera, double x, double y);
diff --git a/libdimension/dimension/cameras.h b/libdimension/dimension/cameras.h
deleted file mode 100644
index 9ef2646..0000000
--- a/libdimension/dimension/cameras.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pre-defined camera types.
- */
-
-/**
- * A perspective camera.  The camera is located at the origin, looking at
- * (0, 0, 1).  The feild of view is the section of the plane z = 1 from
- * (-0.5, -0.5) to (0.5, 0.5).  Rays are transformed by the camera's
- * transformation matrix.
- * @param[in] pool  The memory pool to allocate from.
- * @return A perspective camera.
- */
-dmnsn_camera *dmnsn_new_perspective_camera(dmnsn_pool *pool);
diff --git a/libdimension/dimension/canvas.h b/libdimension/dimension/canvas.h
index e325364..53bbda3 100644
--- a/libdimension/dimension/canvas.h
+++ b/libdimension/dimension/canvas.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
  *                                                                       *
  * This file is part of The Dimension Library.                           *
  *                                                                       *
@@ -20,108 +20,26 @@
 
 /**
  * @file
- * A canvas which is rendered to.
+ * Using, importing, and exporting canvases.
  */
 
-#include <stddef.h>
+#ifndef DMNSN_CANVAS_H
+#define DMNSN_CANVAS_H
 
-/** A canvas, or image. */
-typedef struct dmnsn_canvas {
-  size_t width;  /**< Canvas width. */
-  size_t height; /**< Canvas height. */
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-  /** An array of <tt>dmnsn_canvas_optimizer</tt>s. */
-  dmnsn_array *optimizers;
+#include <dimension/base.h>
+#include <dimension/concurrency.h>
+#include <dimension/color.h>
 
-  /**
-   * @internal
-   * Stored in first-quadrant representation (origin is bottom-left).  The pixel
-   * at (a,b) is accessible as pixels[b*width + a].
-   */
-  dmnsn_tcolor *pixels;
-} dmnsn_canvas;
+#include <dimension/canvas/canvas.h>
+#include <dimension/canvas/png.h>
+#include <dimension/canvas/gl.h>
 
-/* Forward-declare dmnsn_canvas_optimizer. */
-typedef struct dmnsn_canvas_optimizer dmnsn_canvas_optimizer;
-
-/**
- * Canvas optimizer callback type.
- * @param[in] optimizer  The canvas optimizer.
- * @param[in] canvas  The canvas that was just updated.
- * @param[in] x  The x-coordinate that was just updated.
- * @param[in] y  The y-coordinate that was just updated.
- */
-typedef void dmnsn_canvas_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y);
-
-/** Canvas optimizer. */
-struct dmnsn_canvas_optimizer {
-  dmnsn_canvas_optimizer_fn *optimizer_fn; /**< Optimizer callback. */
-};
-
-/**
- * Allocate a new canvas.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] width  The width of the canvas to allocate (in pixels).
- * @param[in] height  The height of the canvas to allocate (in pixels).
- * @return The allocated canvas.
- */
-dmnsn_canvas *dmnsn_new_canvas(dmnsn_pool *pool, size_t width, size_t height);
-
-/**
- * Initialize a dmnsn_canvas_optimizer field
- * @param[in] optimizer  The optimizer to initialize.
- */
-void dmnsn_init_canvas_optimizer(dmnsn_canvas_optimizer *optimizer);
-
-/**
- * Set a canvas optimizer
- * @param[in,out] canvas    The canvas to optimize.
- * @param[in]     optimizer The optimizer to use.
- */
-void dmnsn_canvas_optimize(dmnsn_canvas *canvas,
-                           const dmnsn_canvas_optimizer *optimizer);
-
-/**
- * Find a canvas optimizer by its callback.
- * @param[in] canvas        The canvas to check.
- * @param[in] optimizer_fn  The callback to search for for.
- * @return A pointer to the canvas optimizer with the callback \p optimizer_fn,
- *         or NULL if none is found.
- */
-dmnsn_canvas_optimizer *
-dmnsn_canvas_find_optimizer(const dmnsn_canvas *canvas,
-                            dmnsn_canvas_optimizer_fn *optimizer_fn);
-
-/* Pixel accessors */
-
-/**
- * Get the color of a pixel.
- * @param[in] canvas  The canvas to access.
- * @param[in] x       The x coordinate.
- * @param[in] y       The y coordinate.
- * @return The color of the pixel at (\p x, \p y).
- */
-DMNSN_INLINE dmnsn_tcolor
-dmnsn_canvas_get_pixel(const dmnsn_canvas *canvas, size_t x, size_t y)
-{
-  dmnsn_assert(x < canvas->width && y < canvas->height,
-               "Canvas access out of bounds.");
-  return canvas->pixels[y*canvas->width + x];
+#ifdef __cplusplus
 }
+#endif
 
-/**
- * Set the value of a pixel.
- * @param[in,out] canvas  The canvas to modify.
- * @param[in]     x       The x coordinate of the pixel.
- * @param[in]     y       The y coordinate of the pixel.
- * @param[in]     tcolor  The value to set the pixel at (\p x, \p y) to.
- */
-void dmnsn_canvas_set_pixel(dmnsn_canvas *canvas, size_t x, size_t y,
-                            dmnsn_tcolor tcolor);
-
-/**
- * Clear a canvas uniformly with a given color.
- * @param[in,out] canvas  The canvas to erase.
- * @param[in]     tcolor  The color to paint it with.
- */
-void dmnsn_canvas_clear(dmnsn_canvas *canvas, dmnsn_tcolor tcolor);
+#endif /* DMNSN_CANVAS_H */
diff --git a/libdimension/dimension/canvas/canvas.h b/libdimension/dimension/canvas/canvas.h
new file mode 100644
index 0000000..07d4fde
--- /dev/null
+++ b/libdimension/dimension/canvas/canvas.h
@@ -0,0 +1,131 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * A canvas which is rendered to.
+ */
+
+#ifndef DMNSN_CANVAS_H
+#error "Please include <dimension/canvas.h> instead of this header directly."
+#endif
+
+#include <stddef.h>
+
+/** A canvas, or image. */
+typedef struct dmnsn_canvas {
+  size_t width;  /**< Canvas width. */
+  size_t height; /**< Canvas height. */
+
+  /** An array of <tt>dmnsn_canvas_optimizer</tt>s. */
+  dmnsn_array *optimizers;
+
+  /**
+   * @internal
+   * Stored in first-quadrant representation (origin is bottom-left).  The pixel
+   * at (a,b) is accessible as pixels[b*width + a].
+   */
+  dmnsn_tcolor *pixels;
+} dmnsn_canvas;
+
+/* Forward-declare dmnsn_canvas_optimizer. */
+typedef struct dmnsn_canvas_optimizer dmnsn_canvas_optimizer;
+
+/**
+ * Canvas optimizer callback type.
+ * @param[in] optimizer  The canvas optimizer.
+ * @param[in] canvas  The canvas that was just updated.
+ * @param[in] x  The x-coordinate that was just updated.
+ * @param[in] y  The y-coordinate that was just updated.
+ */
+typedef void dmnsn_canvas_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y);
+
+/** Canvas optimizer. */
+struct dmnsn_canvas_optimizer {
+  dmnsn_canvas_optimizer_fn *optimizer_fn; /**< Optimizer callback. */
+};
+
+/**
+ * Allocate a new canvas.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] width  The width of the canvas to allocate (in pixels).
+ * @param[in] height  The height of the canvas to allocate (in pixels).
+ * @return The allocated canvas.
+ */
+dmnsn_canvas *dmnsn_new_canvas(dmnsn_pool *pool, size_t width, size_t height);
+
+/**
+ * Initialize a dmnsn_canvas_optimizer field
+ * @param[in] optimizer  The optimizer to initialize.
+ */
+void dmnsn_init_canvas_optimizer(dmnsn_canvas_optimizer *optimizer);
+
+/**
+ * Set a canvas optimizer
+ * @param[in,out] canvas    The canvas to optimize.
+ * @param[in]     optimizer The optimizer to use.
+ */
+void dmnsn_canvas_optimize(dmnsn_canvas *canvas,
+                           const dmnsn_canvas_optimizer *optimizer);
+
+/**
+ * Find a canvas optimizer by its callback.
+ * @param[in] canvas        The canvas to check.
+ * @param[in] optimizer_fn  The callback to search for for.
+ * @return A pointer to the canvas optimizer with the callback \p optimizer_fn,
+ *         or NULL if none is found.
+ */
+dmnsn_canvas_optimizer *
+dmnsn_canvas_find_optimizer(const dmnsn_canvas *canvas,
+                            dmnsn_canvas_optimizer_fn *optimizer_fn);
+
+/* Pixel accessors */
+
+/**
+ * Get the color of a pixel.
+ * @param[in] canvas  The canvas to access.
+ * @param[in] x       The x coordinate.
+ * @param[in] y       The y coordinate.
+ * @return The color of the pixel at (\p x, \p y).
+ */
+DMNSN_INLINE dmnsn_tcolor
+dmnsn_canvas_get_pixel(const dmnsn_canvas *canvas, size_t x, size_t y)
+{
+  dmnsn_assert(x < canvas->width && y < canvas->height,
+               "Canvas access out of bounds.");
+  return canvas->pixels[y*canvas->width + x];
+}
+
+/**
+ * Set the value of a pixel.
+ * @param[in,out] canvas  The canvas to modify.
+ * @param[in]     x       The x coordinate of the pixel.
+ * @param[in]     y       The y coordinate of the pixel.
+ * @param[in]     tcolor  The value to set the pixel at (\p x, \p y) to.
+ */
+void dmnsn_canvas_set_pixel(dmnsn_canvas *canvas, size_t x, size_t y,
+                            dmnsn_tcolor tcolor);
+
+/**
+ * Clear a canvas uniformly with a given color.
+ * @param[in,out] canvas  The canvas to erase.
+ * @param[in]     tcolor  The color to paint it with.
+ */
+void dmnsn_canvas_clear(dmnsn_canvas *canvas, dmnsn_tcolor tcolor);
diff --git a/libdimension/dimension/canvas/gl.h b/libdimension/dimension/canvas/gl.h
new file mode 100644
index 0000000..c2d77a1
--- /dev/null
+++ b/libdimension/dimension/canvas/gl.h
@@ -0,0 +1,52 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * OpenGL export/import of canvases.
+ */
+
+#ifndef DMNSN_CANVAS_H
+#error "Please include <dimension/canvas.h> instead of this header directly."
+#endif
+
+/**
+ * Optimize a canvas for GL drawing
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] canvas  The canvas to optimize.
+ * @return Whether the canvas was successfully optimized.
+ */
+int dmnsn_gl_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
+
+/**
+ * Write canvas to GL framebuffer.
+ * @param[in] canvas  The canvas to draw.
+ * @return 0 on success, non-zero on failure.
+ */
+int dmnsn_gl_write_canvas(const dmnsn_canvas *canvas);
+
+/**
+ * Read a canvas from a GL framebuffer.
+ * @param[in] canvas  The canvas to write to.
+ * @param[in] x0  The \a x screen coordinate to start copying from.
+ * @param[in] y0  The \a y screen coordinate to start copying from.
+ * @return 0 on success, non-zero on failure.
+ */
+int dmnsn_gl_read_canvas(dmnsn_canvas *canvas, size_t x0, size_t y0);
diff --git a/libdimension/dimension/canvas/png.h b/libdimension/dimension/canvas/png.h
new file mode 100644
index 0000000..57a68c2
--- /dev/null
+++ b/libdimension/dimension/canvas/png.h
@@ -0,0 +1,75 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * PNG import/export of canvases.
+ */
+
+#ifndef DMNSN_CANVAS_H
+#error "Please include <dimension/canvas.h> instead of this header directly."
+#endif
+
+#include <stdio.h>
+
+/**
+ * Optimize a canvas for PNG exporting
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] canvas  The canvas to optimize.
+ * @return Whether the canvas was successfully optimized.
+ */
+int dmnsn_png_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
+
+/**
+ * Write a canvas to a file in PNG format.
+ * @param[in] canvas  The canvas to write.
+ * @param[in,out] file  The file to write to.
+ * @return 0 on success, non-zero on failure.
+ */
+int dmnsn_png_write_canvas(const dmnsn_canvas *canvas, FILE *file);
+
+/**
+ * Write a canvas to a PNG file in the background.
+ * @param[in] canvas  The canvas to write.
+ * @param[in,out] file  The file to write to.
+ * @return A \ref dmnsn_future object, or NULL on failure.
+ */
+dmnsn_future *dmnsn_png_write_canvas_async(const dmnsn_canvas *canvas,
+                                           FILE *file);
+
+/**
+ * Read a canvas from a PNG file.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] file  The PNG file to read.
+ * @return The new canvas, or NULL on failure.
+ */
+dmnsn_canvas *dmnsn_png_read_canvas(dmnsn_pool *pool, FILE *file);
+
+/**
+ * Read a canvas from a PNG file in the background.
+ * @param[out] canvas  The address of a non-allocated canvas object.  The canvas
+ *                     object will be allocated and filled with the contents of
+ *                     \p file.  Do not read from this object until the
+ *                     background task has finished.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] file  The PNG file to read.
+ * @return A \ref dmnsn_future object, or NULL on failure.
+ */
+dmnsn_future *dmnsn_png_read_canvas_async(dmnsn_canvas **canvas, dmnsn_pool *pool, FILE *file);
diff --git a/libdimension/dimension/color.h b/libdimension/dimension/color.h
index adbd396..74bed59 100644
--- a/libdimension/dimension/color.h
+++ b/libdimension/dimension/color.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
  *                                                                       *
  * This file is part of The Dimension Library.                           *
  *                                                                       *
@@ -20,174 +20,23 @@
 
 /**
  * @file
- * Colors.
+ * Color handling.
  */
 
-#include <stdbool.h>
+#ifndef DMNSN_COLOR_H
+#define DMNSN_COLOR_H
 
-/** A color value. */
-typedef struct {
-  double R; /**< Red component. */
-  double G; /**< Green component. */
-  double B; /**< Blue component. */
-} dmnsn_color;
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-/** A standard format string for colors. */
-#define DMNSN_COLOR_FORMAT "Color<%g, %g, %g>"
-/** The appropriate arguements to printf() a color. */
-#define DMNSN_COLOR_PRINTF(c) (c).R, (c).G, (c).B
+#include <dimension/math.h>
 
-/** Construct a new color. */
-DMNSN_INLINE dmnsn_color
-dmnsn_new_color(double R, double G, double B)
-{
-  dmnsn_color ret = { R, G, B };
-  return ret;
-}
-
-/** Apply sRGB gamma */
-DMNSN_INLINE double
-dmnsn_sRGB_gamma(double Clinear)
-{
-  /*
-   * If C represents R, G, and B, then the sRGB values are now found as follows:
-   *
-   *         { 12.92*Clinear,                Clinear <= 0.0031308
-   * Csrgb = {                1/2.4
-   *         { (1.055)*Clinear      - 0.055, Clinear >  0.0031308
-   */
-  if (Clinear == 1.0) {
-    return 1.0; /* Map 1.0 to 1.0 instead of 0.9999999999999999 */
-  } else if (Clinear > 0.0031308) {
-    return 1.055*pow(Clinear, 1.0/2.4) - 0.055;
-  } else {
-    return 12.92*Clinear;
-  }
-}
-
-/** Convert to sRGB space. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_to_sRGB(dmnsn_color color)
-{
-  return dmnsn_new_color(
-    dmnsn_sRGB_gamma(color.R),
-    dmnsn_sRGB_gamma(color.G),
-    dmnsn_sRGB_gamma(color.B)
-  );
-}
-
-/** Remove sRGB gamma */
-DMNSN_INLINE double
-dmnsn_sRGB_inverse_gamma(double CsRGB)
-{
-  /*
-   * If C represents R, G, and B, then the Clinear values are now found as
-   * follows:
-   *
-   *           { Csrgb/12.92,                Csrgb <= 0.04045
-   * Clinear = {                        2.4
-   *           { ((Csrgb + 0.055)/1.055)   , Csrgb >  0.04045
-   */
-  if (CsRGB == 1.0) {
-    return 1.0; /* Map 1.0 to 1.0 instead of 0.9999999999999999 */
-  } else if (CsRGB <= 0.040449936) {
-    return CsRGB/12.92;
-  } else {
-    return pow((CsRGB + 0.055)/1.055, 2.4);
-  }
-}
-
-/** Convert from sRGB space. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_from_sRGB(dmnsn_color color)
-{
-  return dmnsn_new_color(
-    dmnsn_sRGB_inverse_gamma(color.R),
-    dmnsn_sRGB_inverse_gamma(color.G),
-    dmnsn_sRGB_inverse_gamma(color.B)
-  );
-}
-
-/** Greyscale color intensity. */
-DMNSN_INLINE double
-dmnsn_color_intensity(dmnsn_color color)
-{
-  return 0.2126*color.R + 0.7152*color.G + 0.0722*color.B;
-}
-
-/** Add two colors together. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_add(dmnsn_color lhs, dmnsn_color rhs)
-{
-  return dmnsn_new_color(lhs.R + rhs.R, lhs.G + rhs.G, lhs.B + rhs.B);
-}
-
-/** Subtract two colors. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_sub(dmnsn_color lhs, dmnsn_color rhs)
-{
-  return dmnsn_new_color(lhs.R - rhs.R, lhs.G - rhs.G, lhs.B - rhs.B);
-}
-
-/** Scale a color's intensity. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_mul(double n, dmnsn_color color)
-{
-  return dmnsn_new_color(n*color.R, n*color.G, n*color.B);
-}
-
-/** Return the color at \p n on a gradient from \p c1 at 0 to \p c2 at 1. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_gradient(dmnsn_color c1, dmnsn_color c2, double n)
-{
-  return dmnsn_new_color(
-    n*(c2.R - c1.R) + c1.R,
-    n*(c2.G - c1.G) + c1.G,
-    n*(c2.B - c1.B) + c1.B
-  );
-}
-
-/** Illuminate \p color with \p light. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_illuminate(dmnsn_color light, dmnsn_color color)
-{
-  return dmnsn_new_color(light.R*color.R, light.G*color.G, light.B*color.B);
-}
+#include <dimension/color/color.h>
+#include <dimension/color/tcolor.h>
 
-/** Saturate the color components to [0.0, 1.0]. */
-DMNSN_INLINE dmnsn_color
-dmnsn_color_clamp(dmnsn_color color)
-{
-  color.R = dmnsn_clamp(color.R, 0.0, 1.0);
-  color.G = dmnsn_clamp(color.G, 0.0, 1.0);
-  color.B = dmnsn_clamp(color.B, 0.0, 1.0);
-  return color;
+#ifdef __cplusplus
 }
+#endif
 
-/** Return whether a color contains any NaN components. */
-DMNSN_INLINE bool
-dmnsn_color_isnan(dmnsn_color color)
-{
-  return dmnsn_isnan(color.R) || dmnsn_isnan(color.G) || dmnsn_isnan(color.B);
-}
-
-/* Standard colors */
-
-/** Black. */
-#define dmnsn_black   dmnsn_new_color(0.0, 0.0, 0.0)
-/** White. */
-#define dmnsn_white   dmnsn_new_color(1.0, 1.0, 1.0)
-/** Red. */
-#define dmnsn_red     dmnsn_new_color(1.0, 0.0, 0.0)
-/** Green. */
-#define dmnsn_green   dmnsn_new_color(0.0, 1.0, 0.0)
-/** Blue. */
-#define dmnsn_blue    dmnsn_new_color(0.0, 0.0, 1.0)
-/** Magenta. */
-#define dmnsn_magenta dmnsn_new_color(1.0, 0.0, 1.0)
-/** Orange. */
-#define dmnsn_orange  dmnsn_new_color(1.0, 0.21404114048223255, 0.0)
-/** Yellow. */
-#define dmnsn_yellow  dmnsn_new_color(1.0, 1.0, 0.0)
-/** Cyan. */
-#define dmnsn_cyan    dmnsn_new_color(0.0, 1.0, 1.0)
+#endif /* DMNSN_COLOR_H */
diff --git a/libdimension/dimension/color/color.h b/libdimension/dimension/color/color.h
new file mode 100644
index 0000000..84e66ea
--- /dev/null
+++ b/libdimension/dimension/color/color.h
@@ -0,0 +1,198 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Colors.
+ */
+
+#ifndef DMNSN_COLOR_H
+#error "Please include <dimension/color.h> instead of this header directly."
+#endif
+
+#include <math.h>
+#include <stdbool.h>
+
+/** A color value. */
+typedef struct {
+  double R; /**< Red component. */
+  double G; /**< Green component. */
+  double B; /**< Blue component. */
+} dmnsn_color;
+
+/** A standard format string for colors. */
+#define DMNSN_COLOR_FORMAT "Color<%g, %g, %g>"
+/** The appropriate arguements to printf() a color. */
+#define DMNSN_COLOR_PRINTF(c) (c).R, (c).G, (c).B
+
+/** Construct a new color. */
+DMNSN_INLINE dmnsn_color
+dmnsn_new_color(double R, double G, double B)
+{
+  dmnsn_color ret = { R, G, B };
+  return ret;
+}
+
+/** Apply sRGB gamma */
+DMNSN_INLINE double
+dmnsn_sRGB_gamma(double Clinear)
+{
+  /*
+   * If C represents R, G, and B, then the sRGB values are now found as follows:
+   *
+   *         { 12.92*Clinear,                Clinear <= 0.0031308
+   * Csrgb = {                1/2.4
+   *         { (1.055)*Clinear      - 0.055, Clinear >  0.0031308
+   */
+  if (Clinear == 1.0) {
+    return 1.0; /* Map 1.0 to 1.0 instead of 0.9999999999999999 */
+  } else if (Clinear > 0.0031308) {
+    return 1.055*pow(Clinear, 1.0/2.4) - 0.055;
+  } else {
+    return 12.92*Clinear;
+  }
+}
+
+/** Convert to sRGB space. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_to_sRGB(dmnsn_color color)
+{
+  return dmnsn_new_color(
+    dmnsn_sRGB_gamma(color.R),
+    dmnsn_sRGB_gamma(color.G),
+    dmnsn_sRGB_gamma(color.B)
+  );
+}
+
+/** Remove sRGB gamma */
+DMNSN_INLINE double
+dmnsn_sRGB_inverse_gamma(double CsRGB)
+{
+  /*
+   * If C represents R, G, and B, then the Clinear values are now found as
+   * follows:
+   *
+   *           { Csrgb/12.92,                Csrgb <= 0.04045
+   * Clinear = {                        2.4
+   *           { ((Csrgb + 0.055)/1.055)   , Csrgb >  0.04045
+   */
+  if (CsRGB == 1.0) {
+    return 1.0; /* Map 1.0 to 1.0 instead of 0.9999999999999999 */
+  } else if (CsRGB <= 0.040449936) {
+    return CsRGB/12.92;
+  } else {
+    return pow((CsRGB + 0.055)/1.055, 2.4);
+  }
+}
+
+/** Convert from sRGB space. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_from_sRGB(dmnsn_color color)
+{
+  return dmnsn_new_color(
+    dmnsn_sRGB_inverse_gamma(color.R),
+    dmnsn_sRGB_inverse_gamma(color.G),
+    dmnsn_sRGB_inverse_gamma(color.B)
+  );
+}
+
+/** Greyscale color intensity. */
+DMNSN_INLINE double
+dmnsn_color_intensity(dmnsn_color color)
+{
+  return 0.2126*color.R + 0.7152*color.G + 0.0722*color.B;
+}
+
+/** Add two colors together. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_add(dmnsn_color lhs, dmnsn_color rhs)
+{
+  return dmnsn_new_color(lhs.R + rhs.R, lhs.G + rhs.G, lhs.B + rhs.B);
+}
+
+/** Subtract two colors. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_sub(dmnsn_color lhs, dmnsn_color rhs)
+{
+  return dmnsn_new_color(lhs.R - rhs.R, lhs.G - rhs.G, lhs.B - rhs.B);
+}
+
+/** Scale a color's intensity. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_mul(double n, dmnsn_color color)
+{
+  return dmnsn_new_color(n*color.R, n*color.G, n*color.B);
+}
+
+/** Return the color at \p n on a gradient from \p c1 at 0 to \p c2 at 1. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_gradient(dmnsn_color c1, dmnsn_color c2, double n)
+{
+  return dmnsn_new_color(
+    n*(c2.R - c1.R) + c1.R,
+    n*(c2.G - c1.G) + c1.G,
+    n*(c2.B - c1.B) + c1.B
+  );
+}
+
+/** Illuminate \p color with \p light. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_illuminate(dmnsn_color light, dmnsn_color color)
+{
+  return dmnsn_new_color(light.R*color.R, light.G*color.G, light.B*color.B);
+}
+
+/** Saturate the color components to [0.0, 1.0]. */
+DMNSN_INLINE dmnsn_color
+dmnsn_color_clamp(dmnsn_color color)
+{
+  color.R = dmnsn_clamp(color.R, 0.0, 1.0);
+  color.G = dmnsn_clamp(color.G, 0.0, 1.0);
+  color.B = dmnsn_clamp(color.B, 0.0, 1.0);
+  return color;
+}
+
+/** Return whether a color contains any NaN components. */
+DMNSN_INLINE bool
+dmnsn_color_isnan(dmnsn_color color)
+{
+  return dmnsn_isnan(color.R) || dmnsn_isnan(color.G) || dmnsn_isnan(color.B);
+}
+
+/* Standard colors */
+
+/** Black. */
+#define dmnsn_black   dmnsn_new_color(0.0, 0.0, 0.0)
+/** White. */
+#define dmnsn_white   dmnsn_new_color(1.0, 1.0, 1.0)
+/** Red. */
+#define dmnsn_red     dmnsn_new_color(1.0, 0.0, 0.0)
+/** Green. */
+#define dmnsn_green   dmnsn_new_color(0.0, 1.0, 0.0)
+/** Blue. */
+#define dmnsn_blue    dmnsn_new_color(0.0, 0.0, 1.0)
+/** Magenta. */
+#define dmnsn_magenta dmnsn_new_color(1.0, 0.0, 1.0)
+/** Orange. */
+#define dmnsn_orange  dmnsn_new_color(1.0, 0.21404114048223255, 0.0)
+/** Yellow. */
+#define dmnsn_yellow  dmnsn_new_color(1.0, 1.0, 0.0)
+/** Cyan. */
+#define dmnsn_cyan    dmnsn_new_color(0.0, 1.0, 1.0)
diff --git a/libdimension/dimension/color/tcolor.h b/libdimension/dimension/color/tcolor.h
new file mode 100644
index 0000000..b4b4167
--- /dev/null
+++ b/libdimension/dimension/color/tcolor.h
@@ -0,0 +1,116 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Colors with transparency information.
+ */
+
+#ifndef DMNSN_COLOR_H
+#error "Please include <dimension/color.h> instead of this header directly."
+#endif
+
+/** A transparent color. */
+typedef struct dmnsn_tcolor {
+  dmnsn_color c; /**< Color. */
+  double T;      /**< Transparency. */
+  double F;      /**< Proportion of filtered transparency. */
+} dmnsn_tcolor;
+
+/** A standard format string for colors. */
+#define DMNSN_TCOLOR_FORMAT "TColor<%g, %g, %g, %g, %g>"
+/** The appropriate arguements to printf() a color. */
+#define DMNSN_TCOLOR_PRINTF(tc) (tc).c.R, (tc).c.G, (tc).c.B, (tc).T, (tc).F
+
+/** Create a tcolor. */
+DMNSN_INLINE dmnsn_tcolor
+dmnsn_new_tcolor(dmnsn_color c, double T, double F)
+{
+  dmnsn_tcolor ret = { c, T, F };
+  return ret;
+}
+
+/** Convert a dmnsn_color into a dmnsn_tcolor. */
+#define DMNSN_TCOLOR(c) dmnsn_new_tcolor(c, 0.0, 0.0)
+
+/** Create a tcolor with individually-specified components. */
+DMNSN_INLINE dmnsn_tcolor
+dmnsn_new_tcolor5(double R, double G, double B, double T, double F)
+{
+  dmnsn_tcolor ret = { dmnsn_new_color(R, G, B), T, F };
+  return ret;
+}
+
+/** Return the color at \p n on a gradient from \p c1 at 0 to \p c2 at 1. */
+DMNSN_INLINE dmnsn_tcolor
+dmnsn_tcolor_gradient(dmnsn_tcolor c1, dmnsn_tcolor c2, double n)
+{
+  return dmnsn_new_tcolor(
+    dmnsn_color_gradient(c1.c, c2.c, n),
+    n*(c2.T - c1.T) + c1.T,
+    n*(c2.F - c1.F) + c1.F
+  );
+}
+
+/** Filter \p light through \p filter. */
+DMNSN_INLINE dmnsn_color
+dmnsn_tcolor_filter(dmnsn_color light, dmnsn_tcolor filter)
+{
+  dmnsn_color filtered =
+    dmnsn_color_mul(filter.T*filter.F, dmnsn_color_illuminate(light, filter.c));
+  dmnsn_color transmitted = dmnsn_color_mul(filter.T*(1.0 - filter.F), light);
+  return dmnsn_color_add(filtered, transmitted);
+}
+
+/** Remove the filtered component of a tcolor. */
+DMNSN_INLINE dmnsn_tcolor
+dmnsn_tcolor_remove_filter(dmnsn_tcolor tcolor)
+{
+  double intensity = dmnsn_color_intensity(tcolor.c);
+  double newtrans = (1.0 - (1.0 - intensity)*tcolor.F)*tcolor.T;
+  if (1.0 - newtrans >= dmnsn_epsilon) {
+    tcolor.c = dmnsn_color_mul((1.0 - tcolor.T)/(1.0 - newtrans), tcolor.c);
+  }
+  tcolor.T = newtrans;
+  tcolor.F = 0.0;
+  return tcolor;
+}
+
+/** Saturate the tcolor components to [0.0, 1.0]. */
+DMNSN_INLINE dmnsn_tcolor
+dmnsn_tcolor_clamp(dmnsn_tcolor tcolor)
+{
+  tcolor.c = dmnsn_color_clamp(tcolor.c);
+  tcolor.T = dmnsn_clamp(tcolor.T, 0.0, 1.0);
+  tcolor.F = dmnsn_clamp(tcolor.F, 0.0, 1.0);
+  return tcolor;
+}
+
+/** Return whether a tcolor contains any NaN components. */
+DMNSN_INLINE bool
+dmnsn_tcolor_isnan(dmnsn_tcolor tcolor)
+{
+  return dmnsn_color_isnan(tcolor.c) || dmnsn_isnan(tcolor.T) || dmnsn_isnan(tcolor.F);
+}
+
+/* Standard tcolors */
+
+/** Clear. */
+#define dmnsn_clear dmnsn_new_tcolor5(0.0, 0.0, 0.0, 1.0, 0.0)
diff --git a/libdimension/dimension/common.h b/libdimension/dimension/common.h
deleted file mode 100644
index 15bafd8..0000000
--- a/libdimension/dimension/common.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Common types and utilities.
- */
-
-/**
- * Generic callback type.
- * @param[in,out] ptr  A pointer to an object to act on.
- */
-typedef void dmnsn_callback_fn(void *ptr);
diff --git a/libdimension/dimension/compiler.h b/libdimension/dimension/compiler.h
deleted file mode 100644
index af3f4c8..0000000
--- a/libdimension/dimension/compiler.h
+++ /dev/null
@@ -1,135 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Compiler abstractions.
- */
-
-/**
- * @internal
- * @def DMNSN_C_VERSION
- * The C version according to \p __STDC_VERSION__ if available, otherwise 0.
- */
-#ifdef __STDC_VERSION__
-  #define DMNSN_C_VERSION __STDC_VERSION__
-#else
-  #define DMNSN_C_VERSION 0L
-#endif
-
-/**
- * @internal
- * @def DMNSN_CXX_VERSION
- * The C++ version according to \p __cplusplus if available, otherwise 0.
- */
-#ifdef __cplusplus
-  #define DMNSN_CXX_VERSION __cplusplus
-#else
-  #define DMNSN_CXX_VERSION 0L
-#endif
-
-/**
- * @internal
- * Whether we're being compiled as C++.
- */
-#define DMNSN_CXX (DMNSN_CXX_VERSION > 0)
-
-/**
- * @internal
- * Whether C++11 features are supported.
- */
-#define DMNSN_CXX11 (DMNSN_CXX_VERSION >= 201103L)
-
-/**
- * @internal
- * Whether C99 features are supported.
- */
-#define DMNSN_C99 (DMNSN_C_VERSION >= 199901L || DMNSN_CXX11)
-
-/**
- * @internal
- * Whether C11 features are supported.
- */
-#define DMNSN_C11 (DMNSN_C_VERSION >= 201112L)
-
-/**
- * @internal
- * Whether GNU C features are supported.
- */
-#define DMNSN_GNUC defined(__GNUC__)
-
-/**
- * @def DMNSN_INLINE
- * A portable inline specifier. Expands to the correct method of declaring
- * inline functions for the version of C you are using.
- */
-#ifndef DMNSN_INLINE
-  #if DMNSN_CXX
-    /* C++ inline semantics */
-    #define DMNSN_INLINE inline
-  #elif DMNSN_C99
-    /* C99 inline semantics */
-    #define DMNSN_INLINE inline
-  #elif DMNSN_GNUC
-    /* GCC inline semantics */
-    #define DMNSN_INLINE __extension__ extern __inline__
-  #else
-    /* Unknown C - mark functions static and hope the compiler is smart enough
-       to inline them */
-    #define DMNSN_INLINE static
-  #endif
-#endif
-
-/**
- * @def DMNSN_NORETURN
- * A portable noreturn attribute.
- */
-#if DMNSN_CXX11
-  #define DMNSN_NORETURN [[noreturn]] void
-#elif DMNSN_C11
-  #define DMNSN_NORETURN _Noreturn void
-#elif DMNSN_GNUC
-  #define DMNSN_NORETURN __attribute__((noreturn)) void
-#else
-  #define DMNSN_NORETURN void
-#endif
-
-/**
- * @internal
- * @def DMNSN_FUNC
- * @brief Expands to the name of the current function
- */
-#if DMNSN_GNUC
-  #define DMNSN_FUNC __PRETTY_FUNCTION__
-#elif DMNSN_C99
-  #define DMNSN_FUNC __func__
-#else
-  #define DMNSN_FUNC "<unknown function>"
-#endif
-
-/**
- * @internal
- * An unreachable statement.
- */
-#if DMNSN_GNUC
-  #define DMNSN_UNREACHABLE() __builtin_unreachable()
-#else
-  #define DMNSN_UNREACHABLE() ((void)0)
-#endif
diff --git a/libdimension/dimension/concurrency.h b/libdimension/dimension/concurrency.h
new file mode 100644
index 0000000..6c8c29c
--- /dev/null
+++ b/libdimension/dimension/concurrency.h
@@ -0,0 +1,39 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Concurrency concerns.
+ */
+
+#ifndef DMNSN_CONCURRENCY_H
+#define DMNSN_CONCURRENCY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <dimension/concurrency/future.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DMNSN_CONCURRENCY_H */
diff --git a/libdimension/dimension/concurrency/future.h b/libdimension/dimension/concurrency/future.h
new file mode 100644
index 0000000..24d5ee2
--- /dev/null
+++ b/libdimension/dimension/concurrency/future.h
@@ -0,0 +1,85 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * An interface for asynchronous tasks.  *_async() versions of functions
+ * return a dmnsn_future* object which can indicate the progress of the
+ * background task, and wait for task completion.  The task's return value
+ * is returned as an int from dmnsn_finish_progress().
+ */
+
+#ifndef DMNSN_CONCURRENCY_H
+#error "Please include <dimension/concurrency.h> instead of this header directly."
+#endif
+
+#include <stdbool.h>
+
+/** A future object. */
+typedef struct dmnsn_future dmnsn_future;
+
+/**
+ * Join the worker thread and return its integer return value in addition to
+ * deleting \p future.
+ * @param[in,out] future  The background task to join.
+ * @return The return value of the background task.
+ */
+int dmnsn_future_join(dmnsn_future *future);
+
+/**
+ * Interrupt the execution of a background thread.
+ * @param[in,out] future  The background task to cancel.
+ */
+void dmnsn_future_cancel(dmnsn_future *future);
+
+/**
+ * Get the progress of the background task.
+ * @param[in] future  The background task to examine.
+ * @return The progress of the background task, in [0.0, 1.0].
+ */
+double dmnsn_future_progress(const dmnsn_future *future);
+
+/**
+ * Find out if a background task is finished.
+ * @param[in] future  The background task to examine.
+ * @return true if the task is done, false otherwise.
+ */
+bool dmnsn_future_is_done(const dmnsn_future *future);
+
+/**
+ * Wait for a certain amount of progress.  Always use this rather than
+ * spinlocking.
+ * @param[in] future    The background task to monitor.
+ * @param[in] progress  The progress value to wait for.
+ */
+void dmnsn_future_wait(const dmnsn_future *future, double progress);
+
+/**
+ * Pause all threads working on the given future.  Once this function returns,
+ * it is safe to examine the intermediate state of the asynchronous computation.
+ * @param[in,out] future  The background task to pause.
+ */
+void dmnsn_future_pause(dmnsn_future *future);
+
+/**
+ * Resume a previously paused future object.
+ * @param[in,out] future  The background task to resume.
+ */
+void dmnsn_future_resume(dmnsn_future *future);
diff --git a/libdimension/dimension/csg.h b/libdimension/dimension/csg.h
deleted file mode 100644
index b2ce83f..0000000
--- a/libdimension/dimension/csg.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Constructive solid geometry
- */
-
-/**
- * CSG union.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] objects  The objects from which to compose the union.
- * @return A union of the objects in \p objects.
- */
-dmnsn_object *dmnsn_new_csg_union(dmnsn_pool *pool, dmnsn_array *objects);
-
-/**
- * CSG intersection.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] A  The first object.
- * @param[in,out] B  The second object.
- * @return The intersection of \p A and \p B.
- */
-dmnsn_object *dmnsn_new_csg_intersection(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B);
-
-/**
- * CSG intersection.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] A  The outer object.
- * @param[in,out] B  The inner object.
- * @return The difference between \p A and \p B.
- */
-dmnsn_object *dmnsn_new_csg_difference(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B);
-
-/**
- * CSG Merge.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] A  The first object.
- * @param[in,out] B  The second object.
- * @return The merge of \p A and \p B.
- */
-dmnsn_object *dmnsn_new_csg_merge(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B);
diff --git a/libdimension/dimension/dictionary.h b/libdimension/dimension/dictionary.h
deleted file mode 100644
index 887b171..0000000
--- a/libdimension/dimension/dictionary.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Simple associative arrays.
- */
-
-/** A string-object associative array. */
-typedef struct dmnsn_dictionary dmnsn_dictionary;
-
-/**
- * Allocate a dictionary.
- * @param[in] obj_size  The size of the objects to store in the dictionary.
- * @return An empty dictionary.
- */
-dmnsn_dictionary *dmnsn_new_dictionary(size_t obj_size);
-
-/**
- * Delete a dictionary.
- * @param[in,out] dict  The dictionary to delete.
- */
-void dmnsn_delete_dictionary(dmnsn_dictionary *dict);
-
-/**
- * Find an element in a dictionary.
- * @param[in]  dict  The dictionary to search.
- * @param[in]  key   The key to search for.
- * @param[out] obj   The location to store the found object.
- * @return Whether the element was found.
- */
-bool dmnsn_dictionary_get(const dmnsn_dictionary *dict, const char *key,
-                          void *obj);
-
-/**
- * Access an element in a dictionary.
- * @param[in]  dict  The dictionary to search.
- * @param[in]  key   The key to search for.
- * @return A pointer to the element if found, otherwise NULL.
- */
-void *dmnsn_dictionary_at(const dmnsn_dictionary *dict, const char *key);
-
-/**
- * Insert a (key, value) pair into a dictionary.
- * @param[in,out] dict  The dictionary to modify.
- * @param[in]     key   The key to insert.
- * @param[in]     obj   The object to insert.
- */
-void dmnsn_dictionary_insert(dmnsn_dictionary *dict, const char *key,
-                             const void *obj);
-
-/**
- * Remove a (key, value) pair from a dictionary.
- * @param[in,out] dict  The dictionary to modify.
- * @param[in]     key   The key to remove.
- * @return Whether the key existed in the dictionary.
- */
-bool dmnsn_dictionary_remove(dmnsn_dictionary *dict, const char *key);
-
-/**
- * Apply a callback to all elements in a dictionary.
- * @param[in,out] dict      The dictionary.
- * @param[in]     callback  The callback to apply to the elements.
- */
-void dmnsn_dictionary_apply(dmnsn_dictionary *dict,
-                            dmnsn_callback_fn *callback);
diff --git a/libdimension/dimension/error.h b/libdimension/dimension/error.h
deleted file mode 100644
index 0561b8a..0000000
--- a/libdimension/dimension/error.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Error reporting interface.  Errors are reported at a given severity by the
- * dmnsn_error() macro at a given severity, which prints a warning if it is
- * below the set resilience, or prints an error and exits if it's at or above
- * the set resilience.
- */
-
-#include <stdbool.h>
-
-/**
- * Report a warning.
- * @param[in] str  A string to print explaining the warning.
- */
-#define dmnsn_warning(str)                                      \
-  dmnsn_report_warning(DMNSN_FUNC, __FILE__, __LINE__, str)
-
-/**
- * Report an error.
- * @param[in] str  A string to print explaining the error.
- */
-#define dmnsn_error(str)                                        \
-  dmnsn_report_error(DMNSN_FUNC, __FILE__, __LINE__, str)
-
-/**
- * @def dmnsn_assert
- * Make an assertion.
- * @param[in] expr  The expression to assert.
- * @param[in] str   A string to print if the assertion fails.
- */
-#if DMNSN_DEBUG
-  #define dmnsn_assert(expr, str)                 \
-    do {                                          \
-      if (!(expr)) {                              \
-        dmnsn_error((str));                       \
-      }                                           \
-    } while (0)
-#else
-  #define dmnsn_assert(expr, str) ((void)0)
-#endif
-
-/**
- * @def dmnsn_unreachable
- * Express that a line of code is unreachable.
- * @param[in] str  A string to print if the line is reached.
- */
-#if DMNSN_DEBUG
-  #define dmnsn_unreachable(str) dmnsn_error((str))
-#else
-  #define dmnsn_unreachable(str) DMNSN_UNREACHABLE()
-#endif
-
-/**
- * @internal
- * Called by dmnsn_warning(); don't call directly.
- * @param[in] func  The name of the function where the error originated.
- * @param[in] file  The file where the error originated.
- * @param[in] line  The line number where the error originated.
- * @param[in] str   A string describing the error.
- */
-void dmnsn_report_warning(const char *func, const char *file, unsigned int line, const char *str);
-
-/**
- * @internal
- * Called by dmnsn_error(); don't call directly.
- * @param[in] func  The name of the function where the error originated.
- * @param[in] file  The file where the error originated.
- * @param[in] line  The line number where the error originated.
- * @param[in] str   A string describing the error.
- */
-DMNSN_NORETURN dmnsn_report_error(const char *func, const char *file, unsigned int line, const char *str);
-
-/**
- * Treat warnings as errors.
- * @param[in] always_die  Whether to die on warnings.
- */
-void dmnsn_die_on_warnings(bool always_die);
-
-/**
- * Fatal error callback type.  This function should never return.
- */
-typedef void dmnsn_fatal_error_fn(void);
-
-/**
- * Get the libdimension fatal error handler, thread-safely.  The default fatal
- * error handler terminates the current thread, or the entire program if the
- * current thread is the main thread.
- * @return The current fatal error handler.
- */
-dmnsn_fatal_error_fn *dmnsn_get_fatal_error_fn(void);
-
-/**
- * Set the libdimension fatal error handler, thread-safely.
- * @param[in] fatal  The new fatal error handler.  This function must never
- *                   return.
- */
-void dmnsn_set_fatal_error_fn(dmnsn_fatal_error_fn *fatal);
diff --git a/libdimension/dimension/finish.h b/libdimension/dimension/finish.h
deleted file mode 100644
index d975877..0000000
--- a/libdimension/dimension/finish.h
+++ /dev/null
@@ -1,141 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Object finishes.
- */
-
-/* Ambient component */
-
-/** Ambient finish component. */
-typedef struct dmnsn_ambient {
-  dmnsn_color ambient; /**< Ambient light. */
-} dmnsn_ambient;
-
-/** Allocate an ambient component. */
-dmnsn_ambient *dmnsn_new_ambient(dmnsn_pool *pool, dmnsn_color ambient);
-
-/* Diffuse component */
-
-typedef struct dmnsn_diffuse dmnsn_diffuse;
-
-/**
- * Diffuse reflection callback.
- * @param[in] diffuse  The diffuse object itself.
- * @param[in] light    The color of the light illuminating the object.
- * @param[in] color    The pigment of the object.
- * @param[in] ray      The direction of the light source.
- * @param[in] normal   The normal vector of the surface.
- * @return The diffuse reflection component of the object's color.
- */
-typedef dmnsn_color dmnsn_diffuse_fn(const dmnsn_diffuse *diffuse,
-                                     dmnsn_color light, dmnsn_color color,
-                                     dmnsn_vector ray, dmnsn_vector normal);
-
-/** Diffuse finish component. */
-struct dmnsn_diffuse {
-  dmnsn_diffuse_fn *diffuse_fn; /**< Diffuse callback. */
-};
-
-/** Allocate a dummy diffuse component. */
-dmnsn_diffuse *dmnsn_new_diffuse(dmnsn_pool *pool);
-/** Initialize a dmnsn_diffuse field. */
-void dmnsn_init_diffuse(dmnsn_diffuse *diffuse);
-
-/* Specular component */
-
-typedef struct dmnsn_specular dmnsn_specular;
-
-/**
- * Specular highlight callback.
- * @param[in] specular  The specular object itself.
- * @param[in] light     The color of the light illuminating the object.
- * @param[in] color     The pigment of the object.
- * @param[in] ray       The direction of the light source.
- * @param[in] normal    The normal vector of the surface.
- * @param[in] viewer    The direction of the viewer.
- * @return The specular reflection component of the object's color.
- */
-typedef dmnsn_color dmnsn_specular_fn(const dmnsn_specular *specular,
-                                      dmnsn_color light, dmnsn_color color,
-                                      dmnsn_vector ray, dmnsn_vector normal,
-                                      dmnsn_vector viewer);
-
-/** Specular finish component. */
-struct dmnsn_specular {
-  dmnsn_specular_fn *specular_fn; /**< Specular callback. */
-};
-
-/** Allocate a dummy specular component. */
-dmnsn_specular *dmnsn_new_specular(dmnsn_pool *pool);
-/** Initialize a dmnsn_specular field. */
-void dmnsn_init_specular(dmnsn_specular *specular);
-
-/* Reflection component */
-
-typedef struct dmnsn_reflection dmnsn_reflection;
-
-/**
- * Reflected light callback.
- * @param[in] reflection  The reflection object itself.
- * @param[in] reflect     The color of the reflected ray.
- * @param[in] color       The pigment of the object.
- * @param[in] ray         The direction of the reflected ray.
- * @param[in] normal      The normal vector of the surface.
- * @return The contribution of the reflected ray to the object's color.
- */
-typedef dmnsn_color dmnsn_reflection_fn(const dmnsn_reflection *reflection,
-                                        dmnsn_color reflect, dmnsn_color color,
-                                        dmnsn_vector ray, dmnsn_vector normal);
-
-/** The reflection component. */
-struct dmnsn_reflection {
-  dmnsn_reflection_fn *reflection_fn; /**< Reflection callback. */
-};
-
-/** Allocate a dummy reflection component. */
-dmnsn_reflection *dmnsn_new_reflection(dmnsn_pool *pool);
-/** Initialize a dmnsn_reflection field. */
-void dmnsn_init_reflection(dmnsn_reflection *reflection);
-
-/* Entire finishes */
-
-/** A finish. */
-typedef struct dmnsn_finish {
-  dmnsn_ambient *ambient; /**< Ambient component. */
-  dmnsn_diffuse *diffuse; /**< Diffuse component. */
-  dmnsn_specular *specular; /**< Specular component. */
-  dmnsn_reflection *reflection; /**< Reflection component. */
-} dmnsn_finish;
-
-/**
- * Create a new blank finish.
- * @return The new finish.
- */
-dmnsn_finish dmnsn_new_finish(void);
-
-/**
- * Fill missing finish properties from a default finish.
- * @param[in]     default_finish  The default finish.
- * @param[in,out] finish          The finish to fill.
- */
-void dmnsn_finish_cascade(const dmnsn_finish *default_finish,
-                          dmnsn_finish *finish);
diff --git a/libdimension/dimension/finishes.h b/libdimension/dimension/finishes.h
deleted file mode 100644
index e1f7b44..0000000
--- a/libdimension/dimension/finishes.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pre-defined finishes.
- */
-
-/**
- * Regular diffuse finish.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] diffuse  The diffuse reflection coefficient.
- * @return A diffuse finish component.
- */
-dmnsn_diffuse *dmnsn_new_lambertian(dmnsn_pool *pool, double diffuse);
-
-/**
- * A phong specular highlight.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] specular  The specular reflection coefficient.
- * @param[in] exp  The exponent (roughly the highlight size).
- * @return A phong specular finish component.
- */
-dmnsn_specular *dmnsn_new_phong(dmnsn_pool *pool, double specular, double exp);
-
-/**
- * Specular (mirror) reflection.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] min  Reflection at paralell angles.
- * @param[in] max  Reflection at perpendicular angles (often == \p min).
- * @param[in] falloff  Degree of exponential falloff (usually 1).
- * @return A reflective finish component.
- */
-dmnsn_reflection *dmnsn_new_basic_reflection(dmnsn_pool *pool, dmnsn_color min, dmnsn_color max, double falloff);
diff --git a/libdimension/dimension/future.h b/libdimension/dimension/future.h
deleted file mode 100644
index 9ba28b1..0000000
--- a/libdimension/dimension/future.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * An interface for asynchronous tasks.  *_async() versions of functions
- * return a dmnsn_future* object which can indicate the progress of the
- * background task, and wait for task completion.  The task's return value
- * is returned as an int from dmnsn_finish_progress().
- */
-
-/** A future object. */
-typedef struct dmnsn_future dmnsn_future;
-
-/**
- * Join the worker thread and return its integer return value in addition to
- * deleting \p future.
- * @param[in,out] future  The background task to join.
- * @return The return value of the background task.
- */
-int dmnsn_future_join(dmnsn_future *future);
-
-/**
- * Interrupt the execution of a background thread.
- * @param[in,out] future  The background task to cancel.
- */
-void dmnsn_future_cancel(dmnsn_future *future);
-
-/**
- * Get the progress of the background task.
- * @param[in] future  The background task to examine.
- * @return The progress of the background task, in [0.0, 1.0].
- */
-double dmnsn_future_progress(const dmnsn_future *future);
-
-/**
- * Find out if a background task is finished.
- * @param[in] future  The background task to examine.
- * @return true if the task is done, false otherwise.
- */
-bool dmnsn_future_is_done(const dmnsn_future *future);
-
-/**
- * Wait for a certain amount of progress.  Always use this rather than
- * spinlocking.
- * @param[in] future    The background task to monitor.
- * @param[in] progress  The progress value to wait for.
- */
-void dmnsn_future_wait(const dmnsn_future *future, double progress);
-
-/**
- * Pause all threads working on the given future.  Once this function returns,
- * it is safe to examine the intermediate state of the asynchronous computation.
- * @param[in,out] future  The background task to pause.
- */
-void dmnsn_future_pause(dmnsn_future *future);
-
-/**
- * Resume a previously paused future object.
- * @param[in,out] future  The background task to resume.
- */
-void dmnsn_future_resume(dmnsn_future *future);
diff --git a/libdimension/dimension/geometry.h b/libdimension/dimension/geometry.h
deleted file mode 100644
index 2ea10ca..0000000
--- a/libdimension/dimension/geometry.h
+++ /dev/null
@@ -1,500 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Core geometric types like vectors, matricies, and rays.
- */
-
-#include <math.h>
-#include <stdbool.h>
-
-/** A vector in 3 dimensions. */
-typedef struct dmnsn_vector {
-  double x; /**< The x component. */
-  double y; /**< The y component. */
-  double z; /**< The z component. */
-} dmnsn_vector;
-
-/** A standard format string for vectors. */
-#define DMNSN_VECTOR_FORMAT "<%g, %g, %g>"
-/** The appropriate arguements to printf() a vector. */
-#define DMNSN_VECTOR_PRINTF(v) (v).x, (v).y, (v).z
-
-/** A 4x4 affine transformation matrix, with implied [0 0 0 1] bottom row. */
-typedef struct dmnsn_matrix {
-  double n[3][4]; /**< The matrix elements in row-major order. */
-} dmnsn_matrix;
-
-/** A standard format string for matricies. */
-#define DMNSN_MATRIX_FORMAT                     \
-  "[%g\t%g\t%g\t%g]\n"                          \
-  "[%g\t%g\t%g\t%g]\n"                          \
-  "[%g\t%g\t%g\t%g]\n"                          \
-  "[%g\t%g\t%g\t%g]"
-/** The appropriate arguements to printf() a matrix. */
-#define DMNSN_MATRIX_PRINTF(m)                          \
-  (m).n[0][0], (m).n[0][1], (m).n[0][2], (m).n[0][3],   \
-  (m).n[1][0], (m).n[1][1], (m).n[1][2], (m).n[1][3],   \
-  (m).n[2][0], (m).n[2][1], (m).n[2][2], (m).n[2][3],   \
-  0.0, 0.0, 0.0, 1.0
-
-/** A line, or ray. */
-typedef struct dmnsn_line {
-  dmnsn_vector x0; /**< A point on the line. */
-  dmnsn_vector n;  /**< A normal vector; the direction of the line. */
-} dmnsn_line;
-
-/** A standard format string for lines. */
-#define DMNSN_LINE_FORMAT "(<%g, %g, %g> + t*<%g, %g, %g>)"
-/** The appropriate arguements to printf() a line. */
-#define DMNSN_LINE_PRINTF(l)                                    \
-  DMNSN_VECTOR_PRINTF((l).x0), DMNSN_VECTOR_PRINTF((l).n)
-
-/** An axis-aligned bounding box (AABB). */
-typedef struct dmnsn_bounding_box {
-  dmnsn_vector min; /**< The coordinate-wise minimum extent of the box. */
-  dmnsn_vector max; /**< The coordinate-wise maximum extent of the box. */
-} dmnsn_bounding_box;
-
-/** A standard format string for bounding boxes. */
-#define DMNSN_BOUNDING_BOX_FORMAT "(<%g, %g, %g> ==> <%g, %g, %g>)"
-/** The appropriate arguements to printf() a bounding box. */
-#define DMNSN_BOUNDING_BOX_PRINTF(box)                                  \
-  DMNSN_VECTOR_PRINTF((box).min), DMNSN_VECTOR_PRINTF((box).max)
-
-/* Constants */
-
-/** The zero vector. */
-static const dmnsn_vector dmnsn_zero = { 0.0, 0.0, 0.0 };
-/** The x vector. */
-static const dmnsn_vector dmnsn_x = { 1.0, 0.0, 0.0 };
-/** The y vector. */
-static const dmnsn_vector dmnsn_y = { 0.0, 1.0, 0.0 };
-/** The z vector. */
-static const dmnsn_vector dmnsn_z = { 0.0, 0.0, 1.0 };
-
-/* Shorthand for vector/matrix construction */
-
-/** Construct a new vector. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_new_vector(double x, double y, double z)
-{
-  dmnsn_vector v = { x, y, z };
-  return v;
-}
-
-/** Construct a new transformation matrix. */
-DMNSN_INLINE dmnsn_matrix
-dmnsn_new_matrix(double a0, double a1, double a2, double a3,
-                 double b0, double b1, double b2, double b3,
-                 double c0, double c1, double c2, double c3)
-{
-  dmnsn_matrix m = { { { a0, a1, a2, a3 },
-                       { b0, b1, b2, b3 },
-                       { c0, c1, c2, c3 } } };
-  return m;
-}
-
-/** Construct a new transformation matrix from column vectors. */
-DMNSN_INLINE dmnsn_matrix
-dmnsn_new_matrix4(dmnsn_vector a, dmnsn_vector b, dmnsn_vector c,
-                  dmnsn_vector d)
-{
-  dmnsn_matrix m = { { { a.x, b.x, c.x, d.x },
-                       { a.y, b.y, c.y, d.y },
-                       { a.z, b.z, c.z, d.z } } };
-  return m;
-}
-
-/** Extract column vectors from a matrix. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_matrix_column(dmnsn_matrix M, unsigned int i)
-{
-  return dmnsn_new_vector(M.n[0][i], M.n[1][i], M.n[2][i]);
-}
-
-/** Return the identity matrix. */
-dmnsn_matrix dmnsn_identity_matrix(void);
-
-/**
- * A scale transformation.
- * @param[in] s  A vector with components representing the scaling factor in
- *               each axis.
- * @return The transformation matrix.
- */
-dmnsn_matrix dmnsn_scale_matrix(dmnsn_vector s);
-/**
- * A translation.
- * @param[in] d  The vector to translate by.
- * @return The transformation matrix.
- */
-dmnsn_matrix dmnsn_translation_matrix(dmnsn_vector d);
-/**
- * A left-handed rotation.
- * @param[in] theta  A vector representing an axis and angle.
- *                   @f$ axis = \vec{\theta}/|\vec{\theta}| @f$,
- *                   @f$ angle = |\vec{\theta}| @f$
- * @return The transformation matrix.
- */
-dmnsn_matrix dmnsn_rotation_matrix(dmnsn_vector theta);
-/**
- * An alignment matrix.
- * @param[in] from   The initial vector.
- * @param[in] to     The desired direction.
- * @param[in] axis1  The first axis about which to rotate.
- * @param[in] axis2  The second axis about which to rotate.
- * @return A transformation matrix that will rotate \p from to \p to.
- */
-dmnsn_matrix dmnsn_alignment_matrix(dmnsn_vector from, dmnsn_vector to,
-                                    dmnsn_vector axis1, dmnsn_vector axis2);
-
-/**
- * Construct a new line.
- * @param[in] x0  A point on the line.
- * @param[in] n   The direction of the line.
- * @return The new line.
- */
-DMNSN_INLINE dmnsn_line
-dmnsn_new_line(dmnsn_vector x0, dmnsn_vector n)
-{
-  dmnsn_line l = { x0, n };
-  return l;
-}
-
-/**
- * Construct a new bounding box.
- * @param[in] min  The minimal extent of the bounding box.
- * @param[in] max  The maximal extent of the bounding box.
- * @return The new bounding box.
- */
-DMNSN_INLINE dmnsn_bounding_box
-dmnsn_new_bounding_box(dmnsn_vector min, dmnsn_vector max)
-{
-  dmnsn_bounding_box box = { min, max };
-  return box;
-}
-
-/** Return the bounding box which contains nothing. */
-DMNSN_INLINE dmnsn_bounding_box
-dmnsn_zero_bounding_box(void)
-{
-  dmnsn_bounding_box box = {
-    {  DMNSN_INFINITY,  DMNSN_INFINITY,  DMNSN_INFINITY },
-    { -DMNSN_INFINITY, -DMNSN_INFINITY, -DMNSN_INFINITY }
-  };
-  return box;
-}
-
-/** Return the bounding box which contains everything. */
-DMNSN_INLINE dmnsn_bounding_box
-dmnsn_infinite_bounding_box(void)
-{
-  dmnsn_bounding_box box = {
-    { -DMNSN_INFINITY, -DMNSN_INFINITY, -DMNSN_INFINITY },
-    {  DMNSN_INFINITY,  DMNSN_INFINITY,  DMNSN_INFINITY }
-  };
-  return box;
-}
-
-/* Vector and matrix arithmetic */
-
-/** Negate a vector. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_negate(dmnsn_vector rhs)
-{
-  /* 3 negations */
-  dmnsn_vector v = { -rhs.x, -rhs.y, -rhs.z };
-  return v;
-}
-
-/** Add two vectors. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_add(dmnsn_vector lhs, dmnsn_vector rhs)
-{
-  /* 3 additions */
-  dmnsn_vector v = { lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z };
-  return v;
-}
-
-/** Subtract two vectors. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_sub(dmnsn_vector lhs, dmnsn_vector rhs)
-{
-  /* 3 additions */
-  dmnsn_vector v = { lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z };
-  return v;
-}
-
-/** Multiply a vector by a scalar. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_mul(double lhs, dmnsn_vector rhs)
-{
-  /* 3 multiplications */
-  dmnsn_vector v = { lhs*rhs.x, lhs*rhs.y, lhs*rhs.z };
-  return v;
-}
-
-/** Divide a vector by a scalar. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_div(dmnsn_vector lhs, double rhs)
-{
-  /* 3 divisions */
-  dmnsn_vector v = { lhs.x/rhs, lhs.y/rhs, lhs.z/rhs };
-  return v;
-}
-
-/** Return the dot product of two vectors. */
-DMNSN_INLINE double
-dmnsn_vector_dot(dmnsn_vector lhs, dmnsn_vector rhs)
-{
-  /* 3 multiplications, 2 additions */
-  return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z;
-}
-
-/** Return the cross product of two vectors. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_cross(dmnsn_vector lhs, dmnsn_vector rhs)
-{
-  /* 6 multiplications, 3 additions */
-  dmnsn_vector v = { lhs.y*rhs.z - lhs.z*rhs.y,
-                     lhs.z*rhs.x - lhs.x*rhs.z,
-                     lhs.x*rhs.y - lhs.y*rhs.x };
-  return v;
-}
-
-/** Return the projection of \p u onto \p d. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_proj(dmnsn_vector u, dmnsn_vector d)
-{
-  /* 1 division, 9 multiplications, 4 additions */
-  return dmnsn_vector_mul(dmnsn_vector_dot(u, d)/dmnsn_vector_dot(d, d), d);
-}
-
-/** Return the magnitude of a vector. */
-DMNSN_INLINE double
-dmnsn_vector_norm(dmnsn_vector n)
-{
-  /* 1 sqrt, 3 multiplications, 2 additions */
-  return sqrt(dmnsn_vector_dot(n, n));
-}
-
-/** Return the direction of a vector. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_normalized(dmnsn_vector n)
-{
-  /* 1 sqrt, 3 divisions, 3 multiplications, 2 additions */
-  return dmnsn_vector_div(n, dmnsn_vector_norm(n));
-}
-
-/** Return the component-wise minimum of two vectors. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_min(dmnsn_vector a, dmnsn_vector b)
-{
-  return dmnsn_new_vector(
-    dmnsn_min(a.x, b.x),
-    dmnsn_min(a.y, b.y),
-    dmnsn_min(a.z, b.z)
-  );
-}
-
-/** Return the component-wise maximum of two vectors. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_vector_max(dmnsn_vector a, dmnsn_vector b)
-{
-  return dmnsn_new_vector(
-    dmnsn_max(a.x, b.x),
-    dmnsn_max(a.y, b.y),
-    dmnsn_max(a.z, b.z)
-  );
-}
-
-/** Invert a matrix. */
-dmnsn_matrix dmnsn_matrix_inverse(dmnsn_matrix A);
-
-/** Multiply two matricies. */
-dmnsn_matrix dmnsn_matrix_mul(dmnsn_matrix lhs, dmnsn_matrix rhs);
-
-/** Transform a point by a matrix. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_transform_point(dmnsn_matrix T, dmnsn_vector v)
-{
-  /* 9 multiplications, 9 additions */
-  dmnsn_vector r;
-  r.x = T.n[0][0]*v.x + T.n[0][1]*v.y + T.n[0][2]*v.z + T.n[0][3];
-  r.y = T.n[1][0]*v.x + T.n[1][1]*v.y + T.n[1][2]*v.z + T.n[1][3];
-  r.z = T.n[2][0]*v.x + T.n[2][1]*v.y + T.n[2][2]*v.z + T.n[2][3];
-  return r;
-}
-
-/** Transform a direction by a matrix. */
-DMNSN_INLINE dmnsn_vector
-dmnsn_transform_direction(dmnsn_matrix T, dmnsn_vector v)
-{
-  /* 9 multiplications, 6 additions */
-  dmnsn_vector r;
-  r.x = T.n[0][0]*v.x + T.n[0][1]*v.y + T.n[0][2]*v.z;
-  r.y = T.n[1][0]*v.x + T.n[1][1]*v.y + T.n[1][2]*v.z;
-  r.z = T.n[2][0]*v.x + T.n[2][1]*v.y + T.n[2][2]*v.z;
-  return r;
-}
-
-/**
- * Transform a pseudovector by a matrix.
- * @param[in] Tinv  The inverse of the transformation matrix.
- * @param[in] v     The pseudovector to transform
- * @return The transformed pseudovector.
- */
-DMNSN_INLINE dmnsn_vector
-dmnsn_transform_normal(dmnsn_matrix Tinv, dmnsn_vector v)
-{
-  /* Multiply by the transpose of the inverse
-     (9 multiplications, 6 additions) */
-  dmnsn_vector r;
-  r.x = Tinv.n[0][0]*v.x + Tinv.n[1][0]*v.y + Tinv.n[2][0]*v.z;
-  r.y = Tinv.n[0][1]*v.x + Tinv.n[1][1]*v.y + Tinv.n[2][1]*v.z;
-  r.z = Tinv.n[0][2]*v.x + Tinv.n[1][2]*v.y + Tinv.n[2][2]*v.z;
-  return r;
-}
-
-/** Transform a bounding box by a matrix. */
-dmnsn_bounding_box dmnsn_transform_bounding_box(dmnsn_matrix T,
-                                                dmnsn_bounding_box box);
-
-/**
- * Transform a line by a matrix.
- * \f$ n' = T(l.\vec{x_0} + l.\vec{n}) - T(l.\vec{x_0}) \f$,
- * \f$ \vec{x_0}' = T(l.\vec{x_0}) \f$
- */
-DMNSN_INLINE dmnsn_line
-dmnsn_transform_line(dmnsn_matrix T, dmnsn_line l)
-{
-  /* 18 multiplications, 15 additions */
-  dmnsn_line ret;
-  ret.x0 = dmnsn_transform_point(T, l.x0);
-  ret.n  = dmnsn_transform_direction(T, l.n);
-  return ret;
-}
-
-/**
- * Return the point at \p t on a line.
- * The point is defined by \f$ l.\vec{x_0} + t \cdot l.\vec{n} \f$
- */
-DMNSN_INLINE dmnsn_vector
-dmnsn_line_point(dmnsn_line l, double t)
-{
-  return dmnsn_vector_add(l.x0, dmnsn_vector_mul(t, l.n));
-}
-
-/** Add epsilon*l.n to l.x0, to avoid self-intersections. */
-DMNSN_INLINE dmnsn_line
-dmnsn_line_add_epsilon(dmnsn_line l)
-{
-  return dmnsn_new_line(
-    dmnsn_vector_add(
-      l.x0,
-      dmnsn_vector_mul(1.0e3*dmnsn_epsilon, l.n)
-    ),
-    l.n
-  );
-}
-
-/**
- * Construct a new symmetric bounding box.
- * @param[in] r  The extent of the bounding box from the origin.
- * @return The new bounding box.
- */
-DMNSN_INLINE dmnsn_bounding_box
-dmnsn_symmetric_bounding_box(dmnsn_vector r)
-{
-  dmnsn_vector minus_r = dmnsn_vector_negate(r);
-  dmnsn_bounding_box box = {
-    dmnsn_vector_min(r, minus_r),
-    dmnsn_vector_max(r, minus_r)
-  };
-  return box;
-}
-
-/** Return whether \p p is within the axis-aligned bounding box. */
-DMNSN_INLINE bool
-dmnsn_bounding_box_contains(dmnsn_bounding_box box, dmnsn_vector p)
-{
-  return (p.x >= box.min.x && p.y >= box.min.y && p.z >= box.min.z)
-      && (p.x <= box.max.x && p.y <= box.max.y && p.z <= box.max.z);
-}
-
-/** Return whether a bounding box is infinite. */
-DMNSN_INLINE bool
-dmnsn_bounding_box_is_infinite(dmnsn_bounding_box box)
-{
-  return box.min.x == -DMNSN_INFINITY;
-}
-
-/**
- * Expand a bounding box to contain a point
- * @param[in] box  The bounding box to expand.
- * @param[in] point  The point to swallow.
- * @return The expanded bounding box.
- */
-DMNSN_INLINE dmnsn_bounding_box
-dmnsn_bounding_box_swallow(dmnsn_bounding_box box, dmnsn_vector point)
-{
-  dmnsn_bounding_box ret = {
-    dmnsn_vector_min(box.min, point),
-    dmnsn_vector_max(box.max, point)
-  };
-  return ret;
-}
-
-/** Return whether a vector contains any NaN components. */
-DMNSN_INLINE bool
-dmnsn_vector_isnan(dmnsn_vector v)
-{
-  return dmnsn_isnan(v.x) || dmnsn_isnan(v.y) || dmnsn_isnan(v.z);
-}
-
-/** Return whether a matrix contains any NaN components. */
-DMNSN_INLINE bool
-dmnsn_matrix_isnan(dmnsn_matrix m)
-{
-  size_t i, j;
-  for (i = 0; i < 3; ++i) {
-    for (j = 0; j < 4; ++j) {
-      if (dmnsn_isnan(m.n[i][j])) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
-/** Return whether a line contains any NaN entries. */
-DMNSN_INLINE bool
-dmnsn_line_isnan(dmnsn_line l)
-{
-  return dmnsn_vector_isnan(l.x0) || dmnsn_vector_isnan(l.n);
-}
-
-/** Return whether a bounding box has any NaN components. */
-DMNSN_INLINE bool
-dmnsn_bounding_box_isnan(dmnsn_bounding_box box)
-{
-  return dmnsn_vector_isnan(box.min) || dmnsn_vector_isnan(box.max);
-}
diff --git a/libdimension/dimension/gl.h b/libdimension/dimension/gl.h
deleted file mode 100644
index 284a248..0000000
--- a/libdimension/dimension/gl.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * OpenGL export/import of canvases.
- */
-
-/**
- * Optimize a canvas for GL drawing
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] canvas  The canvas to optimize.
- * @return Whether the canvas was successfully optimized.
- */
-int dmnsn_gl_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
-
-/**
- * Write canvas to GL framebuffer.
- * @param[in] canvas  The canvas to draw.
- * @return 0 on success, non-zero on failure.
- */
-int dmnsn_gl_write_canvas(const dmnsn_canvas *canvas);
-
-/**
- * Read a canvas from a GL framebuffer.
- * @param[in] canvas  The canvas to write to.
- * @param[in] x0  The \a x screen coordinate to start copying from.
- * @param[in] y0  The \a y screen coordinate to start copying from.
- * @return 0 on success, non-zero on failure.
- */
-int dmnsn_gl_read_canvas(dmnsn_canvas *canvas, size_t x0, size_t y0);
diff --git a/libdimension/dimension/interior.h b/libdimension/dimension/interior.h
deleted file mode 100644
index 0ff697d..0000000
--- a/libdimension/dimension/interior.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Object interiors.
- */
-
-/** An interior. */
-typedef struct dmnsn_interior {
-  double ior; /**< Refractive index. */
-} dmnsn_interior;
-
-/**
- * Create an interior object.
- * @param[in] pool  The memory pool to allocate from.
- * @return The new interior.
- */
-dmnsn_interior *dmnsn_new_interior(dmnsn_pool *pool);
-
-/**
- * Fill missing interior properties from a default interior.
- * @param[in]     default_interior  The default interior.
- * @param[in,out] interiorp         A pointer to the interior to fill.
- */
-void dmnsn_interior_cascade(dmnsn_interior *default_interior,
-                            dmnsn_interior **interiorp);
diff --git a/libdimension/dimension/light.h b/libdimension/dimension/light.h
deleted file mode 100644
index 218611d..0000000
--- a/libdimension/dimension/light.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Lights.
- */
-
-#include <stdbool.h>
-
-/* Forward-declar dmnsn_light */
-typedef struct dmnsn_light dmnsn_light;
-
-/**
- * Light direction callback.
- * @param[in] light  The light itself.
- * @param[in] v      The point to illuminate.
- * @return The direction of light rays pointing from \p v
- */
-typedef dmnsn_vector dmnsn_light_direction_fn(const dmnsn_light *light,
-                                              dmnsn_vector v);
-
-/**
- * Light illumination callback.
- * @param[in] light  The light itself.
- * @param[in] v      The point to illuminate.
- * @return The color of the light at \p v.
- */
-typedef dmnsn_color dmnsn_light_illumination_fn(const dmnsn_light *light,
-                                                dmnsn_vector v);
-
-/**
- * Light shadow callback.
- * @param[in] light  The light itself.
- * @param[in] t      The line index of the closest shadow ray intersection.
- * @return Whether the point is in shadow.
- */
-typedef bool dmnsn_light_shadow_fn(const dmnsn_light *light, double t);
-
-/** A light. */
-struct dmnsn_light {
-  /* Callbacks */
-  dmnsn_light_direction_fn *direction_fn; /**< Direction callback. */
-  dmnsn_light_illumination_fn *illumination_fn; /**< Illumination callback. */
-  dmnsn_light_shadow_fn *shadow_fn; /**< Shadow callback. */
-};
-
-/**
- * Create a dummy light.
- * @param[in] pool  The memory pool to allocate from.
- * @return The allocated light.
- */
-dmnsn_light *dmnsn_new_light(dmnsn_pool *pool);
-
-/**
- * Initialize a dmnsn_light field.
- * @param[out] light  The light to initialize.
- */
-void dmnsn_init_light(dmnsn_light *light);
diff --git a/libdimension/dimension/lights.h b/libdimension/dimension/lights.h
deleted file mode 100644
index e7de4cc..0000000
--- a/libdimension/dimension/lights.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pre-defined light types.
- */
-
-/**
- * A point light.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] x0     The origin of the light.
- * @param[in] color  The color of the light.
- * @return A point light.
- */
-dmnsn_light *dmnsn_new_point_light(dmnsn_pool *pool, dmnsn_vector x0, dmnsn_color color);
diff --git a/libdimension/dimension/malloc.h b/libdimension/dimension/malloc.h
deleted file mode 100644
index 742e3a2..0000000
--- a/libdimension/dimension/malloc.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Dynamic memory.  dmnsn_malloc() and friends behave like their
- * non-dmnsn_-prefixed counterparts, but never return NULL.  If allocation
- * fails, they instead call dmnsn_error().
- */
-
-#include <stddef.h> /* For size_t */
-
-/**
- * Allocate some memory.  Always use dmnsn_free() to free this memory, never
- * free().
- * @param[in] size  The size of the memory block to allocate.
- * @return The allocated memory area.
- */
-void *dmnsn_malloc(size_t size);
-
-/**
- * Allocate some memory.  Always use dmnsn_free() to free this memory, never
- * free().
- * @param[in] type  The type of the memory block to allocate.
- * @return The allocated memory area.
- */
-#define DMNSN_MALLOC(type) ((type *)dmnsn_malloc(sizeof(type)))
-
-/**
- * Expand or shrink an allocation created by dmnsn_malloc().
- * @param[in] ptr   The block to resize.
- * @param[in] size  The new size.
- * @return The resized memory area.
- */
-void *dmnsn_realloc(void *ptr, size_t size);
-
-/**
- * Duplicate a string.
- * @param[in] s  The string to duplicate.
- * @return A string with the same contents as \p s, suitable for release by
- *         dmnsn_free().
- */
-char *dmnsn_strdup(const char *s);
-
-/**
- * Free memory allocated by dmnsn_malloc() or dmnsn_strdup().
- * @param[in] ptr  The memory block to free, or NULL.
- */
-void dmnsn_free(void *ptr);
diff --git a/libdimension/dimension/map.h b/libdimension/dimension/map.h
deleted file mode 100644
index 7229a24..0000000
--- a/libdimension/dimension/map.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Generic maps (backend for pigment_maps, etc.).
- */
-
-/** A map. */
-typedef struct dmnsn_map dmnsn_map;
-
-/**
- * Create an empty map.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] size  The size of the objects to store in the map.
- * @return A map with no entries.
- */
-dmnsn_map *dmnsn_new_map(dmnsn_pool *pool, size_t size);
-
-/**
- * Add an entry (a scalar-object pair) to a map.
- * @param[in,out] map  The map to add to.
- * @param[in]     n    The index of the entry.
- * @param[in]     obj  The value of the entry.
- */
-void dmnsn_map_add_entry(dmnsn_map *map, double n, const void *obj);
-
-/**
- * Return the number of entries in a map.
- * @param[in] map  The map to measure.
- * @return The size of \p map.
- */
-size_t dmnsn_map_size(const dmnsn_map *map);
-
-/**
- * Evaluate a map.
- * @param[in]  map   The map to evaluate.
- * @param[in]  n     The index to evaluate.
- * @param[out] val   The normalized distance of \p n from \p obj1.
- * @param[out] obj1  The first object.
- * @param[out] obj2  The second object.
- */
-void dmnsn_map_evaluate(const dmnsn_map *map, double n,
-                        double *val, void *obj1, void *obj2);
-
-/**
- * Apply a callback to each element of a map.
- * @param[in,out] map       The map.
- * @param[in]     callback  The callback to apply to the elements.
- */
-void dmnsn_map_apply(dmnsn_map *map, dmnsn_callback_fn *callback);
diff --git a/libdimension/dimension/math.h b/libdimension/dimension/math.h
index 597be36..603373f 100644
--- a/libdimension/dimension/math.h
+++ b/libdimension/dimension/math.h
@@ -20,80 +20,26 @@
 
 /**
  * @file
- * Useful math functions.
+ * Mathematical functions and types.
  */
 
-#include <math.h>
-#include <stdbool.h>
+#ifndef DMNSN_MATH_H
+#define DMNSN_MATH_H
 
-/** The smallest value considered non-zero by some numerical algorithms. */
-#define dmnsn_epsilon 1.0e-10
-
-/**
- * @def DMNSN_INFINITY
- * Expands to floating-point infinity.
- */
-#if defined(INFINITY) || DMNSN_C99
-  #define DMNSN_INFINITY INFINITY
-#else
-  #define DMNSN_INFINITY HUGE_VAL
+#ifdef __cplusplus
+extern "C" {
 #endif
 
-/** Find the minimum of two values. */
-DMNSN_INLINE double
-dmnsn_min(double a, double b)
-{
-  return a < b ? a : b;
-}
-
-/** Find the maximum of two values. */
-DMNSN_INLINE double
-dmnsn_max(double a, double b)
-{
-  return a > b ? a : b;
-}
-
-/** Clamp a value to an interval. */
-DMNSN_INLINE double
-dmnsn_clamp(double n, double min, double max)
-{
-  return dmnsn_min(dmnsn_max(n, min), max);
-}
-
-/** Convert degrees to radians. */
-DMNSN_INLINE double
-dmnsn_radians(double degrees)
-{
-  return degrees*(atan(1.0)/45.0);
-}
+#include <dimension/base.h>
 
-/** Convert radians to degrees. */
-DMNSN_INLINE double
-dmnsn_degrees(double radians)
-{
-  return radians*(45.0/atan(1.0));
-}
+#include <dimension/math/scalar.h>
+#include <dimension/math/vector.h>
+#include <dimension/math/ray.h>
+#include <dimension/math/aabb.h>
+#include <dimension/math/matrix.h>
 
-/** Signum function: return the sign of a value. */
-DMNSN_INLINE int
-dmnsn_sgn(double n)
-{
-  if (n > 0.0) {
-    return 1;
-  } else if (n < 0.0) {
-    return -1;
-  } else {
-    return 0;
-  }
+#ifdef __cplusplus
 }
-
-/** Return whether a value is NaN. */
-DMNSN_INLINE bool
-dmnsn_isnan(double n)
-{
-#if DMNSN_C99
-  return isnan(n);
-#else
-  return n != n;
 #endif
-}
+
+#endif /* DMNSN_MATH_H */
diff --git a/libdimension/dimension/math/aabb.h b/libdimension/dimension/math/aabb.h
new file mode 100644
index 0000000..14cc575
--- /dev/null
+++ b/libdimension/dimension/math/aabb.h
@@ -0,0 +1,129 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Axis-aligned bounding boxes.
+ */
+
+#ifndef DMNSN_MATH_H
+#error "Please include <dimension/math.h> instead of this header directly."
+#endif
+
+/** An axis-aligned bounding box. */
+typedef struct dmnsn_aabb {
+  dmnsn_vector min; /**< The coordinate-wise minimum extent of the box. */
+  dmnsn_vector max; /**< The coordinate-wise maximum extent of the box. */
+} dmnsn_aabb;
+
+/** A standard format string for bounding boxes. */
+#define DMNSN_AABB_FORMAT "(<%g, %g, %g> ==> <%g, %g, %g>)"
+/** The appropriate arguements to printf() a bounding box. */
+#define DMNSN_AABB_PRINTF(box)                                          \
+  DMNSN_VECTOR_PRINTF((box).min), DMNSN_VECTOR_PRINTF((box).max)
+
+/**
+ * Construct a new bounding box.
+ * @param[in] min  The minimal extent of the bounding box.
+ * @param[in] max  The maximal extent of the bounding box.
+ * @return The new bounding box.
+ */
+DMNSN_INLINE dmnsn_aabb
+dmnsn_new_aabb(dmnsn_vector min, dmnsn_vector max)
+{
+  dmnsn_aabb box = { min, max };
+  return box;
+}
+
+/** Return the bounding box which contains nothing. */
+DMNSN_INLINE dmnsn_aabb
+dmnsn_zero_aabb(void)
+{
+  dmnsn_aabb box = {
+    {  DMNSN_INFINITY,  DMNSN_INFINITY,  DMNSN_INFINITY },
+    { -DMNSN_INFINITY, -DMNSN_INFINITY, -DMNSN_INFINITY }
+  };
+  return box;
+}
+
+/** Return the bounding box which contains everything. */
+DMNSN_INLINE dmnsn_aabb
+dmnsn_infinite_aabb(void)
+{
+  dmnsn_aabb box = {
+    { -DMNSN_INFINITY, -DMNSN_INFINITY, -DMNSN_INFINITY },
+    {  DMNSN_INFINITY,  DMNSN_INFINITY,  DMNSN_INFINITY }
+  };
+  return box;
+}
+
+/**
+ * Construct a new symmetric bounding box.
+ * @param[in] r  The extent of the bounding box from the origin.
+ * @return The new bounding box.
+ */
+DMNSN_INLINE dmnsn_aabb
+dmnsn_symmetric_aabb(dmnsn_vector r)
+{
+  dmnsn_vector minus_r = dmnsn_vector_negate(r);
+  dmnsn_aabb box = {
+    dmnsn_vector_min(r, minus_r),
+    dmnsn_vector_max(r, minus_r)
+  };
+  return box;
+}
+
+/** Return whether \p p is within the axis-aligned bounding box. */
+DMNSN_INLINE bool
+dmnsn_aabb_contains(dmnsn_aabb box, dmnsn_vector p)
+{
+  return (p.x >= box.min.x && p.y >= box.min.y && p.z >= box.min.z)
+      && (p.x <= box.max.x && p.y <= box.max.y && p.z <= box.max.z);
+}
+
+/** Return whether a bounding box is infinite. */
+DMNSN_INLINE bool
+dmnsn_aabb_is_infinite(dmnsn_aabb box)
+{
+  return box.min.x == -DMNSN_INFINITY;
+}
+
+/**
+ * Expand a bounding box to contain a point
+ * @param[in] box  The bounding box to expand.
+ * @param[in] point  The point to swallow.
+ * @return The expanded bounding box.
+ */
+DMNSN_INLINE dmnsn_aabb
+dmnsn_aabb_swallow(dmnsn_aabb box, dmnsn_vector point)
+{
+  dmnsn_aabb ret = {
+    dmnsn_vector_min(box.min, point),
+    dmnsn_vector_max(box.max, point)
+  };
+  return ret;
+}
+
+/** Return whether a bounding box has any NaN components. */
+DMNSN_INLINE bool
+dmnsn_aabb_isnan(dmnsn_aabb box)
+{
+  return dmnsn_vector_isnan(box.min) || dmnsn_vector_isnan(box.max);
+}
diff --git a/libdimension/dimension/math/matrix.h b/libdimension/dimension/math/matrix.h
new file mode 100644
index 0000000..7471bf5
--- /dev/null
+++ b/libdimension/dimension/math/matrix.h
@@ -0,0 +1,193 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Affine transformation matrices.
+ */
+
+#ifndef DMNSN_MATH_H
+#error "Please include <dimension/math.h> instead of this header directly."
+#endif
+
+/** A 4x4 affine transformation matrix, with implied [0 0 0 1] bottom row. */
+typedef struct dmnsn_matrix {
+  double n[3][4]; /**< The matrix elements in row-major order. */
+} dmnsn_matrix;
+
+/** A standard format string for matricies. */
+#define DMNSN_MATRIX_FORMAT                     \
+  "[%g\t%g\t%g\t%g]\n"                          \
+  "[%g\t%g\t%g\t%g]\n"                          \
+  "[%g\t%g\t%g\t%g]\n"                          \
+  "[%g\t%g\t%g\t%g]"
+/** The appropriate arguements to printf() a matrix. */
+#define DMNSN_MATRIX_PRINTF(m)                          \
+  (m).n[0][0], (m).n[0][1], (m).n[0][2], (m).n[0][3],   \
+  (m).n[1][0], (m).n[1][1], (m).n[1][2], (m).n[1][3],   \
+  (m).n[2][0], (m).n[2][1], (m).n[2][2], (m).n[2][3],   \
+  0.0, 0.0, 0.0, 1.0
+
+/** Construct a new transformation matrix. */
+DMNSN_INLINE dmnsn_matrix
+dmnsn_new_matrix(double a0, double a1, double a2, double a3,
+                 double b0, double b1, double b2, double b3,
+                 double c0, double c1, double c2, double c3)
+{
+  dmnsn_matrix m = { { { a0, a1, a2, a3 },
+                       { b0, b1, b2, b3 },
+                       { c0, c1, c2, c3 } } };
+  return m;
+}
+
+/** Construct a new transformation matrix from column vectors. */
+DMNSN_INLINE dmnsn_matrix
+dmnsn_new_matrix4(dmnsn_vector a, dmnsn_vector b, dmnsn_vector c,
+                  dmnsn_vector d)
+{
+  dmnsn_matrix m = { { { a.x, b.x, c.x, d.x },
+                       { a.y, b.y, c.y, d.y },
+                       { a.z, b.z, c.z, d.z } } };
+  return m;
+}
+
+/** Extract column vectors from a matrix. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_matrix_column(dmnsn_matrix M, unsigned int i)
+{
+  return dmnsn_new_vector(M.n[0][i], M.n[1][i], M.n[2][i]);
+}
+
+/** Return the identity matrix. */
+dmnsn_matrix dmnsn_identity_matrix(void);
+
+/**
+ * A scale transformation.
+ * @param[in] s  A vector with components representing the scaling factor in
+ *               each axis.
+ * @return The transformation matrix.
+ */
+dmnsn_matrix dmnsn_scale_matrix(dmnsn_vector s);
+/**
+ * A translation.
+ * @param[in] d  The vector to translate by.
+ * @return The transformation matrix.
+ */
+dmnsn_matrix dmnsn_translation_matrix(dmnsn_vector d);
+/**
+ * A left-handed rotation.
+ * @param[in] theta  A vector representing an axis and angle.
+ *                   @f$ axis = \vec{\theta}/|\vec{\theta}| @f$,
+ *                   @f$ angle = |\vec{\theta}| @f$
+ * @return The transformation matrix.
+ */
+dmnsn_matrix dmnsn_rotation_matrix(dmnsn_vector theta);
+
+/**
+ * An alignment matrix.
+ * @param[in] from   The initial vector.
+ * @param[in] to     The desired direction.
+ * @param[in] axis1  The first axis about which to rotate.
+ * @param[in] axis2  The second axis about which to rotate.
+ * @return A transformation matrix that will rotate \p from to \p to.
+ */
+dmnsn_matrix dmnsn_alignment_matrix(dmnsn_vector from, dmnsn_vector to,
+                                    dmnsn_vector axis1, dmnsn_vector axis2);
+
+/** Invert a matrix. */
+dmnsn_matrix dmnsn_matrix_inverse(dmnsn_matrix A);
+
+/** Multiply two matricies. */
+dmnsn_matrix dmnsn_matrix_mul(dmnsn_matrix lhs, dmnsn_matrix rhs);
+
+/** Transform a point by a matrix. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_transform_point(dmnsn_matrix T, dmnsn_vector v)
+{
+  /* 9 multiplications, 9 additions */
+  dmnsn_vector r;
+  r.x = T.n[0][0]*v.x + T.n[0][1]*v.y + T.n[0][2]*v.z + T.n[0][3];
+  r.y = T.n[1][0]*v.x + T.n[1][1]*v.y + T.n[1][2]*v.z + T.n[1][3];
+  r.z = T.n[2][0]*v.x + T.n[2][1]*v.y + T.n[2][2]*v.z + T.n[2][3];
+  return r;
+}
+
+/** Transform a direction by a matrix. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_transform_direction(dmnsn_matrix T, dmnsn_vector v)
+{
+  /* 9 multiplications, 6 additions */
+  dmnsn_vector r;
+  r.x = T.n[0][0]*v.x + T.n[0][1]*v.y + T.n[0][2]*v.z;
+  r.y = T.n[1][0]*v.x + T.n[1][1]*v.y + T.n[1][2]*v.z;
+  r.z = T.n[2][0]*v.x + T.n[2][1]*v.y + T.n[2][2]*v.z;
+  return r;
+}
+
+/**
+ * Transform a pseudovector by a matrix.
+ * @param[in] Tinv  The inverse of the transformation matrix.
+ * @param[in] v     The pseudovector to transform
+ * @return The transformed pseudovector.
+ */
+DMNSN_INLINE dmnsn_vector
+dmnsn_transform_normal(dmnsn_matrix Tinv, dmnsn_vector v)
+{
+  /* Multiply by the transpose of the inverse
+     (9 multiplications, 6 additions) */
+  dmnsn_vector r;
+  r.x = Tinv.n[0][0]*v.x + Tinv.n[1][0]*v.y + Tinv.n[2][0]*v.z;
+  r.y = Tinv.n[0][1]*v.x + Tinv.n[1][1]*v.y + Tinv.n[2][1]*v.z;
+  r.z = Tinv.n[0][2]*v.x + Tinv.n[1][2]*v.y + Tinv.n[2][2]*v.z;
+  return r;
+}
+
+/**
+ * Transform a ray by a matrix.
+ * \f$ n' = T(l.\vec{x_0} + l.\vec{n}) - T(l.\vec{x_0}) \f$,
+ * \f$ \vec{x_0}' = T(l.\vec{x_0}) \f$
+ */
+DMNSN_INLINE dmnsn_ray
+dmnsn_transform_ray(dmnsn_matrix T, dmnsn_ray l)
+{
+  /* 18 multiplications, 15 additions */
+  dmnsn_ray ret;
+  ret.x0 = dmnsn_transform_point(T, l.x0);
+  ret.n  = dmnsn_transform_direction(T, l.n);
+  return ret;
+}
+
+/** Transform a bounding box by a matrix. */
+dmnsn_aabb dmnsn_transform_aabb(dmnsn_matrix T, dmnsn_aabb box);
+
+/** Return whether a matrix contains any NaN components. */
+DMNSN_INLINE bool
+dmnsn_matrix_isnan(dmnsn_matrix m)
+{
+  size_t i, j;
+  for (i = 0; i < 3; ++i) {
+    for (j = 0; j < 4; ++j) {
+      if (dmnsn_isnan(m.n[i][j])) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
diff --git a/libdimension/dimension/math/ray.h b/libdimension/dimension/math/ray.h
new file mode 100644
index 0000000..1dd98f7
--- /dev/null
+++ b/libdimension/dimension/math/ray.h
@@ -0,0 +1,83 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Lines in 3-D space.
+ */
+
+#ifndef DMNSN_MATH_H
+#error "Please include <dimension/math.h> instead of this header directly."
+#endif
+
+/** A line, or ray. */
+typedef struct dmnsn_ray {
+  dmnsn_vector x0; /**< The origin of the ray. */
+  dmnsn_vector n;  /**< The direction of the ray. */
+} dmnsn_ray;
+
+/** A standard format string for rays. */
+#define DMNSN_RAY_FORMAT "(<%g, %g, %g> + t*<%g, %g, %g>)"
+/** The appropriate arguements to printf() a ray. */
+#define DMNSN_RAY_PRINTF(l)                                     \
+  DMNSN_VECTOR_PRINTF((l).x0), DMNSN_VECTOR_PRINTF((l).n)
+
+/**
+ * Construct a new ray.
+ * @param[in] x0  The origin of the ray.
+ * @param[in] n   The direction of the ray.
+ * @return The new ray.
+ */
+DMNSN_INLINE dmnsn_ray
+dmnsn_new_ray(dmnsn_vector x0, dmnsn_vector n)
+{
+  dmnsn_ray l = { x0, n };
+  return l;
+}
+
+/**
+ * Return the point at \p t on a ray.
+ * The point is defined by \f$ l.\vec{x_0} + t \cdot l.\vec{n} \f$
+ */
+DMNSN_INLINE dmnsn_vector
+dmnsn_ray_point(dmnsn_ray l, double t)
+{
+  return dmnsn_vector_add(l.x0, dmnsn_vector_mul(t, l.n));
+}
+
+/** Add epsilon*l.n to l.x0, to avoid self-intersections. */
+DMNSN_INLINE dmnsn_ray
+dmnsn_ray_add_epsilon(dmnsn_ray l)
+{
+  return dmnsn_new_ray(
+    dmnsn_vector_add(
+      l.x0,
+      dmnsn_vector_mul(1.0e3*dmnsn_epsilon, l.n)
+    ),
+    l.n
+  );
+}
+
+/** Return whether a ray contains any NaN entries. */
+DMNSN_INLINE bool
+dmnsn_ray_isnan(dmnsn_ray l)
+{
+  return dmnsn_vector_isnan(l.x0) || dmnsn_vector_isnan(l.n);
+}
diff --git a/libdimension/dimension/math/scalar.h b/libdimension/dimension/math/scalar.h
new file mode 100644
index 0000000..3887c14
--- /dev/null
+++ b/libdimension/dimension/math/scalar.h
@@ -0,0 +1,103 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Mathematical functions of one variable.
+ */
+
+#ifndef DMNSN_MATH_H
+#error "Please include <dimension/math.h> instead of this header directly."
+#endif
+
+#include <math.h>
+#include <stdbool.h>
+
+/** The smallest value considered non-zero by some numerical algorithms. */
+#define dmnsn_epsilon 1.0e-10
+
+/**
+ * @def DMNSN_INFINITY
+ * Expands to floating-point infinity.
+ */
+#if defined(INFINITY) || DMNSN_C99
+  #define DMNSN_INFINITY INFINITY
+#else
+  #define DMNSN_INFINITY HUGE_VAL
+#endif
+
+/** Find the minimum of two values. */
+DMNSN_INLINE double
+dmnsn_min(double a, double b)
+{
+  return a < b ? a : b;
+}
+
+/** Find the maximum of two values. */
+DMNSN_INLINE double
+dmnsn_max(double a, double b)
+{
+  return a > b ? a : b;
+}
+
+/** Clamp a value to an interval. */
+DMNSN_INLINE double
+dmnsn_clamp(double n, double min, double max)
+{
+  return dmnsn_min(dmnsn_max(n, min), max);
+}
+
+/** Convert degrees to radians. */
+DMNSN_INLINE double
+dmnsn_radians(double degrees)
+{
+  return degrees*(atan(1.0)/45.0);
+}
+
+/** Convert radians to degrees. */
+DMNSN_INLINE double
+dmnsn_degrees(double radians)
+{
+  return radians*(45.0/atan(1.0));
+}
+
+/** Signum function: return the sign of a value. */
+DMNSN_INLINE int
+dmnsn_sgn(double n)
+{
+  if (n > 0.0) {
+    return 1;
+  } else if (n < 0.0) {
+    return -1;
+  } else {
+    return 0;
+  }
+}
+
+/** Return whether a value is NaN. */
+DMNSN_INLINE bool
+dmnsn_isnan(double n)
+{
+#if DMNSN_C99
+  return isnan(n);
+#else
+  return n != n;
+#endif
+}
diff --git a/libdimension/dimension/math/vector.h b/libdimension/dimension/math/vector.h
new file mode 100644
index 0000000..8eacee9
--- /dev/null
+++ b/libdimension/dimension/math/vector.h
@@ -0,0 +1,183 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Vectors in 3-D space.
+ */
+
+#ifndef DMNSN_MATH_H
+#error "Please include <dimension/math.h> instead of this header directly."
+#endif
+
+#include <math.h>
+#include <stdbool.h>
+
+/** A vector in 3 dimensions. */
+typedef struct dmnsn_vector {
+  double x; /**< The x component. */
+  double y; /**< The y component. */
+  double z; /**< The z component. */
+} dmnsn_vector;
+
+/** A standard format string for vectors. */
+#define DMNSN_VECTOR_FORMAT "<%g, %g, %g>"
+/** The appropriate arguements to printf() a vector. */
+#define DMNSN_VECTOR_PRINTF(v) (v).x, (v).y, (v).z
+
+/* Constants */
+
+/** The zero vector. */
+static const dmnsn_vector dmnsn_zero = { 0.0, 0.0, 0.0 };
+/** The x vector. */
+static const dmnsn_vector dmnsn_x = { 1.0, 0.0, 0.0 };
+/** The y vector. */
+static const dmnsn_vector dmnsn_y = { 0.0, 1.0, 0.0 };
+/** The z vector. */
+static const dmnsn_vector dmnsn_z = { 0.0, 0.0, 1.0 };
+
+/* Shorthand for vector construction */
+
+/** Construct a new vector. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_new_vector(double x, double y, double z)
+{
+  dmnsn_vector v = { x, y, z };
+  return v;
+}
+
+/* Vector arithmetic */
+
+/** Negate a vector. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_negate(dmnsn_vector rhs)
+{
+  /* 3 negations */
+  dmnsn_vector v = { -rhs.x, -rhs.y, -rhs.z };
+  return v;
+}
+
+/** Add two vectors. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_add(dmnsn_vector lhs, dmnsn_vector rhs)
+{
+  /* 3 additions */
+  dmnsn_vector v = { lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z };
+  return v;
+}
+
+/** Subtract two vectors. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_sub(dmnsn_vector lhs, dmnsn_vector rhs)
+{
+  /* 3 additions */
+  dmnsn_vector v = { lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z };
+  return v;
+}
+
+/** Multiply a vector by a scalar. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_mul(double lhs, dmnsn_vector rhs)
+{
+  /* 3 multiplications */
+  dmnsn_vector v = { lhs*rhs.x, lhs*rhs.y, lhs*rhs.z };
+  return v;
+}
+
+/** Divide a vector by a scalar. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_div(dmnsn_vector lhs, double rhs)
+{
+  /* 3 divisions */
+  dmnsn_vector v = { lhs.x/rhs, lhs.y/rhs, lhs.z/rhs };
+  return v;
+}
+
+/** Return the dot product of two vectors. */
+DMNSN_INLINE double
+dmnsn_vector_dot(dmnsn_vector lhs, dmnsn_vector rhs)
+{
+  /* 3 multiplications, 2 additions */
+  return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z;
+}
+
+/** Return the cross product of two vectors. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_cross(dmnsn_vector lhs, dmnsn_vector rhs)
+{
+  /* 6 multiplications, 3 additions */
+  dmnsn_vector v = { lhs.y*rhs.z - lhs.z*rhs.y,
+                     lhs.z*rhs.x - lhs.x*rhs.z,
+                     lhs.x*rhs.y - lhs.y*rhs.x };
+  return v;
+}
+
+/** Return the projection of \p u onto \p d. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_proj(dmnsn_vector u, dmnsn_vector d)
+{
+  /* 1 division, 9 multiplications, 4 additions */
+  return dmnsn_vector_mul(dmnsn_vector_dot(u, d)/dmnsn_vector_dot(d, d), d);
+}
+
+/** Return the magnitude of a vector. */
+DMNSN_INLINE double
+dmnsn_vector_norm(dmnsn_vector n)
+{
+  /* 1 sqrt, 3 multiplications, 2 additions */
+  return sqrt(dmnsn_vector_dot(n, n));
+}
+
+/** Return the direction of a vector. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_normalized(dmnsn_vector n)
+{
+  /* 1 sqrt, 3 divisions, 3 multiplications, 2 additions */
+  return dmnsn_vector_div(n, dmnsn_vector_norm(n));
+}
+
+/** Return the component-wise minimum of two vectors. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_min(dmnsn_vector a, dmnsn_vector b)
+{
+  return dmnsn_new_vector(
+    dmnsn_min(a.x, b.x),
+    dmnsn_min(a.y, b.y),
+    dmnsn_min(a.z, b.z)
+  );
+}
+
+/** Return the component-wise maximum of two vectors. */
+DMNSN_INLINE dmnsn_vector
+dmnsn_vector_max(dmnsn_vector a, dmnsn_vector b)
+{
+  return dmnsn_new_vector(
+    dmnsn_max(a.x, b.x),
+    dmnsn_max(a.y, b.y),
+    dmnsn_max(a.z, b.z)
+  );
+}
+
+/** Return whether a vector contains any NaN components. */
+DMNSN_INLINE bool
+dmnsn_vector_isnan(dmnsn_vector v)
+{
+  return dmnsn_isnan(v.x) || dmnsn_isnan(v.y) || dmnsn_isnan(v.z);
+}
diff --git a/libdimension/dimension/model.h b/libdimension/dimension/model.h
new file mode 100644
index 0000000..5367729
--- /dev/null
+++ b/libdimension/dimension/model.h
@@ -0,0 +1,59 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Scene modeling.
+ */
+
+#ifndef DMNSN_MODEL_H
+#define DMNSN_MODEL_H
+
+#include "dimension/base.h"
+#include "dimension/color.h"
+#include "dimension/canvas.h"
+#include "dimension/pattern.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <dimension/platform.h>
+
+#include <dimension/model/pigment.h>
+#include <dimension/model/pigments.h>
+#include <dimension/model/finish.h>
+#include <dimension/model/finishes.h>
+#include <dimension/model/texture.h>
+#include <dimension/model/interior.h>
+#include <dimension/model/object.h>
+#include <dimension/model/objects.h>
+#include <dimension/model/csg.h>
+#include <dimension/model/light.h>
+#include <dimension/model/lights.h>
+#include <dimension/model/camera.h>
+#include <dimension/model/cameras.h>
+#include <dimension/model/scene.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DMNSN_MODEL_H */
diff --git a/libdimension/dimension/model/camera.h b/libdimension/dimension/model/camera.h
new file mode 100644
index 0000000..37f80b9
--- /dev/null
+++ b/libdimension/dimension/model/camera.h
@@ -0,0 +1,66 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Cameras.
+ */
+
+/* Forward-declare dmnsn_camera */
+typedef struct dmnsn_camera dmnsn_camera;
+
+/**
+ * Camera ray callback.
+ * @param[in] camera  The camera itself.
+ * @param[in] x       The x coordinate of the pixel (in [0, 1]).
+ * @param[in] y       The y coordinate of the pixel (in [0, 1]).
+ * @return The ray through (\p x, \p y).
+ */
+typedef dmnsn_ray dmnsn_camera_ray_fn(const dmnsn_camera *camera, double x, double y);
+
+/** A camera. */
+struct dmnsn_camera {
+  /* Callback functions */
+  dmnsn_camera_ray_fn *ray_fn; /**< Camera ray callback. */
+
+  dmnsn_matrix trans; /**< Transformation matrix. */
+};
+
+/**
+ * Create a dummy camera.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return The allocated camera.
+ */
+dmnsn_camera *dmnsn_new_camera(dmnsn_pool *pool);
+
+/**
+ * Initialize a dmnsn_camera field.
+ * @param[out] camera  The camera to initialize.
+ */
+void dmnsn_init_camera(dmnsn_camera *camera);
+
+/**
+ * Invoke the camera ray callback, then correctly transform the ray.
+ * @param[in] camera  The camera itself.
+ * @param[in] x       The x coordinate of the pixel (in [0, 1]).
+ * @param[in] y       The y coordinate of the pixel (in [0, 1]).
+ * @return The ray through (\p x, \p y).
+ */
+dmnsn_ray dmnsn_camera_ray(const dmnsn_camera *camera, double x, double y);
diff --git a/libdimension/dimension/model/cameras.h b/libdimension/dimension/model/cameras.h
new file mode 100644
index 0000000..9ef2646
--- /dev/null
+++ b/libdimension/dimension/model/cameras.h
@@ -0,0 +1,34 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pre-defined camera types.
+ */
+
+/**
+ * A perspective camera.  The camera is located at the origin, looking at
+ * (0, 0, 1).  The feild of view is the section of the plane z = 1 from
+ * (-0.5, -0.5) to (0.5, 0.5).  Rays are transformed by the camera's
+ * transformation matrix.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return A perspective camera.
+ */
+dmnsn_camera *dmnsn_new_perspective_camera(dmnsn_pool *pool);
diff --git a/libdimension/dimension/model/csg.h b/libdimension/dimension/model/csg.h
new file mode 100644
index 0000000..b2ce83f
--- /dev/null
+++ b/libdimension/dimension/model/csg.h
@@ -0,0 +1,59 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Constructive solid geometry
+ */
+
+/**
+ * CSG union.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] objects  The objects from which to compose the union.
+ * @return A union of the objects in \p objects.
+ */
+dmnsn_object *dmnsn_new_csg_union(dmnsn_pool *pool, dmnsn_array *objects);
+
+/**
+ * CSG intersection.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] A  The first object.
+ * @param[in,out] B  The second object.
+ * @return The intersection of \p A and \p B.
+ */
+dmnsn_object *dmnsn_new_csg_intersection(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B);
+
+/**
+ * CSG intersection.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] A  The outer object.
+ * @param[in,out] B  The inner object.
+ * @return The difference between \p A and \p B.
+ */
+dmnsn_object *dmnsn_new_csg_difference(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B);
+
+/**
+ * CSG Merge.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] A  The first object.
+ * @param[in,out] B  The second object.
+ * @return The merge of \p A and \p B.
+ */
+dmnsn_object *dmnsn_new_csg_merge(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B);
diff --git a/libdimension/dimension/model/finish.h b/libdimension/dimension/model/finish.h
new file mode 100644
index 0000000..d975877
--- /dev/null
+++ b/libdimension/dimension/model/finish.h
@@ -0,0 +1,141 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Object finishes.
+ */
+
+/* Ambient component */
+
+/** Ambient finish component. */
+typedef struct dmnsn_ambient {
+  dmnsn_color ambient; /**< Ambient light. */
+} dmnsn_ambient;
+
+/** Allocate an ambient component. */
+dmnsn_ambient *dmnsn_new_ambient(dmnsn_pool *pool, dmnsn_color ambient);
+
+/* Diffuse component */
+
+typedef struct dmnsn_diffuse dmnsn_diffuse;
+
+/**
+ * Diffuse reflection callback.
+ * @param[in] diffuse  The diffuse object itself.
+ * @param[in] light    The color of the light illuminating the object.
+ * @param[in] color    The pigment of the object.
+ * @param[in] ray      The direction of the light source.
+ * @param[in] normal   The normal vector of the surface.
+ * @return The diffuse reflection component of the object's color.
+ */
+typedef dmnsn_color dmnsn_diffuse_fn(const dmnsn_diffuse *diffuse,
+                                     dmnsn_color light, dmnsn_color color,
+                                     dmnsn_vector ray, dmnsn_vector normal);
+
+/** Diffuse finish component. */
+struct dmnsn_diffuse {
+  dmnsn_diffuse_fn *diffuse_fn; /**< Diffuse callback. */
+};
+
+/** Allocate a dummy diffuse component. */
+dmnsn_diffuse *dmnsn_new_diffuse(dmnsn_pool *pool);
+/** Initialize a dmnsn_diffuse field. */
+void dmnsn_init_diffuse(dmnsn_diffuse *diffuse);
+
+/* Specular component */
+
+typedef struct dmnsn_specular dmnsn_specular;
+
+/**
+ * Specular highlight callback.
+ * @param[in] specular  The specular object itself.
+ * @param[in] light     The color of the light illuminating the object.
+ * @param[in] color     The pigment of the object.
+ * @param[in] ray       The direction of the light source.
+ * @param[in] normal    The normal vector of the surface.
+ * @param[in] viewer    The direction of the viewer.
+ * @return The specular reflection component of the object's color.
+ */
+typedef dmnsn_color dmnsn_specular_fn(const dmnsn_specular *specular,
+                                      dmnsn_color light, dmnsn_color color,
+                                      dmnsn_vector ray, dmnsn_vector normal,
+                                      dmnsn_vector viewer);
+
+/** Specular finish component. */
+struct dmnsn_specular {
+  dmnsn_specular_fn *specular_fn; /**< Specular callback. */
+};
+
+/** Allocate a dummy specular component. */
+dmnsn_specular *dmnsn_new_specular(dmnsn_pool *pool);
+/** Initialize a dmnsn_specular field. */
+void dmnsn_init_specular(dmnsn_specular *specular);
+
+/* Reflection component */
+
+typedef struct dmnsn_reflection dmnsn_reflection;
+
+/**
+ * Reflected light callback.
+ * @param[in] reflection  The reflection object itself.
+ * @param[in] reflect     The color of the reflected ray.
+ * @param[in] color       The pigment of the object.
+ * @param[in] ray         The direction of the reflected ray.
+ * @param[in] normal      The normal vector of the surface.
+ * @return The contribution of the reflected ray to the object's color.
+ */
+typedef dmnsn_color dmnsn_reflection_fn(const dmnsn_reflection *reflection,
+                                        dmnsn_color reflect, dmnsn_color color,
+                                        dmnsn_vector ray, dmnsn_vector normal);
+
+/** The reflection component. */
+struct dmnsn_reflection {
+  dmnsn_reflection_fn *reflection_fn; /**< Reflection callback. */
+};
+
+/** Allocate a dummy reflection component. */
+dmnsn_reflection *dmnsn_new_reflection(dmnsn_pool *pool);
+/** Initialize a dmnsn_reflection field. */
+void dmnsn_init_reflection(dmnsn_reflection *reflection);
+
+/* Entire finishes */
+
+/** A finish. */
+typedef struct dmnsn_finish {
+  dmnsn_ambient *ambient; /**< Ambient component. */
+  dmnsn_diffuse *diffuse; /**< Diffuse component. */
+  dmnsn_specular *specular; /**< Specular component. */
+  dmnsn_reflection *reflection; /**< Reflection component. */
+} dmnsn_finish;
+
+/**
+ * Create a new blank finish.
+ * @return The new finish.
+ */
+dmnsn_finish dmnsn_new_finish(void);
+
+/**
+ * Fill missing finish properties from a default finish.
+ * @param[in]     default_finish  The default finish.
+ * @param[in,out] finish          The finish to fill.
+ */
+void dmnsn_finish_cascade(const dmnsn_finish *default_finish,
+                          dmnsn_finish *finish);
diff --git a/libdimension/dimension/model/finishes.h b/libdimension/dimension/model/finishes.h
new file mode 100644
index 0000000..e1f7b44
--- /dev/null
+++ b/libdimension/dimension/model/finishes.h
@@ -0,0 +1,51 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pre-defined finishes.
+ */
+
+/**
+ * Regular diffuse finish.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] diffuse  The diffuse reflection coefficient.
+ * @return A diffuse finish component.
+ */
+dmnsn_diffuse *dmnsn_new_lambertian(dmnsn_pool *pool, double diffuse);
+
+/**
+ * A phong specular highlight.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] specular  The specular reflection coefficient.
+ * @param[in] exp  The exponent (roughly the highlight size).
+ * @return A phong specular finish component.
+ */
+dmnsn_specular *dmnsn_new_phong(dmnsn_pool *pool, double specular, double exp);
+
+/**
+ * Specular (mirror) reflection.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] min  Reflection at paralell angles.
+ * @param[in] max  Reflection at perpendicular angles (often == \p min).
+ * @param[in] falloff  Degree of exponential falloff (usually 1).
+ * @return A reflective finish component.
+ */
+dmnsn_reflection *dmnsn_new_basic_reflection(dmnsn_pool *pool, dmnsn_color min, dmnsn_color max, double falloff);
diff --git a/libdimension/dimension/model/interior.h b/libdimension/dimension/model/interior.h
new file mode 100644
index 0000000..0ff697d
--- /dev/null
+++ b/libdimension/dimension/model/interior.h
@@ -0,0 +1,44 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Object interiors.
+ */
+
+/** An interior. */
+typedef struct dmnsn_interior {
+  double ior; /**< Refractive index. */
+} dmnsn_interior;
+
+/**
+ * Create an interior object.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return The new interior.
+ */
+dmnsn_interior *dmnsn_new_interior(dmnsn_pool *pool);
+
+/**
+ * Fill missing interior properties from a default interior.
+ * @param[in]     default_interior  The default interior.
+ * @param[in,out] interiorp         A pointer to the interior to fill.
+ */
+void dmnsn_interior_cascade(dmnsn_interior *default_interior,
+                            dmnsn_interior **interiorp);
diff --git a/libdimension/dimension/model/light.h b/libdimension/dimension/model/light.h
new file mode 100644
index 0000000..218611d
--- /dev/null
+++ b/libdimension/dimension/model/light.h
@@ -0,0 +1,76 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Lights.
+ */
+
+#include <stdbool.h>
+
+/* Forward-declar dmnsn_light */
+typedef struct dmnsn_light dmnsn_light;
+
+/**
+ * Light direction callback.
+ * @param[in] light  The light itself.
+ * @param[in] v      The point to illuminate.
+ * @return The direction of light rays pointing from \p v
+ */
+typedef dmnsn_vector dmnsn_light_direction_fn(const dmnsn_light *light,
+                                              dmnsn_vector v);
+
+/**
+ * Light illumination callback.
+ * @param[in] light  The light itself.
+ * @param[in] v      The point to illuminate.
+ * @return The color of the light at \p v.
+ */
+typedef dmnsn_color dmnsn_light_illumination_fn(const dmnsn_light *light,
+                                                dmnsn_vector v);
+
+/**
+ * Light shadow callback.
+ * @param[in] light  The light itself.
+ * @param[in] t      The line index of the closest shadow ray intersection.
+ * @return Whether the point is in shadow.
+ */
+typedef bool dmnsn_light_shadow_fn(const dmnsn_light *light, double t);
+
+/** A light. */
+struct dmnsn_light {
+  /* Callbacks */
+  dmnsn_light_direction_fn *direction_fn; /**< Direction callback. */
+  dmnsn_light_illumination_fn *illumination_fn; /**< Illumination callback. */
+  dmnsn_light_shadow_fn *shadow_fn; /**< Shadow callback. */
+};
+
+/**
+ * Create a dummy light.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return The allocated light.
+ */
+dmnsn_light *dmnsn_new_light(dmnsn_pool *pool);
+
+/**
+ * Initialize a dmnsn_light field.
+ * @param[out] light  The light to initialize.
+ */
+void dmnsn_init_light(dmnsn_light *light);
diff --git a/libdimension/dimension/model/lights.h b/libdimension/dimension/model/lights.h
new file mode 100644
index 0000000..e7de4cc
--- /dev/null
+++ b/libdimension/dimension/model/lights.h
@@ -0,0 +1,33 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pre-defined light types.
+ */
+
+/**
+ * A point light.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] x0     The origin of the light.
+ * @param[in] color  The color of the light.
+ * @return A point light.
+ */
+dmnsn_light *dmnsn_new_point_light(dmnsn_pool *pool, dmnsn_vector x0, dmnsn_color color);
diff --git a/libdimension/dimension/model/object.h b/libdimension/dimension/model/object.h
new file mode 100644
index 0000000..287d838
--- /dev/null
+++ b/libdimension/dimension/model/object.h
@@ -0,0 +1,165 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Objects.
+ */
+
+#include <stdbool.h>
+
+/* Forward-declare dmnsn_object */
+typedef struct dmnsn_object dmnsn_object;
+
+/** A type to represent a ray-object intersection. */
+typedef struct dmnsn_intersection {
+  dmnsn_ray ray; /**< The ray that intersected. */
+  double t;      /**< The ray index that intersected. */
+
+  /** The surface normal at the intersection point. */
+  dmnsn_vector normal;
+
+  /** The object of intersection. */
+  const dmnsn_object *object;
+} dmnsn_intersection;
+
+/**
+ * Ray-object intersection callback.
+ * @param[in]  object        The object to test.
+ * @param[in]  ray           The ray to test.
+ * @param[out] intersection  Where to store the intersection details of the
+ *                           closest (if any) intersection.
+ * @return Whether \p ray intersected \p object.
+ */
+typedef bool dmnsn_object_intersection_fn(const dmnsn_object *object, dmnsn_ray ray, dmnsn_intersection *intersection);
+
+/**
+ * Object inside callback.
+ * @param[in] object  The object to test.
+ * @param[in] point   The point to test.
+ * @return Whether \p point is inside \p object.
+ */
+typedef bool dmnsn_object_inside_fn(const dmnsn_object *object, dmnsn_vector point);
+
+/**
+ * Object bounding callback.
+ * @param[in,out] object  The object to bound.
+ * @param[in,out] trans  The effective transformation for the object.
+ * @return A world-coordinate bounding box computed for the object.
+ */
+typedef dmnsn_aabb dmnsn_object_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans);
+
+/**
+ * Object precomputation callback.
+ * @param[in,out] object  The object to precompute.
+ */
+typedef void dmnsn_object_precompute_fn(dmnsn_object *object);
+
+/** Object callbacks. */
+typedef struct dmnsn_object_vtable {
+  dmnsn_object_intersection_fn *intersection_fn; /**< Intersection callback. */
+  dmnsn_object_inside_fn *inside_fn; /**< Inside callback. */
+  dmnsn_object_bounding_fn *bounding_fn; /**< Bounding callback. */
+  dmnsn_object_precompute_fn *precompute_fn; /**< Precomputation callback. */
+} dmnsn_object_vtable;
+
+/** An object. */
+struct dmnsn_object {
+  const dmnsn_object_vtable *vtable; /**< Callbacks. */
+
+  dmnsn_texture *texture;  /**< Surface properties. */
+  dmnsn_interior *interior; /**< Interior properties. */
+
+  dmnsn_matrix trans; /**< Transformation matrix. */
+  dmnsn_matrix intrinsic_trans; /**< Transformations intrinsic to the object. */
+
+  dmnsn_array *children; /**< Child objects. */
+  bool split_children;   /**< Whether the child objects can be split. */
+
+  /* Precomputed values */
+  bool precomputed; /**< @internal Whether the object is precomputed yet. */
+  dmnsn_matrix trans_inv; /**< Inverse of the transformation matrix. */
+  dmnsn_matrix pigment_trans; /**< Inverse transformation for the texture. */
+  dmnsn_aabb aabb; /**< Bounding box in world coordinates. */
+};
+
+/**
+ * Allocate a dummy object.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return The allocated object.
+ */
+dmnsn_object *dmnsn_new_object(dmnsn_pool *pool);
+
+/**
+ * Initialize a dmnsn_object field.
+ * @param[out] object  The object to initialize.
+ */
+void dmnsn_init_object(dmnsn_object *object);
+
+/**
+ * Precompute values for an object and its children.
+ * @param[in,out] object  The object to precompute.
+ */
+void dmnsn_object_precompute(dmnsn_object *object);
+
+/**
+ * Appropriately transform a ray, then test for an intersection.
+ * @param[in]  object        The object to test.
+ * @param[in]  ray           The ray to test.
+ * @param[out] intersection  Where to store the intersection details.
+ * @return Whether there was an intersection.
+ */
+DMNSN_INLINE bool
+dmnsn_object_intersection(const dmnsn_object *object, dmnsn_ray ray,
+                          dmnsn_intersection *intersection)
+{
+  dmnsn_ray ray_trans = dmnsn_transform_ray(object->trans_inv, ray);
+  intersection->object = NULL;
+  if (object->vtable->intersection_fn(object, ray_trans, intersection)) {
+    /* Get us back into world coordinates */
+    intersection->ray = ray;
+    intersection->normal = dmnsn_vector_normalized(
+      dmnsn_transform_normal(object->trans_inv, intersection->normal)
+    );
+    if (!intersection->object) {
+      intersection->object = object;
+    }
+
+    dmnsn_assert(!dmnsn_isnan(intersection->t), "Intersection point is NaN.");
+    dmnsn_assert(!dmnsn_vector_isnan(intersection->normal), "Intersection normal is NaN.");
+
+    return true;
+  } else {
+    return false;
+  }
+}
+
+/**
+ * Appropriately transform a point, then test for containment.
+ * @param[in] object  The object to test.
+ * @param[in] point   The point to test.
+ * @return Whether \p point was inside \p object.
+ */
+DMNSN_INLINE bool
+dmnsn_object_inside(const dmnsn_object *object, dmnsn_vector point)
+{
+  point = dmnsn_transform_point(object->trans_inv, point);
+  return object->vtable->inside_fn(object, point);
+}
diff --git a/libdimension/dimension/model/objects.h b/libdimension/dimension/model/objects.h
new file mode 100644
index 0000000..2865d82
--- /dev/null
+++ b/libdimension/dimension/model/objects.h
@@ -0,0 +1,103 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pre-defined objects.
+ */
+
+#include <stdbool.h>
+
+/**
+ * A flat triangle.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] vertices  The corners of the triangle.
+ * @return A triangle.
+ */
+dmnsn_object *dmnsn_new_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3]);
+
+/**
+ * A triangle, with normals interpolated between the points.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] vertices  The corners of the triangle.
+ * @param[in] normals  The normals at each corner.
+ * @return A smooth triangle.
+ */
+dmnsn_object *dmnsn_new_smooth_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3], dmnsn_vector normals[3]);
+
+/**
+ * A triangle fan.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] vertices  The vertices of the fan, starting in the center.
+ * @param[in] nvertices  The number of vertices.
+ * @return A triangle fan.
+ */
+dmnsn_object *dmnsn_new_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], size_t nvertices);
+
+/**
+ * A smooth triangle fan.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] vertices  The vertices of the fan, starting in the center.
+ * @param[in] normals  The normal vector for each vertex.
+ * @param[in] nvertices  The number of vertices.
+ * @return A triangle fan.
+ */
+dmnsn_object *dmnsn_new_smooth_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], dmnsn_vector normals[], size_t nvertices);
+
+/**
+ * A plane.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] normal  The normal vector of the plane.
+ * @return A plane through the origin, with the given normal.
+ */
+dmnsn_object *dmnsn_new_plane(dmnsn_pool *pool, dmnsn_vector normal);
+
+/**
+ * A sphere.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return A sphere of radius 1, centered at the origin.
+ */
+dmnsn_object *dmnsn_new_sphere(dmnsn_pool *pool);
+
+/**
+ * A cube.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return An axis-aligned cube, from (-1, -1, -1) to (1, 1, 1).
+ */
+dmnsn_object *dmnsn_new_cube(dmnsn_pool *pool);
+
+/**
+ * A cylinder/cone.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] r1    The bottom radius.
+ * @param[in] r2    The top radius.
+ * @param[in] open  Whether to render caps.
+ * @return A cone slice, from r = \p r1 at y = -1, to r = \p r2 at y = 1
+ */
+dmnsn_object *dmnsn_new_cone(dmnsn_pool *pool, double r1, double r2, bool open);
+
+/**
+ * A torus.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] major  The major radius.
+ * @param[in] minor  The minor radius.
+ * @return A torus, centered at the origin and lying in the x-z plane.
+ */
+dmnsn_object *dmnsn_new_torus(dmnsn_pool *pool, double major, double minor);
diff --git a/libdimension/dimension/model/pigment.h b/libdimension/dimension/model/pigment.h
new file mode 100644
index 0000000..14d8bae
--- /dev/null
+++ b/libdimension/dimension/model/pigment.h
@@ -0,0 +1,86 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Object pigments.
+ */
+
+/* Forward-declare dmnsn_pigment */
+typedef struct dmnsn_pigment dmnsn_pigment;
+
+/**
+ * Pigment callback.
+ * @param[in] pigment  The pigment itself.
+ * @param[in] v        The point to color.
+ * @return The color of the pigment at \p v.
+ */
+typedef dmnsn_tcolor dmnsn_pigment_fn(const dmnsn_pigment *pigment,
+                                      dmnsn_vector v);
+
+/**
+ * Pigment initializer callback.
+ * @param[in,out] pigment  The pigment to initialize.
+ */
+typedef void dmnsn_pigment_initialize_fn(dmnsn_pigment *pigment);
+
+/** A pigment. */
+struct dmnsn_pigment {
+  dmnsn_pigment_fn *pigment_fn; /**< The pigment callback. */
+  dmnsn_pigment_initialize_fn *initialize_fn; /**< The initializer callback. */
+
+  dmnsn_matrix trans; /**< Transformation matrix. */
+  dmnsn_matrix trans_inv; /**< The inverse of the transformation matrix. */
+
+  /** Quick color -- used for low-quality renders. */
+  dmnsn_tcolor quick_color;
+
+  bool initialized; /** @internal Whether the pigment is initialized. */
+};
+
+/**
+ * Allocate a new dummy pigment.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return The allocated pigment.
+ */
+dmnsn_pigment *dmnsn_new_pigment(dmnsn_pool *pool);
+
+/**
+ * Initialize a dmnsn_pigment field.
+ * @param[out] pigment  The pigment to initialize.
+ */
+void dmnsn_init_pigment(dmnsn_pigment *pigment);
+
+/**
+ * Initialize a pigment.  Pigments should not be used before being initialized,
+ * but should not be modified after being initialized.  Pigments are generally
+ * initialized for you.
+ * @param[in,out] pigment  The pigment to initialize.
+ */
+void dmnsn_pigment_initialize(dmnsn_pigment *pigment);
+
+/**
+ * Evaluate the color of a pigment at a point.
+ * @param[in] pigment  The pigment to evaluate.
+ * @param[in] v        The point to color.
+ * @return The color at \p v.
+ */
+dmnsn_tcolor dmnsn_pigment_evaluate(const dmnsn_pigment *pigment,
+                                    dmnsn_vector v);
diff --git a/libdimension/dimension/model/pigments.h b/libdimension/dimension/model/pigments.h
new file mode 100644
index 0000000..100016d
--- /dev/null
+++ b/libdimension/dimension/model/pigments.h
@@ -0,0 +1,66 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pre-defined pigments.
+ */
+
+/**
+ * A solid color.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] color  The color of the pigment.
+ * @return A pigment with the color \p color everywhere.
+ */
+dmnsn_pigment *dmnsn_new_solid_pigment(dmnsn_pool *pool, dmnsn_tcolor color);
+
+/**
+ * An image map.  The image (regardless of its real dimensions) is projected
+ * on the x-y plane in tesselating unit squares.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] canvas  The canvas holding the image.
+ * @return An image-mapped pigment.
+ */
+dmnsn_pigment *dmnsn_new_canvas_pigment(dmnsn_pool *pool, dmnsn_canvas *canvas);
+
+/**
+ * Pigment map flags.
+ */
+typedef enum dmnsn_pigment_map_flags {
+  DMNSN_PIGMENT_MAP_REGULAR, /**< Calculate linear color gradients. */
+  DMNSN_PIGMENT_MAP_SRGB /**< Calculate sRGB color gradients. */
+} dmnsn_pigment_map_flags;
+
+/**
+ * Construct a pigment map.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return An empty pigment map.
+ */
+dmnsn_map *dmnsn_new_pigment_map(dmnsn_pool *pool);
+
+/**
+ * A pigment-mapped pigment.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in,out] pattern  The pattern of the pigment.
+ * @param[in,out] map  The pigment map to apply to the pattern.
+ * @param[in] flags  Gradient flags
+ * @return A pigment mapping the pattern to other pigments.
+ */
+dmnsn_pigment *dmnsn_new_pigment_map_pigment(dmnsn_pool *pool, dmnsn_pattern *pattern, dmnsn_map *map, dmnsn_pigment_map_flags flags);
diff --git a/libdimension/dimension/model/scene.h b/libdimension/dimension/model/scene.h
new file mode 100644
index 0000000..dd0c1ba
--- /dev/null
+++ b/libdimension/dimension/model/scene.h
@@ -0,0 +1,95 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Entire scenes.
+ */
+
+/** Render quality flags. */
+enum {
+  DMNSN_RENDER_NONE         = 0,      /**< Render nothing. */
+  DMNSN_RENDER_PIGMENT      = 1 << 0, /**< Render pigments. */
+  DMNSN_RENDER_LIGHTS       = 1 << 1, /**< Render lights and shadows. */
+  DMNSN_RENDER_FINISH       = 1 << 2, /**< Render object finishes. */
+  DMNSN_RENDER_TRANSPARENCY = 1 << 3, /**< Render transparency/refraction. */
+  DMNSN_RENDER_REFLECTION   = 1 << 4, /**< Render specular reflection. */
+  DMNSN_RENDER_FULL         = ~DMNSN_RENDER_NONE /**< Render everything. */
+};
+
+/** Render quality. */
+typedef unsigned int dmnsn_quality;
+
+/** An entire scene. */
+typedef struct dmnsn_scene {
+  /* World attributes */
+  dmnsn_pigment *background;        /**< Background pigment. */
+  dmnsn_texture *default_texture;   /**< Default object texture. */
+  dmnsn_interior *default_interior; /**< Default object interior. */
+
+  /** Canvas. */
+  dmnsn_canvas *canvas;
+
+  /* Support for rendering image subregions. */
+  size_t region_x; /**< The x position of the canvas in the broader image. */
+  size_t region_y; /**< The y position of the canvas in the broader image. */
+  size_t outer_width; /**< Width of the broader image. */
+  size_t outer_height; /**< Height of the broader image. */
+
+  /** Objects. */
+  dmnsn_array *objects;
+
+  /** Lights. */
+  dmnsn_array *lights;
+
+  /** Camera. */
+  dmnsn_camera *camera;
+
+  /** Render quality. */
+  dmnsn_quality quality;
+
+  /** Recursion limit. */
+  unsigned int reclimit;
+
+  /** Adaptive depth control bailout. */
+  double adc_bailout;
+
+  /** Number of parallel threads. */
+  unsigned int nthreads;
+
+  /** Timers. */
+  dmnsn_timer bounding_timer;
+  dmnsn_timer render_timer;
+
+  bool initialized; /**< @internal Whether the scene is initialized. */
+} dmnsn_scene;
+
+/**
+ * Create a scene.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return A new empty scene.
+ */
+dmnsn_scene *dmnsn_new_scene(dmnsn_pool *pool);
+
+/**
+ * Initialize a scene.
+ * @param[in,out] scene  The scene to initalize.
+ */
+void dmnsn_scene_initialize(dmnsn_scene *scene);
diff --git a/libdimension/dimension/model/texture.h b/libdimension/dimension/model/texture.h
new file mode 100644
index 0000000..df08a4a
--- /dev/null
+++ b/libdimension/dimension/model/texture.h
@@ -0,0 +1,57 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Object textures.
+ */
+
+/** A complete texture. */
+typedef struct {
+  dmnsn_pigment *pigment; /**< Pigment. */
+  dmnsn_finish finish;    /**< Finish. */
+
+  dmnsn_matrix trans;     /**< Transformation matrix. */
+  dmnsn_matrix trans_inv; /**< The inverse of the transformation matrix. */
+
+  bool initialized; /**< @internal Whether the texture is initialized yet. */
+} dmnsn_texture;
+
+/**
+ * Create a blank texture.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return The new texture.
+ */
+dmnsn_texture *dmnsn_new_texture(dmnsn_pool *pool);
+
+/**
+ * Initialize a texture.  Textures should not be used before being initialized,
+ * but should not be modified after being initialized.  Textures are generally
+ * initialized for you.
+ * @param[in,out] texture  The texture to initialize.
+ */
+void dmnsn_texture_initialize(dmnsn_texture *texture);
+
+/**
+ * Fill missing texture properties from a default texture.
+ * @param[in]     default_texture  The default texture.
+ * @param[in,out] texturep         A pointer to the texture to fill.
+ */
+void dmnsn_texture_cascade(dmnsn_texture *default_texture, dmnsn_texture **texturep);
diff --git a/libdimension/dimension/object.h b/libdimension/dimension/object.h
deleted file mode 100644
index c8e9d86..0000000
--- a/libdimension/dimension/object.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Objects.
- */
-
-#include <stdbool.h>
-
-/* Forward-declare dmnsn_object */
-typedef struct dmnsn_object dmnsn_object;
-
-/** A type to represent a ray-object intersection. */
-typedef struct dmnsn_intersection {
-  dmnsn_line ray; /**< The ray that intersected. */
-  double t;       /**< The line index that intersected. */
-
-  /** The surface normal at the intersection point. */
-  dmnsn_vector normal;
-
-  /** The object of intersection. */
-  const dmnsn_object *object;
-} dmnsn_intersection;
-
-/**
- * Ray-object intersection callback.
- * @param[in]  object        The object to test.
- * @param[in]  line          The line to test.
- * @param[out] intersection  Where to store the intersection details of the
- *                           closest (if any) intersection.
- * @return Whether \p line intersected \p object.
- */
-typedef bool dmnsn_object_intersection_fn(const dmnsn_object *object, dmnsn_line line, dmnsn_intersection *intersection);
-
-/**
- * Object inside callback.
- * @param[in] object  The object to test.
- * @param[in] point   The point to test.
- * @return Whether \p point is inside \p object.
- */
-typedef bool dmnsn_object_inside_fn(const dmnsn_object *object, dmnsn_vector point);
-
-/**
- * Object bounding callback.
- * @param[in,out] object  The object to bound.
- * @param[in,out] trans  The effective transformation for the object.
- */
-typedef dmnsn_bounding_box dmnsn_object_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans);
-
-/**
- * Object precomputation callback.
- * @param[in,out] object  The object to precompute.
- */
-typedef void dmnsn_object_precompute_fn(dmnsn_object *object);
-
-/** Object callbacks. */
-typedef struct dmnsn_object_vtable {
-  dmnsn_object_intersection_fn *intersection_fn; /**< Intersection callback. */
-  dmnsn_object_inside_fn *inside_fn; /**< Inside callback. */
-  dmnsn_object_bounding_fn *bounding_fn; /**< Bounding callback. */
-  dmnsn_object_precompute_fn *precompute_fn; /**< Precomputation callback. */
-} dmnsn_object_vtable;
-
-/** An object. */
-struct dmnsn_object {
-  const dmnsn_object_vtable *vtable; /**< Callbacks. */
-
-  dmnsn_texture *texture;  /**< Surface properties. */
-  dmnsn_interior *interior; /**< Interior properties. */
-
-  dmnsn_matrix trans; /**< Transformation matrix. */
-  dmnsn_matrix intrinsic_trans; /**< Transformations intrinsic to the object. */
-
-  dmnsn_array *children; /**< Child objects. */
-  bool split_children;   /**< Whether the child objects can be split. */
-
-  /* Precomputed values */
-  bool precomputed; /**< @internal Whether the object is precomputed yet. */
-  dmnsn_matrix trans_inv; /**< Inverse of the transformation matrix. */
-  dmnsn_matrix pigment_trans; /**< Inverse transformation for the texture. */
-  dmnsn_bounding_box bounding_box; /**< Bounding box in world coordinates. */
-};
-
-/**
- * Allocate a dummy object.
- * @param[in] pool  The memory pool to allocate from.
- * @return The allocated object.
- */
-dmnsn_object *dmnsn_new_object(dmnsn_pool *pool);
-
-/**
- * Initialize a dmnsn_object field.
- * @param[out] object  The object to initialize.
- */
-void dmnsn_init_object(dmnsn_object *object);
-
-/**
- * Precompute values for an object and its children.
- * @param[in,out] object  The object to precompute.
- */
-void dmnsn_object_precompute(dmnsn_object *object);
-
-/**
- * Appropriately transform a ray, then test for an intersection.
- * @param[in]  object        The object to test.
- * @param[in]  line          The ray to test.
- * @param[out] intersection  Where to store the intersection details.
- * @return Whether there was an intersection.
- */
-DMNSN_INLINE bool
-dmnsn_object_intersection(const dmnsn_object *object, dmnsn_line line,
-                          dmnsn_intersection *intersection)
-{
-  dmnsn_line line_trans = dmnsn_transform_line(object->trans_inv, line);
-  intersection->object = NULL;
-  if (object->vtable->intersection_fn(object, line_trans, intersection)) {
-    /* Get us back into world coordinates */
-    intersection->ray = line;
-    intersection->normal = dmnsn_vector_normalized(
-      dmnsn_transform_normal(object->trans_inv, intersection->normal)
-    );
-    if (!intersection->object) {
-      intersection->object = object;
-    }
-
-    dmnsn_assert(!dmnsn_isnan(intersection->t), "Intersection point is NaN.");
-    dmnsn_assert(!dmnsn_vector_isnan(intersection->normal), "Intersection normal is NaN.");
-
-    return true;
-  } else {
-    return false;
-  }
-}
-
-/**
- * Appropriately transform a point, then test for containment.
- * @param[in] object  The object to test.
- * @param[in] point   The point to test.
- * @return Whether \p point was inside \p object.
- */
-DMNSN_INLINE bool
-dmnsn_object_inside(const dmnsn_object *object, dmnsn_vector point)
-{
-  point = dmnsn_transform_point(object->trans_inv, point);
-  return object->vtable->inside_fn(object, point);
-}
diff --git a/libdimension/dimension/objects.h b/libdimension/dimension/objects.h
deleted file mode 100644
index 2865d82..0000000
--- a/libdimension/dimension/objects.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pre-defined objects.
- */
-
-#include <stdbool.h>
-
-/**
- * A flat triangle.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] vertices  The corners of the triangle.
- * @return A triangle.
- */
-dmnsn_object *dmnsn_new_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3]);
-
-/**
- * A triangle, with normals interpolated between the points.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] vertices  The corners of the triangle.
- * @param[in] normals  The normals at each corner.
- * @return A smooth triangle.
- */
-dmnsn_object *dmnsn_new_smooth_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3], dmnsn_vector normals[3]);
-
-/**
- * A triangle fan.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] vertices  The vertices of the fan, starting in the center.
- * @param[in] nvertices  The number of vertices.
- * @return A triangle fan.
- */
-dmnsn_object *dmnsn_new_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], size_t nvertices);
-
-/**
- * A smooth triangle fan.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] vertices  The vertices of the fan, starting in the center.
- * @param[in] normals  The normal vector for each vertex.
- * @param[in] nvertices  The number of vertices.
- * @return A triangle fan.
- */
-dmnsn_object *dmnsn_new_smooth_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], dmnsn_vector normals[], size_t nvertices);
-
-/**
- * A plane.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] normal  The normal vector of the plane.
- * @return A plane through the origin, with the given normal.
- */
-dmnsn_object *dmnsn_new_plane(dmnsn_pool *pool, dmnsn_vector normal);
-
-/**
- * A sphere.
- * @param[in] pool  The memory pool to allocate from.
- * @return A sphere of radius 1, centered at the origin.
- */
-dmnsn_object *dmnsn_new_sphere(dmnsn_pool *pool);
-
-/**
- * A cube.
- * @param[in] pool  The memory pool to allocate from.
- * @return An axis-aligned cube, from (-1, -1, -1) to (1, 1, 1).
- */
-dmnsn_object *dmnsn_new_cube(dmnsn_pool *pool);
-
-/**
- * A cylinder/cone.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] r1    The bottom radius.
- * @param[in] r2    The top radius.
- * @param[in] open  Whether to render caps.
- * @return A cone slice, from r = \p r1 at y = -1, to r = \p r2 at y = 1
- */
-dmnsn_object *dmnsn_new_cone(dmnsn_pool *pool, double r1, double r2, bool open);
-
-/**
- * A torus.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] major  The major radius.
- * @param[in] minor  The minor radius.
- * @return A torus, centered at the origin and lying in the x-z plane.
- */
-dmnsn_object *dmnsn_new_torus(dmnsn_pool *pool, double major, double minor);
diff --git a/libdimension/dimension/pattern.h b/libdimension/dimension/pattern.h
index 2caed07..17a43a9 100644
--- a/libdimension/dimension/pattern.h
+++ b/libdimension/dimension/pattern.h
@@ -1,5 +1,5 @@
 /*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
  *                                                                       *
  * This file is part of The Dimension Library.                           *
  *                                                                       *
@@ -20,43 +20,25 @@
 
 /**
  * @file
- * Patterns.  Patterns are functions which map vectors to scalars, which are
- * used for pigments and normals.
+ * Patterns.
  */
 
-/* Forward-declare dmnsn_pattern */
-typedef struct dmnsn_pattern dmnsn_pattern;
+#ifndef DMNSN_PATTERN_H
+#define DMNSN_PATTERN_H
 
-/**
- * Pattern callback.
- * @param[in] pattern  The pattern itself.
- * @param[in] v        The point at which to evaluate the pattern.
- * @return The value of the pattern at \p v.
- */
-typedef double dmnsn_pattern_fn(const dmnsn_pattern *pattern, dmnsn_vector v);
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-/** A pattern. */
-struct dmnsn_pattern {
-  dmnsn_pattern_fn *pattern_fn; /**< The pattern callback. */
-};
+#include <dimension/base.h>
+#include <dimension/math.h>
 
-/**
- * Allocate a dummy pattern.
- * @param[in] pool  The memory pool to allocate from.
- * @return A pattern with no callbacks set.
- */
-dmnsn_pattern *dmnsn_new_pattern(dmnsn_pool *pool);
+#include <dimension/pattern/pattern.h>
+#include <dimension/pattern/patterns.h>
+#include <dimension/pattern/map.h>
 
-/**
- * Initialize a dmnsn_pattern field.
- * @param[out] pattern  The pattern to initialize.
- */
-void dmnsn_init_pattern(dmnsn_pattern *pattern);
+#ifdef __cplusplus
+}
+#endif
 
-/**
- * Invoke the pattern callback.
- * @param[in] pattern  The pattern to evaluate.
- * @param[in] v        The point to get the pattern value for.
- * @return The value of the pattern at \p v.
- */
-double dmnsn_pattern_value(const dmnsn_pattern *pattern, dmnsn_vector v);
+#endif /* DMNSN_PATTERN_H */
diff --git a/libdimension/dimension/pattern/map.h b/libdimension/dimension/pattern/map.h
new file mode 100644
index 0000000..7229a24
--- /dev/null
+++ b/libdimension/dimension/pattern/map.h
@@ -0,0 +1,68 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Generic maps (backend for pigment_maps, etc.).
+ */
+
+/** A map. */
+typedef struct dmnsn_map dmnsn_map;
+
+/**
+ * Create an empty map.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] size  The size of the objects to store in the map.
+ * @return A map with no entries.
+ */
+dmnsn_map *dmnsn_new_map(dmnsn_pool *pool, size_t size);
+
+/**
+ * Add an entry (a scalar-object pair) to a map.
+ * @param[in,out] map  The map to add to.
+ * @param[in]     n    The index of the entry.
+ * @param[in]     obj  The value of the entry.
+ */
+void dmnsn_map_add_entry(dmnsn_map *map, double n, const void *obj);
+
+/**
+ * Return the number of entries in a map.
+ * @param[in] map  The map to measure.
+ * @return The size of \p map.
+ */
+size_t dmnsn_map_size(const dmnsn_map *map);
+
+/**
+ * Evaluate a map.
+ * @param[in]  map   The map to evaluate.
+ * @param[in]  n     The index to evaluate.
+ * @param[out] val   The normalized distance of \p n from \p obj1.
+ * @param[out] obj1  The first object.
+ * @param[out] obj2  The second object.
+ */
+void dmnsn_map_evaluate(const dmnsn_map *map, double n,
+                        double *val, void *obj1, void *obj2);
+
+/**
+ * Apply a callback to each element of a map.
+ * @param[in,out] map       The map.
+ * @param[in]     callback  The callback to apply to the elements.
+ */
+void dmnsn_map_apply(dmnsn_map *map, dmnsn_callback_fn *callback);
diff --git a/libdimension/dimension/pattern/pattern.h b/libdimension/dimension/pattern/pattern.h
new file mode 100644
index 0000000..2caed07
--- /dev/null
+++ b/libdimension/dimension/pattern/pattern.h
@@ -0,0 +1,62 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Patterns.  Patterns are functions which map vectors to scalars, which are
+ * used for pigments and normals.
+ */
+
+/* Forward-declare dmnsn_pattern */
+typedef struct dmnsn_pattern dmnsn_pattern;
+
+/**
+ * Pattern callback.
+ * @param[in] pattern  The pattern itself.
+ * @param[in] v        The point at which to evaluate the pattern.
+ * @return The value of the pattern at \p v.
+ */
+typedef double dmnsn_pattern_fn(const dmnsn_pattern *pattern, dmnsn_vector v);
+
+/** A pattern. */
+struct dmnsn_pattern {
+  dmnsn_pattern_fn *pattern_fn; /**< The pattern callback. */
+};
+
+/**
+ * Allocate a dummy pattern.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return A pattern with no callbacks set.
+ */
+dmnsn_pattern *dmnsn_new_pattern(dmnsn_pool *pool);
+
+/**
+ * Initialize a dmnsn_pattern field.
+ * @param[out] pattern  The pattern to initialize.
+ */
+void dmnsn_init_pattern(dmnsn_pattern *pattern);
+
+/**
+ * Invoke the pattern callback.
+ * @param[in] pattern  The pattern to evaluate.
+ * @param[in] v        The point to get the pattern value for.
+ * @return The value of the pattern at \p v.
+ */
+double dmnsn_pattern_value(const dmnsn_pattern *pattern, dmnsn_vector v);
diff --git a/libdimension/dimension/pattern/patterns.h b/libdimension/dimension/pattern/patterns.h
new file mode 100644
index 0000000..59b7fec
--- /dev/null
+++ b/libdimension/dimension/pattern/patterns.h
@@ -0,0 +1,48 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pre-defined patterns.
+ */
+
+/**
+ * A checker pattern.  The pattern is composed of tesselating unit cubes
+ * alternating between 0 and 1.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return A checker pattern.
+ */
+dmnsn_pattern *dmnsn_new_checker_pattern(dmnsn_pool *pool);
+
+/**
+ * A gradient.  The value starts at 0 at the origin, and goes linearly to 1 in
+ * the direction of \p orientation, then repeats after a distance of 1.
+ * @param[in] pool  The memory pool to allocate from.
+ * @param[in] orientation  The direction of the gradient.
+ * @return A gradient pattern.
+ */
+dmnsn_pattern *dmnsn_new_gradient_pattern(dmnsn_pool *pool, dmnsn_vector orientation);
+
+/**
+ * A leopard pattern.
+ * @param[in] pool  The memory pool to allocate from.
+ * @return A leopard pattern.
+ */
+dmnsn_pattern *dmnsn_new_leopard_pattern(dmnsn_pool *pool);
diff --git a/libdimension/dimension/patterns.h b/libdimension/dimension/patterns.h
deleted file mode 100644
index 59b7fec..0000000
--- a/libdimension/dimension/patterns.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pre-defined patterns.
- */
-
-/**
- * A checker pattern.  The pattern is composed of tesselating unit cubes
- * alternating between 0 and 1.
- * @param[in] pool  The memory pool to allocate from.
- * @return A checker pattern.
- */
-dmnsn_pattern *dmnsn_new_checker_pattern(dmnsn_pool *pool);
-
-/**
- * A gradient.  The value starts at 0 at the origin, and goes linearly to 1 in
- * the direction of \p orientation, then repeats after a distance of 1.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] orientation  The direction of the gradient.
- * @return A gradient pattern.
- */
-dmnsn_pattern *dmnsn_new_gradient_pattern(dmnsn_pool *pool, dmnsn_vector orientation);
-
-/**
- * A leopard pattern.
- * @param[in] pool  The memory pool to allocate from.
- * @return A leopard pattern.
- */
-dmnsn_pattern *dmnsn_new_leopard_pattern(dmnsn_pool *pool);
diff --git a/libdimension/dimension/pigment.h b/libdimension/dimension/pigment.h
deleted file mode 100644
index 14d8bae..0000000
--- a/libdimension/dimension/pigment.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Object pigments.
- */
-
-/* Forward-declare dmnsn_pigment */
-typedef struct dmnsn_pigment dmnsn_pigment;
-
-/**
- * Pigment callback.
- * @param[in] pigment  The pigment itself.
- * @param[in] v        The point to color.
- * @return The color of the pigment at \p v.
- */
-typedef dmnsn_tcolor dmnsn_pigment_fn(const dmnsn_pigment *pigment,
-                                      dmnsn_vector v);
-
-/**
- * Pigment initializer callback.
- * @param[in,out] pigment  The pigment to initialize.
- */
-typedef void dmnsn_pigment_initialize_fn(dmnsn_pigment *pigment);
-
-/** A pigment. */
-struct dmnsn_pigment {
-  dmnsn_pigment_fn *pigment_fn; /**< The pigment callback. */
-  dmnsn_pigment_initialize_fn *initialize_fn; /**< The initializer callback. */
-
-  dmnsn_matrix trans; /**< Transformation matrix. */
-  dmnsn_matrix trans_inv; /**< The inverse of the transformation matrix. */
-
-  /** Quick color -- used for low-quality renders. */
-  dmnsn_tcolor quick_color;
-
-  bool initialized; /** @internal Whether the pigment is initialized. */
-};
-
-/**
- * Allocate a new dummy pigment.
- * @param[in] pool  The memory pool to allocate from.
- * @return The allocated pigment.
- */
-dmnsn_pigment *dmnsn_new_pigment(dmnsn_pool *pool);
-
-/**
- * Initialize a dmnsn_pigment field.
- * @param[out] pigment  The pigment to initialize.
- */
-void dmnsn_init_pigment(dmnsn_pigment *pigment);
-
-/**
- * Initialize a pigment.  Pigments should not be used before being initialized,
- * but should not be modified after being initialized.  Pigments are generally
- * initialized for you.
- * @param[in,out] pigment  The pigment to initialize.
- */
-void dmnsn_pigment_initialize(dmnsn_pigment *pigment);
-
-/**
- * Evaluate the color of a pigment at a point.
- * @param[in] pigment  The pigment to evaluate.
- * @param[in] v        The point to color.
- * @return The color at \p v.
- */
-dmnsn_tcolor dmnsn_pigment_evaluate(const dmnsn_pigment *pigment,
-                                    dmnsn_vector v);
diff --git a/libdimension/dimension/pigments.h b/libdimension/dimension/pigments.h
deleted file mode 100644
index 100016d..0000000
--- a/libdimension/dimension/pigments.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pre-defined pigments.
- */
-
-/**
- * A solid color.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] color  The color of the pigment.
- * @return A pigment with the color \p color everywhere.
- */
-dmnsn_pigment *dmnsn_new_solid_pigment(dmnsn_pool *pool, dmnsn_tcolor color);
-
-/**
- * An image map.  The image (regardless of its real dimensions) is projected
- * on the x-y plane in tesselating unit squares.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] canvas  The canvas holding the image.
- * @return An image-mapped pigment.
- */
-dmnsn_pigment *dmnsn_new_canvas_pigment(dmnsn_pool *pool, dmnsn_canvas *canvas);
-
-/**
- * Pigment map flags.
- */
-typedef enum dmnsn_pigment_map_flags {
-  DMNSN_PIGMENT_MAP_REGULAR, /**< Calculate linear color gradients. */
-  DMNSN_PIGMENT_MAP_SRGB /**< Calculate sRGB color gradients. */
-} dmnsn_pigment_map_flags;
-
-/**
- * Construct a pigment map.
- * @param[in] pool  The memory pool to allocate from.
- * @return An empty pigment map.
- */
-dmnsn_map *dmnsn_new_pigment_map(dmnsn_pool *pool);
-
-/**
- * A pigment-mapped pigment.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] pattern  The pattern of the pigment.
- * @param[in,out] map  The pigment map to apply to the pattern.
- * @param[in] flags  Gradient flags
- * @return A pigment mapping the pattern to other pigments.
- */
-dmnsn_pigment *dmnsn_new_pigment_map_pigment(dmnsn_pool *pool, dmnsn_pattern *pattern, dmnsn_map *map, dmnsn_pigment_map_flags flags);
diff --git a/libdimension/dimension/platform.h b/libdimension/dimension/platform.h
new file mode 100644
index 0000000..4c01709
--- /dev/null
+++ b/libdimension/dimension/platform.h
@@ -0,0 +1,39 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Platform abstractions.
+ */
+
+#ifndef DMNSN_PLATFORM_H
+#define DMNSN_PLATFORM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <dimension/platform/timer.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DMNSN_PLATFORM_H */
diff --git a/libdimension/dimension/platform/timer.h b/libdimension/dimension/platform/timer.h
new file mode 100644
index 0000000..bcf4a9d
--- /dev/null
+++ b/libdimension/dimension/platform/timer.h
@@ -0,0 +1,58 @@
+/*************************************************************************
+ * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * A platform-agnostic timer abstraction.
+ */
+
+#ifndef DMNSN_PLATFORM_H
+#error "Please include <dimension/platform.h> instead of this header directly."
+#endif
+
+/** A platform-agnotic timer. */
+typedef struct dmnsn_timer {
+  double real;   /**< Wall-clock time. */
+  double user;   /**< Time spent executing. */
+  double system; /**< Time spent waiting for the system. */
+} dmnsn_timer;
+
+/** A standard format string for timers. */
+#define DMNSN_TIMER_FORMAT "%.2fs (user: %.2fs; system: %.2fs)"
+/**
+ * The appropriate arguments to printf() a timer.  For example:
+ * @code
+ *   printf(DMNSN_TIMER_FORMAT "\n", DMNSN_TIMER_PRINTF(timer));
+ * @endcode
+ * will print something like "1.00s (user: 0.99s; system: 0.01s)".
+ */
+#define DMNSN_TIMER_PRINTF(t) (t).real, (t).user, (t).system
+
+/**
+ * Start a timer.  The values of an unfinished timer are undefined.
+ * @param[in,out] timer  The timer to start.
+ */
+void dmnsn_timer_start(dmnsn_timer *timer);
+
+/**
+ * Finish timing.  The members of the timer struct will now contain timing data.
+ * @param[in,out] timer  The timer to stop.
+ */
+void dmnsn_timer_stop(dmnsn_timer *timer);
diff --git a/libdimension/dimension/png.h b/libdimension/dimension/png.h
deleted file mode 100644
index f4d2c1e..0000000
--- a/libdimension/dimension/png.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * PNG import/export of canvases.
- */
-
-#include <stdio.h>
-
-/**
- * Optimize a canvas for PNG exporting
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] canvas  The canvas to optimize.
- * @return Whether the canvas was successfully optimized.
- */
-int dmnsn_png_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
-
-/**
- * Write a canvas to a file in PNG format.
- * @param[in] canvas  The canvas to write.
- * @param[in,out] file  The file to write to.
- * @return 0 on success, non-zero on failure.
- */
-int dmnsn_png_write_canvas(const dmnsn_canvas *canvas, FILE *file);
-
-/**
- * Write a canvas to a PNG file in the background.
- * @param[in] canvas  The canvas to write.
- * @param[in,out] file  The file to write to.
- * @return A \ref dmnsn_future object, or NULL on failure.
- */
-dmnsn_future *dmnsn_png_write_canvas_async(const dmnsn_canvas *canvas,
-                                           FILE *file);
-
-/**
- * Read a canvas from a PNG file.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] file  The PNG file to read.
- * @return The new canvas, or NULL on failure.
- */
-dmnsn_canvas *dmnsn_png_read_canvas(dmnsn_pool *pool, FILE *file);
-
-/**
- * Read a canvas from a PNG file in the background.
- * @param[out] canvas  The address of a non-allocated canvas object.  The canvas
- *                     object will be allocated and filled with the contents of
- *                     \p file.  Do not read from this object until the
- *                     background task has finished.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in,out] file  The PNG file to read.
- * @return A \ref dmnsn_future object, or NULL on failure.
- */
-dmnsn_future *dmnsn_png_read_canvas_async(dmnsn_canvas **canvas, dmnsn_pool *pool, FILE *file);
diff --git a/libdimension/dimension/polynomial.h b/libdimension/dimension/polynomial.h
deleted file mode 100644
index ab95d50..0000000
--- a/libdimension/dimension/polynomial.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Utility functions for working with and numerically solving polynomials.
- * Polynomials are represented as simple arrays where the ith element is the
- * coefficient on x^i.  In general, we are only interested in positive roots.
- */
-
-#include <stddef.h>
-#include <stdio.h>
-
-/**
- * Evaluate a polynomial at \p x.
- * @param[in] poly    The coefficients of the polynomial to evaluate, in order
- *                    from lowest degree to highest degree.  The array should
- *                    have dimension <tt>degree + 1</tt>.
- * @param[in] degree  The degree of the polynomial.
- * @param[in] x       The value of the variable at which to evaluate.
- */
-DMNSN_INLINE double
-dmnsn_polynomial_evaluate(const double poly[], size_t degree, double x)
-{
-  double ret = poly[degree];
-  size_t i;
-  for (i = degree; i-- > 0;) {
-    ret = ret*x + poly[i];
-  }
-  return ret;
-}
-
-/**
- * Evaluate the derivative of a polynomial at \p x.
- * @param[in] poly    The coefficients of the polynomial to evaluate.
- * @param[in] degree  The degree of the polynomial.
- * @param[in] x       The value of the variable at which to evaluate.
- */
-DMNSN_INLINE double
-dmnsn_polynomial_evaluate_derivative(const double poly[], size_t degree,
-                                     double x)
-{
-  double ret = poly[degree]*degree;
-  size_t i;
-  for (i = degree - 1; i >= 1; --i) {
-    ret = ret*x + poly[i]*i;
-  }
-  return ret;
-}
-
-/**
- * Find the positive roots of a polynomial.
- * @param[in]  poly    The coefficients of the polynomial to solve.
- * @param[in]  degree  The degree of the polynomial.
- * @param[out] x       An array in which to store the roots.  It should have
- *                     dimension \p degree.
- * @return The number of positive roots stored in \c x[].
- */
-size_t dmnsn_polynomial_solve(const double poly[], size_t degree, double x[]);
-
-/**
- * Output a polynomial.  The polynomial is printed as a function of x suitable
- * for input into a CAS, and without a trailing newline.
- * @param[in,out] file    The file to write to.
- * @param[in]     poly    The coefficients of the polynomial to print.
- * @param[in]     degree  The degree of the polynomial.
- */
-void dmnsn_polynomial_print(FILE *file, const double poly[], size_t degree);
diff --git a/libdimension/dimension/pool.h b/libdimension/dimension/pool.h
deleted file mode 100644
index 164bbbc..0000000
--- a/libdimension/dimension/pool.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Memory pools.  Rather than more complicated garbage collection methods like
- * reference counting, objects are allocated out of pools which are freed all at
- * once once a scene is rendered (for example).
- */
-
-#include <stddef.h> /* For size_t */
-
-/* Forward-declare dmnsn_pool. */
-typedef struct dmnsn_pool dmnsn_pool;
-
-/**
- * Create a new memory pool.
- * @return The new pool.
- */
-dmnsn_pool *dmnsn_new_pool(void);
-
-/**
- * Allocate some memory from a pool.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] size  The size of the memory block to allocate.
- * @return The allocated memory area.
- */
-void *dmnsn_palloc(dmnsn_pool *pool, size_t size);
-
-/**
- * Allocate some memory from a pool.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] size  The size of the memory block to allocate.
- * @param[in] cleanup_fn  A callback to invoke before the memory is freed.
- * @return The allocated memory area.
- */
-void *dmnsn_palloc_tidy(dmnsn_pool *pool, size_t size, dmnsn_callback_fn *cleanup_fn);
-
-/**
- * Allocate some memory from a pool.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] type  The type of the memory block to allocate.
- * @return The allocated memory area.
- */
-#define DMNSN_PALLOC(pool, type) ((type *)dmnsn_palloc((pool), sizeof(type)))
-
-/**
- * Allocate some memory from a pool.
- * @param[in] pool  The memory pool to allocate from.
- * @param[in] type  The type of the memory block to allocate.
- * @param[in] cleanup_fn  A callback to invoke before the memory is freed.
- * @return The allocated memory area.
- */
-#define DMNSN_PALLOC_TIDY(pool, type, cleanup_fn) ((type *)dmnsn_palloc_tidy((pool), sizeof(type), (cleanup_fn)))
-
-/**
- * Free a memory pool and all associated allocations.
- * @param[in] pool  The memory pool to free.
- */
-void dmnsn_delete_pool(dmnsn_pool *pool);
diff --git a/libdimension/dimension/ray_trace.h b/libdimension/dimension/ray_trace.h
deleted file mode 100644
index 82fb759..0000000
--- a/libdimension/dimension/ray_trace.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Ray-trace a scene.
- */
-
-/**
- * Render a scene by ray tracing.
- * @param[in,out] scene  The scene to render.
- */
-void dmnsn_ray_trace(dmnsn_scene *scene);
-
-/**
- * Render a scene in the background.
- * @param[in,out] scene  The scene to render.
- * @return A \p dmnsn_future object.
- */
-dmnsn_future *dmnsn_ray_trace_async(dmnsn_scene *scene);
diff --git a/libdimension/dimension/render.h b/libdimension/dimension/render.h
new file mode 100644
index 0000000..296d7a3
--- /dev/null
+++ b/libdimension/dimension/render.h
@@ -0,0 +1,41 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Rendering.
+ */
+
+#ifndef DMNSN_RENDER_H
+#define DMNSN_RENDER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <dimension/model.h>
+
+#include <dimension/render/render.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DMNSN_RENDER_H */
diff --git a/libdimension/dimension/render/render.h b/libdimension/dimension/render/render.h
new file mode 100644
index 0000000..69f52c2
--- /dev/null
+++ b/libdimension/dimension/render/render.h
@@ -0,0 +1,41 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Rendering.
+ */
+
+#ifndef DMNSN_RENDER_H
+#error "Please include <dimension/render.h> instead of this header directly."
+#endif
+
+/**
+ * Render a scene.
+ * @param[in,out] scene  The scene to render.
+ */
+void dmnsn_render(dmnsn_scene *scene);
+
+/**
+ * Render a scene in the background.
+ * @param[in,out] scene  The scene to render.
+ * @return A \p dmnsn_future object.
+ */
+dmnsn_future *dmnsn_render_async(dmnsn_scene *scene);
diff --git a/libdimension/dimension/scene.h b/libdimension/dimension/scene.h
deleted file mode 100644
index dd0c1ba..0000000
--- a/libdimension/dimension/scene.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Entire scenes.
- */
-
-/** Render quality flags. */
-enum {
-  DMNSN_RENDER_NONE         = 0,      /**< Render nothing. */
-  DMNSN_RENDER_PIGMENT      = 1 << 0, /**< Render pigments. */
-  DMNSN_RENDER_LIGHTS       = 1 << 1, /**< Render lights and shadows. */
-  DMNSN_RENDER_FINISH       = 1 << 2, /**< Render object finishes. */
-  DMNSN_RENDER_TRANSPARENCY = 1 << 3, /**< Render transparency/refraction. */
-  DMNSN_RENDER_REFLECTION   = 1 << 4, /**< Render specular reflection. */
-  DMNSN_RENDER_FULL         = ~DMNSN_RENDER_NONE /**< Render everything. */
-};
-
-/** Render quality. */
-typedef unsigned int dmnsn_quality;
-
-/** An entire scene. */
-typedef struct dmnsn_scene {
-  /* World attributes */
-  dmnsn_pigment *background;        /**< Background pigment. */
-  dmnsn_texture *default_texture;   /**< Default object texture. */
-  dmnsn_interior *default_interior; /**< Default object interior. */
-
-  /** Canvas. */
-  dmnsn_canvas *canvas;
-
-  /* Support for rendering image subregions. */
-  size_t region_x; /**< The x position of the canvas in the broader image. */
-  size_t region_y; /**< The y position of the canvas in the broader image. */
-  size_t outer_width; /**< Width of the broader image. */
-  size_t outer_height; /**< Height of the broader image. */
-
-  /** Objects. */
-  dmnsn_array *objects;
-
-  /** Lights. */
-  dmnsn_array *lights;
-
-  /** Camera. */
-  dmnsn_camera *camera;
-
-  /** Render quality. */
-  dmnsn_quality quality;
-
-  /** Recursion limit. */
-  unsigned int reclimit;
-
-  /** Adaptive depth control bailout. */
-  double adc_bailout;
-
-  /** Number of parallel threads. */
-  unsigned int nthreads;
-
-  /** Timers. */
-  dmnsn_timer bounding_timer;
-  dmnsn_timer render_timer;
-
-  bool initialized; /**< @internal Whether the scene is initialized. */
-} dmnsn_scene;
-
-/**
- * Create a scene.
- * @param[in] pool  The memory pool to allocate from.
- * @return A new empty scene.
- */
-dmnsn_scene *dmnsn_new_scene(dmnsn_pool *pool);
-
-/**
- * Initialize a scene.
- * @param[in,out] scene  The scene to initalize.
- */
-void dmnsn_scene_initialize(dmnsn_scene *scene);
diff --git a/libdimension/dimension/tcolor.h b/libdimension/dimension/tcolor.h
deleted file mode 100644
index 8c48800..0000000
--- a/libdimension/dimension/tcolor.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Colors with transparency information.
- */
-
-/** A transparent color. */
-typedef struct dmnsn_tcolor {
-  dmnsn_color c; /**< Color. */
-  double T;      /**< Transparency. */
-  double F;      /**< Proportion of filtered transparency. */
-} dmnsn_tcolor;
-
-/** A standard format string for colors. */
-#define DMNSN_TCOLOR_FORMAT "TColor<%g, %g, %g, %g, %g>"
-/** The appropriate arguements to printf() a color. */
-#define DMNSN_TCOLOR_PRINTF(tc) (tc).c.R, (tc).c.G, (tc).c.B, (tc).T, (tc).F
-
-/** Create a tcolor. */
-DMNSN_INLINE dmnsn_tcolor
-dmnsn_new_tcolor(dmnsn_color c, double T, double F)
-{
-  dmnsn_tcolor ret = { c, T, F };
-  return ret;
-}
-
-/** Convert a dmnsn_color into a dmnsn_tcolor. */
-#define DMNSN_TCOLOR(c) dmnsn_new_tcolor(c, 0.0, 0.0)
-
-/** Create a tcolor with individually-specified components. */
-DMNSN_INLINE dmnsn_tcolor
-dmnsn_new_tcolor5(double R, double G, double B, double T, double F)
-{
-  dmnsn_tcolor ret = { dmnsn_new_color(R, G, B), T, F };
-  return ret;
-}
-
-/** Return the color at \p n on a gradient from \p c1 at 0 to \p c2 at 1. */
-DMNSN_INLINE dmnsn_tcolor
-dmnsn_tcolor_gradient(dmnsn_tcolor c1, dmnsn_tcolor c2, double n)
-{
-  return dmnsn_new_tcolor(
-    dmnsn_color_gradient(c1.c, c2.c, n),
-    n*(c2.T - c1.T) + c1.T,
-    n*(c2.F - c1.F) + c1.F
-  );
-}
-
-/** Filter \p light through \p filter. */
-DMNSN_INLINE dmnsn_color
-dmnsn_tcolor_filter(dmnsn_color light, dmnsn_tcolor filter)
-{
-  dmnsn_color filtered =
-    dmnsn_color_mul(filter.T*filter.F, dmnsn_color_illuminate(light, filter.c));
-  dmnsn_color transmitted = dmnsn_color_mul(filter.T*(1.0 - filter.F), light);
-  return dmnsn_color_add(filtered, transmitted);
-}
-
-/** Remove the filtered component of a tcolor. */
-DMNSN_INLINE dmnsn_tcolor
-dmnsn_tcolor_remove_filter(dmnsn_tcolor tcolor)
-{
-  double intensity = dmnsn_color_intensity(tcolor.c);
-  double newtrans = (1.0 - (1.0 - intensity)*tcolor.F)*tcolor.T;
-  if (1.0 - newtrans >= dmnsn_epsilon) {
-    tcolor.c = dmnsn_color_mul((1.0 - tcolor.T)/(1.0 - newtrans), tcolor.c);
-  }
-  tcolor.T = newtrans;
-  tcolor.F = 0.0;
-  return tcolor;
-}
-
-/** Saturate the tcolor components to [0.0, 1.0]. */
-DMNSN_INLINE dmnsn_tcolor
-dmnsn_tcolor_clamp(dmnsn_tcolor tcolor)
-{
-  tcolor.c = dmnsn_color_clamp(tcolor.c);
-  tcolor.T = dmnsn_clamp(tcolor.T, 0.0, 1.0);
-  tcolor.F = dmnsn_clamp(tcolor.F, 0.0, 1.0);
-  return tcolor;
-}
-
-/** Return whether a tcolor contains any NaN components. */
-DMNSN_INLINE bool
-dmnsn_tcolor_isnan(dmnsn_tcolor tcolor)
-{
-  return dmnsn_color_isnan(tcolor.c) || dmnsn_isnan(tcolor.T) || dmnsn_isnan(tcolor.F);
-}
-
-/* Standard tcolors */
-
-/** Clear. */
-#define dmnsn_clear dmnsn_new_tcolor5(0.0, 0.0, 0.0, 1.0, 0.0)
diff --git a/libdimension/dimension/texture.h b/libdimension/dimension/texture.h
deleted file mode 100644
index df08a4a..0000000
--- a/libdimension/dimension/texture.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Object textures.
- */
-
-/** A complete texture. */
-typedef struct {
-  dmnsn_pigment *pigment; /**< Pigment. */
-  dmnsn_finish finish;    /**< Finish. */
-
-  dmnsn_matrix trans;     /**< Transformation matrix. */
-  dmnsn_matrix trans_inv; /**< The inverse of the transformation matrix. */
-
-  bool initialized; /**< @internal Whether the texture is initialized yet. */
-} dmnsn_texture;
-
-/**
- * Create a blank texture.
- * @param[in] pool  The memory pool to allocate from.
- * @return The new texture.
- */
-dmnsn_texture *dmnsn_new_texture(dmnsn_pool *pool);
-
-/**
- * Initialize a texture.  Textures should not be used before being initialized,
- * but should not be modified after being initialized.  Textures are generally
- * initialized for you.
- * @param[in,out] texture  The texture to initialize.
- */
-void dmnsn_texture_initialize(dmnsn_texture *texture);
-
-/**
- * Fill missing texture properties from a default texture.
- * @param[in]     default_texture  The default texture.
- * @param[in,out] texturep         A pointer to the texture to fill.
- */
-void dmnsn_texture_cascade(dmnsn_texture *default_texture, dmnsn_texture **texturep);
diff --git a/libdimension/dimension/timer.h b/libdimension/dimension/timer.h
deleted file mode 100644
index 55d66ff..0000000
--- a/libdimension/dimension/timer.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * A platform-agnostic timer abstraction.
- */
-
-/** A platform-agnotic timer. */
-typedef struct dmnsn_timer {
-  double real;   /**< Wall-clock time. */
-  double user;   /**< Time spent executing. */
-  double system; /**< Time spent waiting for the system. */
-} dmnsn_timer;
-
-/** A standard format string for timers. */
-#define DMNSN_TIMER_FORMAT "%.2fs (user: %.2fs; system: %.2fs)"
-/**
- * The appropriate arguments to printf() a timer.  For example:
- * @code
- *   printf(DMNSN_TIMER_FORMAT "\n", DMNSN_TIMER_PRINTF(timer));
- * @endcode
- * will print something like "1.00s (user: 0.99s; system: 0.01s)".
- */
-#define DMNSN_TIMER_PRINTF(t) (t).real, (t).user, (t).system
-
-/**
- * Start a timer.  The values of an unfinished timer are undefined.
- * @param[in,out] timer  The timer to start.
- */
-void dmnsn_timer_start(dmnsn_timer *timer);
-
-/**
- * Finish timing.  The members of the timer struct will now contain timing data.
- * @param[in,out] timer  The timer to stop.
- */
-void dmnsn_timer_stop(dmnsn_timer *timer);
diff --git a/libdimension/error.c b/libdimension/error.c
deleted file mode 100644
index e9e7cf1..0000000
--- a/libdimension/error.c
+++ /dev/null
@@ -1,143 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Error handling.
- */
-
-#include "dimension-internal.h"
-#include <errno.h>
-#include <pthread.h>
-#include <stdatomic.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-/// Report internal errors in this file.
-#define DMNSN_LOCAL_ERROR(str)                          \
-  do {                                                  \
-    fprintf(stderr, "Dimension ERROR: %s, %s:%u: %s\n", \
-            DMNSN_FUNC, __FILE__, __LINE__, (str));     \
-    abort();                                            \
-  } while (0)
-
-/// The default fatal error handler.
-static void dmnsn_default_fatal_error_fn(void);
-
-/// The current fatal error handler.
-static atomic(dmnsn_fatal_error_fn *) dmnsn_fatal = ATOMIC_VAR_INIT(dmnsn_default_fatal_error_fn);
-
-/// The current resilience.
-static atomic_bool dmnsn_always_die = ATOMIC_VAR_INIT(false);
-
-void
-dmnsn_report_impl(bool die, const char *func, const char *file, unsigned int line, const char *str)
-{
-  // Save the value of errno
-  int err = errno;
-
-  bool always_die = atomic_load(&dmnsn_always_die);
-
-  // Print the diagnostic string
-  fprintf(stderr, "Dimension %s: %s, %s:%u: %s\n",
-          die ? "ERROR" : "WARNING", func, file, line, str);
-
-  // Print the value of errno
-  if (err != 0) {
-    fprintf(stderr, "Last error: %d", err);
-#if DMNSN_STRERROR_R
-    char errbuf[256];
-    if (strerror_r(err, errbuf, 256) == 0) {
-      fprintf(stderr, " (%s)", errbuf);
-    }
-#elif DMNSN_SYS_ERRLIST
-    if (err >= 0 && err < sys_nerr) {
-      fprintf(stderr, " (%s)", sys_errlist[err]);
-    }
-#endif
-    fprintf(stderr, "\n");
-  }
-
-  // Print a stack trace to standard error
-  dmnsn_backtrace(stderr);
-
-  // Call the fatal error handler if needed
-  if (die || always_die) {
-    static __thread bool thread_exiting = false;
-
-    if (thread_exiting) {
-      if (die) {
-        // Prevent infinite recursion if the fatal error function itself calls
-        // dmnsn_error() (not dmnsn_warning())
-        DMNSN_LOCAL_ERROR("Error raised while in error handler, aborting.");
-      }
-    } else {
-      thread_exiting = true;
-
-      dmnsn_fatal_error_fn *fatal = dmnsn_get_fatal_error_fn();
-      fatal();
-      DMNSN_LOCAL_ERROR("Fatal error handler didn't exit.");
-    }
-  }
-}
-
-void
-dmnsn_report_warning(const char *func, const char *file, unsigned int line, const char *str)
-{
-  dmnsn_report_impl(false, func, file, line, str);
-}
-
-DMNSN_NORETURN
-dmnsn_report_error(const char *func, const char *file, unsigned int line, const char *str)
-{
-  dmnsn_report_impl(true, func, file, line, str);
-  DMNSN_UNREACHABLE();
-}
-
-void
-dmnsn_die_on_warnings(bool always_die)
-{
-  atomic_store(&dmnsn_always_die, always_die);
-}
-
-dmnsn_fatal_error_fn *
-dmnsn_get_fatal_error_fn(void)
-{
-  dmnsn_fatal_error_fn *fatal = atomic_load(&dmnsn_fatal);
-  return fatal;
-}
-
-void
-dmnsn_set_fatal_error_fn(dmnsn_fatal_error_fn *fatal)
-{
-  atomic_store(&dmnsn_fatal, fatal);
-}
-
-static void
-dmnsn_default_fatal_error_fn(void)
-{
-  if (dmnsn_is_main_thread()) {
-    exit(EXIT_FAILURE);
-  } else {
-    pthread_exit(NULL);
-  }
-}
diff --git a/libdimension/finish.c b/libdimension/finish.c
deleted file mode 100644
index 13adefb..0000000
--- a/libdimension/finish.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Finishes.
- */
-
-#include "dimension-internal.h"
-
-dmnsn_ambient *
-dmnsn_new_ambient(dmnsn_pool *pool, dmnsn_color ambient_light)
-{
-  dmnsn_ambient *ambient = DMNSN_PALLOC(pool, dmnsn_ambient);
-  ambient->ambient = ambient_light;
-  return ambient;
-}
-
-dmnsn_diffuse *
-dmnsn_new_diffuse(dmnsn_pool *pool)
-{
-  dmnsn_diffuse *diffuse = DMNSN_PALLOC(pool, dmnsn_diffuse);
-  dmnsn_init_diffuse(diffuse);
-  return diffuse;
-}
-
-void
-dmnsn_init_diffuse(dmnsn_diffuse *diffuse)
-{
-}
-
-dmnsn_specular *
-dmnsn_new_specular(dmnsn_pool *pool)
-{
-  dmnsn_specular *specular = DMNSN_PALLOC(pool, dmnsn_specular);
-  dmnsn_init_specular(specular);
-  return specular;
-}
-
-void
-dmnsn_init_specular(dmnsn_specular *specular)
-{
-}
-
-dmnsn_reflection *
-dmnsn_new_reflection(dmnsn_pool *pool)
-{
-  dmnsn_reflection *reflection = DMNSN_PALLOC(pool, dmnsn_reflection);
-  dmnsn_init_reflection(reflection);
-  return reflection;
-}
-
-void
-dmnsn_init_reflection(dmnsn_reflection *reflection)
-{
-}
-
-dmnsn_finish
-dmnsn_new_finish(void)
-{
-  dmnsn_finish finish = {
-    .ambient = NULL,
-    .diffuse = NULL,
-    .specular = NULL,
-    .reflection = NULL,
-  };
-  return finish;
-}
-
-void
-dmnsn_finish_cascade(const dmnsn_finish *default_finish, dmnsn_finish *finish)
-{
-  if (!finish->ambient) {
-    finish->ambient = default_finish->ambient;
-  }
-  if (!finish->diffuse) {
-    finish->diffuse = default_finish->diffuse;
-  }
-  if (!finish->specular) {
-    finish->specular = default_finish->specular;
-  }
-  if (!finish->reflection) {
-    finish->reflection = default_finish->reflection;
-  }
-}
diff --git a/libdimension/future-internal.h b/libdimension/future-internal.h
deleted file mode 100644
index 7ca318a..0000000
--- a/libdimension/future-internal.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Future object implementation.
- */
-
-#include <pthread.h>
-
-/// Allocate a new future object.
-DMNSN_INTERNAL dmnsn_future *dmnsn_new_future(void);
-
-/// Set the total number of loop iterations.
-DMNSN_INTERNAL void dmnsn_future_set_total(dmnsn_future *future, size_t total);
-/// Increment the progress of a background task.
-DMNSN_INTERNAL void dmnsn_future_increment(dmnsn_future *future);
-/// Instantly complete the background teask.
-DMNSN_INTERNAL void dmnsn_future_finish(dmnsn_future *future);
-/// Set the number of worker threads.
-DMNSN_INTERNAL void dmnsn_future_set_nthreads(dmnsn_future *future,
-                                              unsigned int nthreads);
-/// Notify completion of a worker thread.
-DMNSN_INTERNAL void dmnsn_future_finish_thread(dmnsn_future *future);
-
-struct dmnsn_future {
-  size_t progress; ///< Completed loop iterations.
-  size_t total;    ///< Total expected loop iterations.
-
-  /// The worker thread.
-  pthread_t thread;
-
-  /// Mutex to guard progress and total.
-  pthread_mutex_t mutex;
-
-  /// Condition variable for waiting for a particular amount of progress.
-  pthread_cond_t cond;
-
-  /// Minimum waited-on value.
-  double min_wait;
-
-  /// Number of threads working on the future's background task.
-  unsigned int nthreads;
-  /// Number of threads not yet paused.
-  unsigned int nrunning;
-  /// Count of threads holding the future paused.
-  unsigned int npaused;
-  /// Condition variable for waiting for nrunning == 0.
-  pthread_cond_t none_running_cond;
-  /// Condition variable for waiting for nrunning == nthreads.
-  pthread_cond_t all_running_cond;
-  /// Condition variable for waiting for npaused == 0.
-  pthread_cond_t resume_cond;
-};
diff --git a/libdimension/future.c b/libdimension/future.c
deleted file mode 100644
index 8133812..0000000
--- a/libdimension/future.c
+++ /dev/null
@@ -1,279 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Future objects.
- */
-
-#include "dimension-internal.h"
-#include <pthread.h>
-
-/**
- * Since C doesn't support anything like C++'s mutable, we fake it by casting
- * away the constness.  This is okay since all valid dmnsn_futures live on the
- * heap, so cannot be const.
- */
-#define MUTATE(future) ((dmnsn_future *)(future))
-
-// Allocate a new dmnsn_future*
-dmnsn_future *
-dmnsn_new_future(void)
-{
-  dmnsn_future *future = DMNSN_MALLOC(dmnsn_future);
-  future->progress = 0;
-  future->total    = 1;
-
-  dmnsn_initialize_mutex(&future->mutex);
-  dmnsn_initialize_cond(&future->cond);
-
-  future->min_wait = 1.0;
-
-  future->nthreads = future->nrunning = 1;
-  future->npaused = 0;
-  dmnsn_initialize_cond(&future->none_running_cond);
-  dmnsn_initialize_cond(&future->all_running_cond);
-  dmnsn_initialize_cond(&future->resume_cond);
-
-  return future;
-}
-
-static void
-dmnsn_delete_future(dmnsn_future *future)
-{
-  if (future) {
-    dmnsn_destroy_cond(&future->resume_cond);
-    dmnsn_destroy_cond(&future->all_running_cond);
-    dmnsn_destroy_cond(&future->none_running_cond);
-    dmnsn_destroy_cond(&future->cond);
-    dmnsn_destroy_mutex(&future->mutex);
-    dmnsn_free(future);
-  }
-}
-
-// Join the worker thread and delete `future'.
-int
-dmnsn_future_join(dmnsn_future *future)
-{
-  void *ptr;
-  int retval = -1;
-
-  if (future) {
-    dmnsn_assert(future->npaused == 0, "Attempt to join future while paused");
-
-    // Get the thread's return value
-    dmnsn_join_thread(future->thread, &ptr);
-    if (ptr && ptr != PTHREAD_CANCELED) {
-      retval = *(int *)ptr;
-      dmnsn_free(ptr);
-    }
-
-    // Free the future object
-    dmnsn_delete_future(future);
-  }
-
-  return retval;
-}
-
-// Cancel a background thread
-void
-dmnsn_future_cancel(dmnsn_future *future)
-{
-  pthread_cancel(future->thread);
-}
-
-/**
- * Get the current progress, without locking anything.
- *
- * future->mutex must be locked for this call to be safe.
- */
-static inline double
-dmnsn_future_progress_unlocked(const dmnsn_future *future)
-{
-  return (double)future->progress/future->total;
-}
-
-// Get the current progress of the worker thread, in [0.0, 1.0]
-double
-dmnsn_future_progress(const dmnsn_future *future)
-{
-  dmnsn_future *mfuture = MUTATE(future);
-  double progress;
-
-  dmnsn_lock_mutex(&mfuture->mutex);
-    progress = dmnsn_future_progress_unlocked(mfuture);
-  dmnsn_unlock_mutex(&mfuture->mutex);
-
-  return progress;
-}
-
-// Find out whether the task is complete.
-bool
-dmnsn_future_is_done(const dmnsn_future *future)
-{
-  dmnsn_future *mfuture = MUTATE(future);
-  bool result;
-
-  dmnsn_lock_mutex(&mfuture->mutex);
-    result = future->progress == future->total;
-  dmnsn_unlock_mutex(&mfuture->mutex);
-
-  return result;
-}
-
-// Wait until dmnsn_future_progress(future) >= progress
-void
-dmnsn_future_wait(const dmnsn_future *future, double progress)
-{
-  dmnsn_future *mfuture = MUTATE(future);
-
-  dmnsn_lock_mutex(&mfuture->mutex);
-    while (dmnsn_future_progress_unlocked(mfuture) < progress) {
-      // Set the minimum waited-on value
-      if (progress < mfuture->min_wait) {
-        mfuture->min_wait = progress;
-      }
-
-      dmnsn_cond_wait_safely(&mfuture->cond, &mfuture->mutex);
-    }
-  dmnsn_unlock_mutex(&mfuture->mutex);
-}
-
-// Pause all threads working on a future.
-void
-dmnsn_future_pause(dmnsn_future *future)
-{
-  dmnsn_lock_mutex(&future->mutex);
-    while (future->nrunning < future->nthreads) {
-      dmnsn_cond_wait_safely(&future->all_running_cond, &future->mutex);
-    }
-    ++future->npaused;
-    while (future->nrunning > 0) {
-      dmnsn_cond_wait_safely(&future->none_running_cond, &future->mutex);
-    }
-  dmnsn_unlock_mutex(&future->mutex);
-}
-
-// Resume all threads working on a future.
-void
-dmnsn_future_resume(dmnsn_future *future)
-{
-  dmnsn_lock_mutex(&future->mutex);
-    dmnsn_assert(future->npaused > 0, "dmnsn_future_resume() without matching dmnsn_future_pause()");
-    if (--future->npaused == 0) {
-      dmnsn_cond_broadcast(&future->resume_cond);
-    }
-  dmnsn_unlock_mutex(&future->mutex);
-}
-
-// Set the total number of loop iterations
-void
-dmnsn_future_set_total(dmnsn_future *future, size_t total)
-{
-  dmnsn_lock_mutex(&future->mutex);
-    future->total = total;
-  dmnsn_unlock_mutex(&future->mutex);
-}
-
-static void
-dmnsn_future_increment_cleanup(void *ptr)
-{
-  dmnsn_future *future = ptr;
-  ++future->nrunning;
-  dmnsn_unlock_mutex_impl(&future->mutex);
-}
-
-// Increment the number of completed loop iterations
-void
-dmnsn_future_increment(dmnsn_future *future)
-{
-  // Allow a thread to be canceled whenever it increments a future object --
-  // this is close to PTHREAD_CANCEL_ASYNCHRONOUS but allows consistent state
-  // on cancellation
-  pthread_testcancel();
-
-  dmnsn_lock_mutex(&future->mutex);
-    ++future->progress;
-
-    if (dmnsn_future_progress_unlocked(future) >= future->min_wait) {
-      future->min_wait = 1.0;
-      dmnsn_cond_broadcast(&future->cond);
-    }
-
-    if (future->npaused > 0) {
-      dmnsn_assert(future->nrunning > 0, "More worker threads than expected");
-
-      if (--future->nrunning == 0) {
-        dmnsn_cond_broadcast(&future->none_running_cond);
-      }
-
-      pthread_cleanup_push(dmnsn_future_increment_cleanup, future);
-        do {
-          dmnsn_cond_wait(&future->resume_cond, &future->mutex);
-        } while (future->npaused > 0);
-      pthread_cleanup_pop(false);
-
-      if (++future->nrunning == future->nthreads) {
-        dmnsn_cond_broadcast(&future->all_running_cond);
-      }
-    }
-  dmnsn_unlock_mutex(&future->mutex);
-}
-
-// Immediately set to 100% completion
-void
-dmnsn_future_finish(dmnsn_future *future)
-{
-  dmnsn_lock_mutex(&future->mutex);
-    future->progress = future->total;
-    future->nthreads = future->nrunning = 0;
-    dmnsn_cond_broadcast(&future->cond);
-    dmnsn_cond_broadcast(&future->none_running_cond);
-    dmnsn_cond_broadcast(&future->all_running_cond);
-  dmnsn_unlock_mutex(&future->mutex);
-}
-
-// Set the number of threads
-void
-dmnsn_future_set_nthreads(dmnsn_future *future, unsigned int nthreads)
-{
-  dmnsn_lock_mutex(&future->mutex);
-    dmnsn_assert(future->nrunning == future->nthreads,
-                 "dmnsn_future_set_nthreads() called with paused threads");
-    future->nthreads = future->nrunning = nthreads;
-  dmnsn_unlock_mutex(&future->mutex);
-}
-
-// Notify completion of a worker thread
-void
-dmnsn_future_finish_thread(dmnsn_future *future)
-{
-  dmnsn_lock_mutex(&future->mutex);
-    dmnsn_assert(future->nthreads > 0,
-                 "dmnsn_future_finish_thread() called with no threads");
-    --future->nthreads;
-
-    dmnsn_assert(future->nrunning > 0,
-                 "dmnsn_future_finish_thread() called with no running threads");
-    if (--future->nrunning == 0) {
-      dmnsn_cond_broadcast(&future->none_running_cond);
-    }
-  dmnsn_unlock_mutex(&future->mutex);
-}
diff --git a/libdimension/geometry.c b/libdimension/geometry.c
deleted file mode 100644
index 4a16a3c..0000000
--- a/libdimension/geometry.c
+++ /dev/null
@@ -1,387 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Geometrical function implementations.
- */
-
-#include "dimension-internal.h"
-#include <math.h>
-
-// Identity matrix
-dmnsn_matrix
-dmnsn_identity_matrix(void)
-{
-  return dmnsn_new_matrix(1.0, 0.0, 0.0, 0.0,
-                          0.0, 1.0, 0.0, 0.0,
-                          0.0, 0.0, 1.0, 0.0);
-}
-
-// Scaling matrix
-dmnsn_matrix
-dmnsn_scale_matrix(dmnsn_vector s)
-{
-  return dmnsn_new_matrix(s.x, 0.0, 0.0, 0.0,
-                          0.0, s.y, 0.0, 0.0,
-                          0.0, 0.0, s.z, 0.0);
-}
-
-// Translation matrix
-dmnsn_matrix
-dmnsn_translation_matrix(dmnsn_vector d)
-{
-  return dmnsn_new_matrix(1.0, 0.0, 0.0, d.x,
-                          0.0, 1.0, 0.0, d.y,
-                          0.0, 0.0, 1.0, d.z);
-}
-
-// Left-handed rotation matrix; theta/|theta| = axis, |theta| = angle
-dmnsn_matrix
-dmnsn_rotation_matrix(dmnsn_vector theta)
-{
-  // Two trig calls, 25 multiplications, 13 additions
-
-  double angle = dmnsn_vector_norm(theta);
-  if (fabs(angle) < dmnsn_epsilon) {
-    return dmnsn_identity_matrix();
-  }
-  dmnsn_vector axis = dmnsn_vector_div(theta, angle);
-
-  // Shorthand to make dmnsn_new_matrix() call legible
-
-  double s = sin(angle);
-  double t = 1.0 - cos(angle);
-
-  double x = axis.x;
-  double y = axis.y;
-  double z = axis.z;
-
-  return dmnsn_new_matrix(
-    1.0 + t*(x*x - 1.0), -z*s + t*x*y,        y*s + t*x*z,         0.0,
-    z*s + t*x*y,         1.0 + t*(y*y - 1.0), -x*s + t*y*z,        0.0,
-    -y*s + t*x*z,        x*s + t*y*z,         1.0 + t*(z*z - 1.0), 0.0
-  );
-}
-
-// Find the angle between two vectors with respect to an axis
-static double
-dmnsn_axis_angle(dmnsn_vector from, dmnsn_vector to, dmnsn_vector axis)
-{
-  from = dmnsn_vector_sub(from, dmnsn_vector_proj(from, axis));
-  to   = dmnsn_vector_sub(to,   dmnsn_vector_proj(to, axis));
-
-  double fromnorm = dmnsn_vector_norm(from);
-  double tonorm   = dmnsn_vector_norm(to);
-  if (fromnorm < dmnsn_epsilon || tonorm < dmnsn_epsilon) {
-    return 0.0;
-  }
-
-  from = dmnsn_vector_div(from, fromnorm);
-  to   = dmnsn_vector_div(to,   tonorm);
-
-  double angle = acos(dmnsn_vector_dot(from, to));
-
-  if (dmnsn_vector_dot(dmnsn_vector_cross(from, to), axis) > 0.0) {
-    return angle;
-  } else {
-    return -angle;
-  }
-}
-
-// Alignment matrix
-dmnsn_matrix
-dmnsn_alignment_matrix(dmnsn_vector from, dmnsn_vector to,
-                       dmnsn_vector axis1, dmnsn_vector axis2)
-{
-  double theta1 = dmnsn_axis_angle(from, to, axis1);
-  dmnsn_matrix align1 = dmnsn_rotation_matrix(dmnsn_vector_mul(theta1, axis1));
-  from  = dmnsn_transform_direction(align1, from);
-  axis2 = dmnsn_transform_direction(align1, axis2);
-
-  double theta2 = dmnsn_axis_angle(from, to, axis2);
-  dmnsn_matrix align2 = dmnsn_rotation_matrix(dmnsn_vector_mul(theta2, axis2));
-
-  return dmnsn_matrix_mul(align2, align1);
-}
-
-// Matrix inversion helper functions
-
-/// A 2x2 matrix for inversion by partitioning.
-typedef struct { double n[2][2]; } dmnsn_matrix2;
-
-/// Construct a 2x2 matrix.
-static dmnsn_matrix2 dmnsn_new_matrix2(double a1, double a2,
-                                       double b1, double b2);
-/// Invert a 2x2 matrix.
-static dmnsn_matrix2 dmnsn_matrix2_inverse(dmnsn_matrix2 A);
-/// Negate a 2x2 matrix.
-static dmnsn_matrix2 dmnsn_matrix2_negate(dmnsn_matrix2 A);
-/// Subtract two 2x2 matricies.
-static dmnsn_matrix2 dmnsn_matrix2_sub(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs);
-/// Add two 2x2 matricies.
-static dmnsn_matrix2 dmnsn_matrix2_mul(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs);
-
-/// Invert a matrix with the slower cofactor algorithm, if partitioning failed.
-static dmnsn_matrix dmnsn_matrix_inverse_generic(dmnsn_matrix A);
-/// Get the [\p row, \p col] cofactor of A.
-static double dmnsn_matrix_cofactor(dmnsn_matrix A, size_t row, size_t col);
-
-// Invert a matrix, by partitioning
-dmnsn_matrix
-dmnsn_matrix_inverse(dmnsn_matrix A)
-{
-  // Use partitioning to invert a matrix:
-  //
-  //     [ P Q ] -1
-  //     [ R S ]
-  //
-  //   = [ PP QQ ]
-  //     [ RR SS ],
-  //
-  // with PP = inv(P) - inv(P)*Q*RR,
-  //      QQ = -inv(P)*Q*SS,
-  //      RR = -SS*R*inv(P), and
-  //      SS = inv(S - R*inv(P)*Q).
-
-  // The algorithm uses 2 inversions, 6 multiplications, and 2 subtractions,
-  // giving 52 multiplications, 34 additions, and 8 divisions.
-
-  dmnsn_matrix2 P, Q, R, S, Pi, RPi, PiQ, RPiQ, PP, QQ, RR, SS;
-  double Pdet = A.n[0][0]*A.n[1][1] - A.n[0][1]*A.n[1][0];
-
-  if (dmnsn_unlikely(fabs(Pdet) < dmnsn_epsilon)) {
-    // If P is close to singular, try a more generic algorithm; this is very
-    // unlikely, but not impossible, eg.
-    //   [ 1 1 0 0 ]
-    //   [ 1 1 1 0 ]
-    //   [ 0 1 1 0 ]
-    //   [ 0 0 0 1 ]
-    return dmnsn_matrix_inverse_generic(A);
-  }
-
-  // Partition the matrix
-  P = dmnsn_new_matrix2(A.n[0][0], A.n[0][1],
-                        A.n[1][0], A.n[1][1]);
-  Q = dmnsn_new_matrix2(A.n[0][2], A.n[0][3],
-                        A.n[1][2], A.n[1][3]);
-  R = dmnsn_new_matrix2(A.n[2][0], A.n[2][1],
-                        0.0,       0.0);
-  S = dmnsn_new_matrix2(A.n[2][2], A.n[2][3],
-                        0.0,       1.0);
-
-  // Do this inversion ourselves, since we already have the determinant
-  Pi = dmnsn_new_matrix2( P.n[1][1]/Pdet, -P.n[0][1]/Pdet,
-                         -P.n[1][0]/Pdet,  P.n[0][0]/Pdet);
-
-  // Calculate R*inv(P), inv(P)*Q, and R*inv(P)*Q
-  RPi  = dmnsn_matrix2_mul(R, Pi);
-  PiQ  = dmnsn_matrix2_mul(Pi, Q);
-  RPiQ = dmnsn_matrix2_mul(R, PiQ);
-
-  // Calculate the partitioned inverse
-  SS = dmnsn_matrix2_inverse(dmnsn_matrix2_sub(S, RPiQ));
-  RR = dmnsn_matrix2_negate(dmnsn_matrix2_mul(SS, RPi));
-  QQ = dmnsn_matrix2_negate(dmnsn_matrix2_mul(PiQ, SS));
-  PP = dmnsn_matrix2_sub(Pi, dmnsn_matrix2_mul(PiQ, RR));
-
-  // Reconstruct the matrix
-  return dmnsn_new_matrix(PP.n[0][0], PP.n[0][1], QQ.n[0][0], QQ.n[0][1],
-                          PP.n[1][0], PP.n[1][1], QQ.n[1][0], QQ.n[1][1],
-                          RR.n[0][0], RR.n[0][1], SS.n[0][0], SS.n[0][1]);
-}
-
-// For nice shorthand
-static dmnsn_matrix2
-dmnsn_new_matrix2(double a1, double a2, double b1, double b2)
-{
-  dmnsn_matrix2 m = { { { a1, a2 },
-                        { b1, b2 } } };
-  return m;
-}
-
-// Invert a 2x2 matrix
-static dmnsn_matrix2
-dmnsn_matrix2_inverse(dmnsn_matrix2 A)
-{
-  // 4 divisions, 2 multiplications, 1 addition
-  double det = A.n[0][0]*A.n[1][1] - A.n[0][1]*A.n[1][0];
-  return dmnsn_new_matrix2( A.n[1][1]/det, -A.n[0][1]/det,
-                           -A.n[1][0]/det,  A.n[0][0]/det);
-}
-
-// Also basically a shorthand
-static dmnsn_matrix2
-dmnsn_matrix2_negate(dmnsn_matrix2 A)
-{
-  return dmnsn_new_matrix2(-A.n[0][0], -A.n[0][1],
-                           -A.n[1][0], -A.n[1][1]);
-}
-
-// 2x2 matrix subtraction
-static dmnsn_matrix2
-dmnsn_matrix2_sub(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs)
-{
-  // 4 additions
-  return dmnsn_new_matrix2(
-    lhs.n[0][0] - rhs.n[0][0], lhs.n[0][1] - rhs.n[0][1],
-    lhs.n[1][0] - rhs.n[1][0], lhs.n[1][1] - rhs.n[1][1]
-  );
-}
-
-// 2x2 matrix multiplication
-static dmnsn_matrix2
-dmnsn_matrix2_mul(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs)
-{
-  // 8 multiplications, 4 additions
-  return dmnsn_new_matrix2(
-    lhs.n[0][0]*rhs.n[0][0] + lhs.n[0][1]*rhs.n[1][0],
-      lhs.n[0][0]*rhs.n[0][1] + lhs.n[0][1]*rhs.n[1][1],
-    lhs.n[1][0]*rhs.n[0][0] + lhs.n[1][1]*rhs.n[1][0],
-      lhs.n[1][0]*rhs.n[0][1] + lhs.n[1][1]*rhs.n[1][1]
-  );
-}
-
-// Invert a matrix, if partitioning failed (|P| == 0)
-static dmnsn_matrix
-dmnsn_matrix_inverse_generic(dmnsn_matrix A)
-{
-  // For A = [ A'      b ]  A^-1 = [ A'^-1   -(A'^-1)*b ]
-  //         [ 0 ... 0 1 ],        [ 0 ... 0      1     ].
-  //
-  // Invert A' by calculating its adjucate.
-  dmnsn_matrix inv;
-  double det = 0.0, C;
-
-  // Perform a Laplace expansion along the first row to give us the adjugate's
-  // first column and the determinant
-  for (size_t j = 0; j < 3; ++j) {
-    C = dmnsn_matrix_cofactor(A, 0, j);
-    det += A.n[0][j]*C;
-    inv.n[j][0] = C;
-  }
-
-  // Divide the first column by the determinant
-  for (size_t j = 0; j < 3; ++j) {
-    inv.n[j][0] /= det;
-  }
-
-  // Find the rest of A'
-  for (size_t j = 0; j < 3; ++j) {
-    for (size_t i = 1; i < 3; ++i) {
-      inv.n[j][i] = dmnsn_matrix_cofactor(A, i, j)/det;
-    }
-    inv.n[j][3] = 0.0;
-  }
-
-  // Find the translational component of the inverse
-  for (size_t i = 0; i < 3; ++i) {
-    for (size_t j = 0; j < 3; ++j) {
-      inv.n[i][3] -= inv.n[i][j]*A.n[j][3];
-    }
-  }
-
-  return inv;
-}
-
-// Gives the cofactor at row, col; the determinant of the matrix formed from the
-// upper-left 3x3 corner of A by ignoring row `row' and column `col',
-// times (-1)^(row + col)
-static double
-dmnsn_matrix_cofactor(dmnsn_matrix A, size_t row, size_t col)
-{
-  // 2 multiplications, 1 addition
-  double n[4];
-  size_t k = 0;
-  for (size_t i = 0; i < 3; ++i) {
-    for (size_t j = 0; j < 3; ++j) {
-      if (i != row && j != col) {
-        n[k] = A.n[i][j];
-        ++k;
-      }
-    }
-  }
-
-  double C = n[0]*n[3] - n[1]*n[2];
-  if ((row + col)%2 == 0) {
-    return C;
-  } else {
-    return -C;
-  }
-}
-
-// 4x4 matrix multiplication
-dmnsn_matrix
-dmnsn_matrix_mul(dmnsn_matrix lhs, dmnsn_matrix rhs)
-{
-  // 36 multiplications, 27 additions
-  dmnsn_matrix r;
-
-  r.n[0][0] = lhs.n[0][0]*rhs.n[0][0] + lhs.n[0][1]*rhs.n[1][0] + lhs.n[0][2]*rhs.n[2][0];
-  r.n[0][1] = lhs.n[0][0]*rhs.n[0][1] + lhs.n[0][1]*rhs.n[1][1] + lhs.n[0][2]*rhs.n[2][1];
-  r.n[0][2] = lhs.n[0][0]*rhs.n[0][2] + lhs.n[0][1]*rhs.n[1][2] + lhs.n[0][2]*rhs.n[2][2];
-  r.n[0][3] = lhs.n[0][0]*rhs.n[0][3] + lhs.n[0][1]*rhs.n[1][3] + lhs.n[0][2]*rhs.n[2][3] + lhs.n[0][3];
-
-  r.n[1][0] = lhs.n[1][0]*rhs.n[0][0] + lhs.n[1][1]*rhs.n[1][0] + lhs.n[1][2]*rhs.n[2][0];
-  r.n[1][1] = lhs.n[1][0]*rhs.n[0][1] + lhs.n[1][1]*rhs.n[1][1] + lhs.n[1][2]*rhs.n[2][1];
-  r.n[1][2] = lhs.n[1][0]*rhs.n[0][2] + lhs.n[1][1]*rhs.n[1][2] + lhs.n[1][2]*rhs.n[2][2];
-  r.n[1][3] = lhs.n[1][0]*rhs.n[0][3] + lhs.n[1][1]*rhs.n[1][3] + lhs.n[1][2]*rhs.n[2][3] + lhs.n[1][3];
-
-  r.n[2][0] = lhs.n[2][0]*rhs.n[0][0] + lhs.n[2][1]*rhs.n[1][0] + lhs.n[2][2]*rhs.n[2][0];
-  r.n[2][1] = lhs.n[2][0]*rhs.n[0][1] + lhs.n[2][1]*rhs.n[1][1] + lhs.n[2][2]*rhs.n[2][1];
-  r.n[2][2] = lhs.n[2][0]*rhs.n[0][2] + lhs.n[2][1]*rhs.n[1][2] + lhs.n[2][2]*rhs.n[2][2];
-  r.n[2][3] = lhs.n[2][0]*rhs.n[0][3] + lhs.n[2][1]*rhs.n[1][3] + lhs.n[2][2]*rhs.n[2][3] + lhs.n[2][3];
-
-  return r;
-}
-
-// Give an axis-aligned box that contains the given box transformed by `lhs'
-dmnsn_bounding_box
-dmnsn_transform_bounding_box(dmnsn_matrix trans, dmnsn_bounding_box box)
-{
-  // Infinite/zero bounding box support
-  if (isinf(box.min.x)) {
-    return box;
-  }
-
-  // Taking the "absolute value" of the matrix saves some min/max calculations
-  for (int i = 0; i < 3; ++i) {
-    for (int j = 0; j < 3; ++j) {
-      trans.n[i][j] = fabs(trans.n[i][j]);
-    }
-  }
-
-  dmnsn_vector Mt = dmnsn_matrix_column(trans, 3);
-  dmnsn_bounding_box ret = { Mt, Mt };
-
-  dmnsn_vector Mz = dmnsn_matrix_column(trans, 2);
-  ret.min = dmnsn_vector_add(ret.min, dmnsn_vector_mul(box.min.z, Mz));
-  ret.max = dmnsn_vector_add(ret.max, dmnsn_vector_mul(box.max.z, Mz));
-
-  dmnsn_vector My = dmnsn_matrix_column(trans, 1);
-  ret.min = dmnsn_vector_add(ret.min, dmnsn_vector_mul(box.min.y, My));
-  ret.max = dmnsn_vector_add(ret.max, dmnsn_vector_mul(box.max.y, My));
-
-  dmnsn_vector Mx = dmnsn_matrix_column(trans, 0);
-  ret.min = dmnsn_vector_add(ret.min, dmnsn_vector_mul(box.min.x, Mx));
-  ret.max = dmnsn_vector_add(ret.max, dmnsn_vector_mul(box.max.x, Mx));
-
-  return ret;
-}
diff --git a/libdimension/gl-stubs.c b/libdimension/gl-stubs.c
deleted file mode 100644
index df31308..0000000
--- a/libdimension/gl-stubs.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Stubs for GL functions when compiled with --disable-gl.
- */
-
-#include "dimension.h"
-#include <errno.h>
-
-int
-dmnsn_gl_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
-{
-  errno = ENOSYS;
-  return -1;
-}
-
-int
-dmnsn_gl_write_canvas(const dmnsn_canvas *canvas)
-{
-  errno = ENOSYS;
-  return -1;
-}
-
-int
-dmnsn_gl_read_canvas(dmnsn_canvas *canvas, size_t x0, size_t y0)
-{
-  errno = ENOSYS;
-  return NULL;
-}
diff --git a/libdimension/gl.c b/libdimension/gl.c
deleted file mode 100644
index 77099d2..0000000
--- a/libdimension/gl.c
+++ /dev/null
@@ -1,111 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * OpenGL import/export.
- */
-
-#include "dimension-internal.h"
-#include <GL/gl.h>
-#include <stdlib.h>
-#include <stdint.h>
-
-// Optimize canvas for GL drawing
-int
-dmnsn_gl_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
-{
-  dmnsn_rgba8_optimize_canvas(pool, canvas);
-  return 0;
-}
-
-// Write canvas to GL framebuffer.  Returns 0 on success, nonzero on failure
-int
-dmnsn_gl_write_canvas(const dmnsn_canvas *canvas)
-{
-  size_t width = canvas->width;
-  size_t height = canvas->height;
-
-  // Check if we can optimize this
-  dmnsn_rgba8_optimizer *rgba8 = (dmnsn_rgba8_optimizer *)dmnsn_canvas_find_optimizer(canvas, dmnsn_rgba8_optimizer_fn);
-  if (rgba8) {
-    glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, rgba8->data);
-    return glGetError() == GL_NO_ERROR ? 0 : 1;
-  }
-
-  // We couldn't, so transform the canvas to RGB now
-  GLubyte *pixels = dmnsn_malloc(4*width*height*sizeof(GLubyte));
-
-  for (size_t y = 0; y < height; ++y) {
-    for (size_t x = 0; x < width; ++x) {
-      GLubyte *pixel = pixels + 4*(y*width + x);
-
-      dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(canvas, x, y);
-      tcolor = dmnsn_tcolor_remove_filter(tcolor);
-      tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
-      tcolor = dmnsn_tcolor_clamp(tcolor);
-
-      pixel[0] = lround(tcolor.c.R*UINT8_MAX);
-      pixel[1] = lround(tcolor.c.G*UINT8_MAX);
-      pixel[2] = lround(tcolor.c.B*UINT8_MAX);
-      pixel[3] = lround(tcolor.T*UINT8_MAX);
-    }
-  }
-
-  glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
-
-  dmnsn_free(pixels);
-  return glGetError() == GL_NO_ERROR ? 0 : 1;
-}
-
-// Read a canvas from a GL framebuffer.  Returns NULL on failure.
-int
-dmnsn_gl_read_canvas(dmnsn_canvas *canvas, size_t x0, size_t y0)
-{
-  size_t width = canvas->width;
-  size_t height = canvas->height;
-
-  // Array of 16-bit ints in RGBA order
-  GLushort *pixels = dmnsn_malloc(4*width*height*sizeof(GLushort));
-  glReadPixels(x0, y0, width, height, GL_RGBA, GL_UNSIGNED_SHORT, pixels);
-  if (glGetError() != GL_NO_ERROR) {
-    dmnsn_free(pixels);
-    return -1;
-  }
-
-  for (size_t y = 0; y < height; ++y) {
-    for (size_t x = 0; x < width; ++x) {
-      GLushort *pixel = pixels + 4*(y*width + x);
-
-      dmnsn_tcolor tcolor = dmnsn_new_tcolor5(
-        (double)pixel[0]/UINT16_MAX,
-        (double)pixel[1]/UINT16_MAX,
-        (double)pixel[2]/UINT16_MAX,
-        (double)pixel[3]/UINT16_MAX,
-        0.0
-      );
-      tcolor.c = dmnsn_color_from_sRGB(tcolor.c);
-      dmnsn_canvas_set_pixel(canvas, x, y, tcolor);
-    }
-  }
-
-  dmnsn_free(pixels);
-  return 0;
-}
diff --git a/libdimension/gradient.c b/libdimension/gradient.c
deleted file mode 100644
index 74e8045..0000000
--- a/libdimension/gradient.c
+++ /dev/null
@@ -1,56 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Gradient pattern.
- */
-
-#include "dimension.h"
-
-/// Gradient pattern type.
-typedef struct dmnns_gradient {
-  dmnsn_pattern pattern;
-  dmnsn_vector orientation;
-} dmnsn_gradient;
-
-/// Gradient pattern callback.
-static double
-dmnsn_gradient_pattern_fn(const dmnsn_pattern *pattern, dmnsn_vector v)
-{
-  const dmnsn_gradient *gradient = (const dmnsn_gradient *)pattern;
-  double n = fmod(dmnsn_vector_dot(gradient->orientation, v), 1.0);
-  if (n < -dmnsn_epsilon) {
-    n += 1.0;
-  }
-  return n;
-}
-
-dmnsn_pattern *
-dmnsn_new_gradient_pattern(dmnsn_pool *pool, dmnsn_vector orientation)
-{
-  dmnsn_gradient *gradient = DMNSN_PALLOC(pool, dmnsn_gradient);
-  gradient->orientation = dmnsn_vector_normalized(orientation);
-
-  dmnsn_pattern *pattern = &gradient->pattern;
-  dmnsn_init_pattern(pattern);
-  pattern->pattern_fn = dmnsn_gradient_pattern_fn;
-  return pattern;
-}
diff --git a/libdimension/inline.c b/libdimension/inline.c
deleted file mode 100644
index b0622fe..0000000
--- a/libdimension/inline.c
+++ /dev/null
@@ -1,43 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Emit definitions of inline functions, if necessary.
- */
-
-// Set DMNSN_INLINE to produce definitions of inline functions, emitted here,
-// if needed
-#ifdef __cplusplus
-  // C++ inline semantics
-  #define DMNSN_INLINE inline
-#elif __STDC_VERSION__ >= 199901L
-  // C99 inline semantics
-  #define DMNSN_INLINE
-#elif defined(__GNUC__)
-  // GCC inline semantics
-  #define DMNSN_INLINE __inline__
-#else
-  // Unknown C - mark functions static and hope the compiler is smart enough to
-  // inline them
-  #define DMNSN_INLINE static
-#endif
-
-#include "dimension.h"
diff --git a/libdimension/interior.c b/libdimension/interior.c
deleted file mode 100644
index d725b6f..0000000
--- a/libdimension/interior.c
+++ /dev/null
@@ -1,46 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Interiors.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-// Allocate an interior
-dmnsn_interior *
-dmnsn_new_interior(dmnsn_pool *pool)
-{
-  dmnsn_interior *interior = DMNSN_PALLOC(pool, dmnsn_interior);
-  interior->ior = 1.0;
-  return interior;
-}
-
-// Cascade an interior
-void
-dmnsn_interior_cascade(dmnsn_interior *default_interior,
-                       dmnsn_interior **interiorp)
-{
-  if (!*interiorp) {
-    *interiorp = default_interior;
-  }
-}
diff --git a/libdimension/internal.h b/libdimension/internal.h
new file mode 100644
index 0000000..3db2612
--- /dev/null
+++ b/libdimension/internal.h
@@ -0,0 +1,35 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * The basic internal libdimension API.  These functions and types are used to
+ * implement libdimension, but are not part of its public API.
+ */
+
+#ifndef DMNSN_INTERNAL_H
+#define DMNSN_INTERNAL_H
+
+#include "dimension/base.h"
+
+#include "internal/compiler.h"
+#include "internal/profile.h"
+
+#endif // DMNSN_INTERNAL_H
diff --git a/libdimension/internal/bvh.h b/libdimension/internal/bvh.h
new file mode 100644
index 0000000..c97e2ba
--- /dev/null
+++ b/libdimension/internal/bvh.h
@@ -0,0 +1,79 @@
+/*************************************************************************
+ * Copyright (C) 2012-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file.
+ * Bounding volume hierarchy.  This generic interface allows different data
+ * structures to be represented in the same way, thus sharing code for their
+ * traversal algorithm.
+ */
+
+#ifndef DMNSN_INTERNAL_BVH_H
+#define DMNSN_INTERNAL_BVH_H
+
+#include "internal.h"
+#include "dimension/math.h"
+#include "dimension/model.h"
+#include <stdbool.h>
+
+/// A bounding volume hierarchy.
+typedef struct dmnsn_bvh dmnsn_bvh;
+
+/// Available BVH implementations.
+typedef enum dmnsn_bvh_kind {
+  DMNSN_BVH_NONE,
+  DMNSN_BVH_PRTREE,
+} dmnsn_bvh_kind;
+
+/// Create a BVH.
+DMNSN_INTERNAL dmnsn_bvh *dmnsn_new_bvh(const dmnsn_array *objects,
+                                        dmnsn_bvh_kind kind);
+/// Delete a BVH.
+DMNSN_INTERNAL void dmnsn_delete_bvh(dmnsn_bvh *bvh);
+
+/// Find the closest ray-object intersection in the tree.
+DMNSN_INTERNAL bool dmnsn_bvh_intersection(const dmnsn_bvh *bvh, dmnsn_ray ray, dmnsn_intersection *intersection, bool reset);
+/// Determine whether a point is inside any object in the tree.
+DMNSN_INTERNAL bool dmnsn_bvh_inside(const dmnsn_bvh *bvh, dmnsn_vector point);
+/// Return the bounding box of the whole hierarchy.
+DMNSN_INTERNAL dmnsn_aabb dmnsn_bvh_aabb(const dmnsn_bvh *bvh);
+
+/// A non-flat BVH representation, used by BVH implementations.
+typedef struct dmnsn_bvh_node {
+  dmnsn_aabb aabb;                   ///< The bounding box of this node.
+  dmnsn_object *object;              ///< The object, for leaf nodes.
+  unsigned int nchildren;            ///< How many children this node has.
+  unsigned int max_children;         ///< Maximum number of children.
+  struct dmnsn_bvh_node *children[]; ///< Flexible array of children.
+} dmnsn_bvh_node;
+
+/// Create a BVH node.
+DMNSN_INTERNAL dmnsn_bvh_node *dmnsn_new_bvh_node(unsigned int max_children);
+
+/// Create a BVH leaf node.
+DMNSN_INTERNAL dmnsn_bvh_node *dmnsn_new_bvh_leaf_node(dmnsn_object *object);
+
+/// Delete a BVH node.
+DMNSN_INTERNAL void dmnsn_delete_bvh_node(dmnsn_bvh_node *node);
+
+/// Add a child to a BVH node.
+DMNSN_INTERNAL void dmnsn_bvh_node_add(dmnsn_bvh_node *parent, dmnsn_bvh_node *child);
+
+#endif // DMNSN_INTERNAL_BVH_H
diff --git a/libdimension/internal/compiler.h b/libdimension/internal/compiler.h
new file mode 100644
index 0000000..e3555a4
--- /dev/null
+++ b/libdimension/internal/compiler.h
@@ -0,0 +1,71 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Internally-used compiler abstractions.
+ */
+
+#ifndef DMNSN_INTERNAL_H
+#error "Please include \"internal.h\" instead of this header directly."
+#endif
+
+#include <stdalign.h>
+
+/**
+ * @def DMNSN_HOT
+ * Mark a function as a hot path.
+ */
+/**
+ * @def DMNSN_INTERNAL
+ * Mark a function as internal linkage.
+ */
+/**
+ * @def DMNSN_DESTRUCTOR
+ * Queue a function to run at program termination.
+ */
+/**
+ * @def DMNSN_LATE_DESTRUCTOR
+ * Queue a function to run at program termination, after those labeled
+ * DMNSN_DESTRUCTOR.
+ */
+#if DMNSN_GNUC
+  #define DMNSN_HOT             __attribute__((hot))
+  #define DMNSN_INTERNAL        __attribute__((visibility("hidden")))
+  #define DMNSN_DESTRUCTOR      __attribute__((destructor(102)))
+  #define DMNSN_LATE_DESTRUCTOR __attribute__((destructor(101)))
+#else
+  #define DMNSN_HOT
+  #define DMNSN_INTERNAL
+  #define DMNSN_DESTRUCTOR
+  #define DMNSN_LATE_DESTRUCTOR
+#endif
+
+/// Synonym for _Atomic that stdatomic.h doesn't define for some reason
+#define atomic _Atomic
+
+/// Nearly-compliant alloca variant
+#define DMNSN_ALLOCA(var, size) DMNSN_ALLOCA_IMPL(var, size, __LINE__)
+
+#define DMNSN_ALLOCA_IMPL(var, size, ctr) DMNSN_ALLOCA_IMPL2(var, size, ctr)
+
+#define DMNSN_ALLOCA_IMPL2(var, size, ctr)              \
+  alignas(max_align_t) char dmnsn_alloca##ctr[size];    \
+  var = (void *)dmnsn_alloca##ctr
diff --git a/libdimension/internal/concurrency.h b/libdimension/internal/concurrency.h
new file mode 100644
index 0000000..e9c024a
--- /dev/null
+++ b/libdimension/internal/concurrency.h
@@ -0,0 +1,236 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Internal threading abstraction.
+ */
+
+#ifndef DMNSN_INTERNAL_CONCURRENCY_H
+#define DMNSN_INTERNAL_CONCURRENCY_H
+
+#include "internal.h"
+#include "dimension/concurrency.h"
+#include <pthread.h>
+
+/// Allocate a new future object.
+DMNSN_INTERNAL dmnsn_future *dmnsn_new_future(void);
+
+/// Set the total number of loop iterations.
+DMNSN_INTERNAL void dmnsn_future_set_total(dmnsn_future *future, size_t total);
+/// Increment the progress of a background task.
+DMNSN_INTERNAL void dmnsn_future_increment(dmnsn_future *future);
+/// Instantly complete the background teask.
+DMNSN_INTERNAL void dmnsn_future_finish(dmnsn_future *future);
+/// Set the number of worker threads.
+DMNSN_INTERNAL void dmnsn_future_set_nthreads(dmnsn_future *future, unsigned int nthreads);
+/// Notify completion of a worker thread.
+DMNSN_INTERNAL void dmnsn_future_finish_thread(dmnsn_future *future);
+
+/**
+ * Thread callback type.
+ * @param[in,out] ptr  An arbitrary pointer.
+ * @return 0 on success, non-zero on failure.
+ */
+typedef int dmnsn_thread_fn(void *ptr);
+
+/**
+ * Create a thread that cleans up after itself on errors.
+ * @param[in,out] future     The future object to associate with the thread.
+ * @param[in]     thread_fn  The thread callback.
+ * @param[in,out] arg        The pointer to pass to the thread callback.
+ */
+DMNSN_INTERNAL void dmnsn_new_thread(dmnsn_future *future,
+                                     dmnsn_thread_fn *thread_fn, void *arg);
+
+/**
+ * Thread callback type for parallel tasks.
+ * @param[in,out] ptr       An arbitrary pointer.
+ * @param[in]     thread    An ID for this thread, in [0, \p nthreads).
+ * @param[in]     nthreads  The number of concurrent threads.
+ * @return 0 on success, non-zero on failure.
+ */
+typedef int dmnsn_ccthread_fn(void *ptr, unsigned int thread,
+                              unsigned int nthreads);
+
+/**
+ * Run \p nthreads threads in parallel.
+ * @param[in,out] future       The future object to associate with the threads,
+ *                             possibly NULL.
+ * @param[in]     ccthread_fn  The routine to run in each concurrent thread.
+ * @param[in,out] arg          The pointer to pass to the thread callbacks.
+ * @param[in]     nthreads     The number of concurrent threads to run.
+ * @return 0 if all threads were successful, and an error code otherwise.
+ */
+DMNSN_INTERNAL int dmnsn_execute_concurrently(dmnsn_future *future,
+                                              dmnsn_ccthread_fn *ccthread_fn,
+                                              void *arg, unsigned int nthreads);
+
+/**
+ * Initialize a mutex, bailing out on failure.
+ * @param[out] mutex  The mutex to initialize.
+ */
+DMNSN_INTERNAL void dmnsn_initialize_mutex(pthread_mutex_t *mutex);
+
+/// dmnsn_lock_mutex() implementation.
+DMNSN_INTERNAL void dmnsn_lock_mutex_impl(pthread_mutex_t *mutex);
+/// dmnsn_unlock_mutex() implementation.
+DMNSN_INTERNAL void dmnsn_unlock_mutex_impl(void *mutex);
+
+/**
+ * Lock a mutex, bailing out on failure.
+ * Contains a {, so must be used in the same block as dmnsn_unlock_mutex().
+ * @param[in,out] mutex  The mutex to lock.
+ */
+#define dmnsn_lock_mutex(mutex) do { dmnsn_lock_mutex_impl((mutex))
+
+/**
+ * Unlock a mutex, bailing out on failure.
+ * Contains a }, so must be used in the same block as dmnsn_lock_mutex().
+ * @param[in,out] mutex  The mutex to unlock.
+ */
+#define dmnsn_unlock_mutex(mutex) dmnsn_unlock_mutex_impl((mutex)); } while (0)
+
+/**
+ * Destroy a mutex, warning on failure.
+ * @param[in,out] mutex  The mutex to destroy.
+ */
+DMNSN_INTERNAL void dmnsn_destroy_mutex(pthread_mutex_t *mutex);
+
+/**
+ * Initialize a read-write lock, bailing out on failure.
+ * @param[out] rwlock  The read-write lock to initialize.
+ */
+DMNSN_INTERNAL void dmnsn_initialize_rwlock(pthread_rwlock_t *rwlock);
+
+/// dmnsn_read_lock() implementation.
+DMNSN_INTERNAL void dmnsn_read_lock_impl(pthread_rwlock_t *rwlock);
+/// dmnsn_write_lock() implementation.
+DMNSN_INTERNAL void dmnsn_write_lock_impl(pthread_rwlock_t *rwlock);
+/// dmnsn_unlock_rwlock() implementation.
+DMNSN_INTERNAL void dmnsn_unlock_rwlock_impl(pthread_rwlock_t *rwlock);
+
+/**
+ * Read-lock a read-write lock, bailing out on failure.
+ * Contains a {, so must be used in the same block as dmnsn_unlock_rwlock().
+ * @param[in,out] rwlock  The read-write lock to lock.
+ */
+#define dmnsn_read_lock(rwlock) do { dmnsn_read_lock_impl((rwlock))
+
+/**
+ * Write-lock a read-write lock, bailing out on failure.
+ * Contains a {, so must be used in the same block as dmnsn_unlock_rwlock().
+ * @param[in,out] rwlock  The read-write lock to lock.
+ */
+#define dmnsn_write_lock(rwlock) do { dmnsn_write_lock_impl((rwlock))
+
+/**
+ * Unlock a read-write lock, bailing out on failure.
+ * Contains a }, so must be used in the same block as dmnsn_read_lock() or
+ * dmnsn_write_lock().
+ * @param[in,out] rwlock  The read-write lock to lock.
+ */
+#define dmnsn_unlock_rwlock(rwlock)                     \
+  dmnsn_unlock_rwlock_impl((rwlock)); } while (0)
+
+/**
+ * Destroy a read-write lock, warning on failure.
+ * @param[in,out] rwlock  The read-write lock to destroy.
+ */
+DMNSN_INTERNAL void dmnsn_destroy_rwlock(pthread_rwlock_t *rwlock);
+
+/**
+ * Initialize a condition variable, bailing out on failure.
+ * @param[out] cond  The condition variable to initialize.
+ */
+DMNSN_INTERNAL void dmnsn_initialize_cond(pthread_cond_t *cond);
+
+/**
+ * Wait on a condition variable, bailing out on error.
+ * @param[in] cond   The condition variable to wait on.
+ * @param[in] mutex  The associated mutex.
+ */
+DMNSN_INTERNAL void dmnsn_cond_wait(pthread_cond_t *cond,
+                                    pthread_mutex_t *mutex);
+
+/**
+ * Wait on a condition variable, bailing out on error, and unlock the mutex if
+ * cancelled.
+ * @param[in] cond   The condition variable to wait on.
+ * @param[in] mutex  The associated mutex.
+ */
+#define dmnsn_cond_wait_safely(cond, mutex)                     \
+  do {                                                          \
+    pthread_cleanup_push(dmnsn_unlock_mutex_impl, (mutex));     \
+    dmnsn_cond_wait((cond), (mutex));                           \
+    pthread_cleanup_pop(false);                                 \
+  } while (0)
+
+/**
+ * Signal a condition variable, bailing out on error.
+ * @param[in] cond   The condition variable to signal.
+ */
+DMNSN_INTERNAL void dmnsn_cond_broadcast(pthread_cond_t *cond);
+
+/**
+ * Destroy a condition variable, warning on failure.
+ * @param[in,out] cond  The condition variable to destroy.
+ */
+DMNSN_INTERNAL void dmnsn_destroy_cond(pthread_cond_t *cond);
+
+/**
+ * Once-called callback type.
+ */
+typedef void dmnsn_once_fn(void);
+
+/**
+ * Call a function exactly once, bailing out on failure.
+ * @param[in,out] once     The once control.
+ * @param[in]     once_fn  The function to call.
+ */
+DMNSN_INTERNAL void dmnsn_once(pthread_once_t *once, dmnsn_once_fn *once_fn);
+
+/**
+ * Initialize a thread-local storage key, bailing out on failure.
+ * @param[out] key         The key to initialize.
+ * @param[in]  destructor  An optional destructor callback.
+ */
+DMNSN_INTERNAL void dmnsn_key_create(pthread_key_t *key, dmnsn_callback_fn *destructor);
+
+/**
+ * Set a thread-specific pointer, bailing out on failure.
+ * @param[in] key    The thread-local storage key.
+ * @param[in] value  The value to set.
+ */
+DMNSN_INTERNAL void dmnsn_setspecific(pthread_key_t key, const void *value);
+
+/**
+ * Destroy a thread-local storage key, warning on failure.
+ * @param[out] key         The key to destroy.
+ */
+DMNSN_INTERNAL void dmnsn_key_delete(pthread_key_t key);
+
+/**
+ * Join a thread, bailing out on failure.
+ * @param[in,out] thread  The thread to join.
+ */
+DMNSN_INTERNAL void dmnsn_join_thread(pthread_t thread, void **retval);
+
+#endif // DMNSN_INTERNAL_CONCURRENCY_H
diff --git a/libdimension/internal/future.h b/libdimension/internal/future.h
new file mode 100644
index 0000000..047523e
--- /dev/null
+++ b/libdimension/internal/future.h
@@ -0,0 +1,61 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Future object implementation.
+ */
+
+#ifndef DMNSN_INTERNAL_FUTURE_H
+#define DMNSN_INTERNAL_FUTURE_H
+
+#include <pthread.h>
+
+struct dmnsn_future {
+  size_t progress; ///< Completed loop iterations.
+  size_t total;    ///< Total expected loop iterations.
+
+  /// The worker thread.
+  pthread_t thread;
+
+  /// Mutex to guard progress and total.
+  pthread_mutex_t mutex;
+
+  /// Condition variable for waiting for a particular amount of progress.
+  pthread_cond_t cond;
+
+  /// Minimum waited-on value.
+  double min_wait;
+
+  /// Number of threads working on the future's background task.
+  unsigned int nthreads;
+  /// Number of threads not yet paused.
+  unsigned int nrunning;
+  /// Count of threads holding the future paused.
+  unsigned int npaused;
+  /// Condition variable for waiting for nrunning == 0.
+  pthread_cond_t none_running_cond;
+  /// Condition variable for waiting for nrunning == nthreads.
+  pthread_cond_t all_running_cond;
+  /// Condition variable for waiting for npaused == 0.
+  pthread_cond_t resume_cond;
+};
+
+#endif // DMNSN_INTERNAL_FUTURE_H
diff --git a/libdimension/internal/platform.h b/libdimension/internal/platform.h
new file mode 100644
index 0000000..e037d21
--- /dev/null
+++ b/libdimension/internal/platform.h
@@ -0,0 +1,67 @@
+/*************************************************************************
+ * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Internal platform abstractions.
+ */
+
+#ifndef DMNSN_INTERNAL_PLATFORM_H
+#define DMNSN_INTERNAL_PLATFORM_H
+
+#include "internal.h"
+#include "dimension/platform.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+/**
+ * Print a stack trace, if implemented for the current platform.
+ * @param[in,out] file  The file to which to write the stack trace.
+ */
+DMNSN_INTERNAL void dmnsn_backtrace(FILE *file);
+
+/**
+ * Is the calling thread the main thread?
+ * @return Whether this is the main execution thread, or \c true if we can't
+ *         tell.
+ */
+DMNSN_INTERNAL bool dmnsn_is_main_thread(void);
+
+/**
+ * Are we running on a little-endian computer?
+ * @return Whether the current architecture is little-endian.
+ */
+DMNSN_INTERNAL bool dmnsn_is_little_endian(void);
+
+/**
+ * How many CPUs are available?
+ * @return The number of CPUs available to dimension.
+ */
+DMNSN_INTERNAL size_t dmnsn_ncpus(void);
+
+/**
+ * Calculate process times.
+ * @param[out] timer  The timer in which to store the current total process
+ *                    times.
+ */
+DMNSN_INTERNAL void dmnsn_get_times(dmnsn_timer *timer);
+
+#endif // DMNSN_INTERNAL_PLATFORM_H
diff --git a/libdimension/internal/polynomial.h b/libdimension/internal/polynomial.h
new file mode 100644
index 0000000..7af3e5a
--- /dev/null
+++ b/libdimension/internal/polynomial.h
@@ -0,0 +1,85 @@
+/*************************************************************************
+ * Copyright (C) 2009-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Utility functions for working with and numerically solving polynomials.
+ * Polynomials are represented as simple arrays where the ith element is the
+ * coefficient on x^i.  In general, we are only interested in positive roots.
+ */
+
+#include "internal.h"
+#include <stddef.h>
+#include <stdio.h>
+
+/**
+ * Evaluate a polynomial at \p x.
+ * @param[in] poly    The coefficients of the polynomial to evaluate, in order
+ *                    from lowest degree to highest degree.  The array should
+ *                    have dimension <tt>degree + 1</tt>.
+ * @param[in] degree  The degree of the polynomial.
+ * @param[in] x       The value of the variable at which to evaluate.
+ */
+DMNSN_INTERNAL DMNSN_INLINE double
+dmnsn_polynomial_evaluate(const double poly[], size_t degree, double x)
+{
+  double ret = poly[degree];
+  size_t i;
+  for (i = degree; i-- > 0;) {
+    ret = ret*x + poly[i];
+  }
+  return ret;
+}
+
+/**
+ * Evaluate the derivative of a polynomial at \p x.
+ * @param[in] poly    The coefficients of the polynomial to evaluate.
+ * @param[in] degree  The degree of the polynomial.
+ * @param[in] x       The value of the variable at which to evaluate.
+ */
+DMNSN_INTERNAL DMNSN_INLINE double
+dmnsn_polynomial_evaluate_derivative(const double poly[], size_t degree, double x)
+{
+  double ret = poly[degree]*degree;
+  size_t i;
+  for (i = degree - 1; i >= 1; --i) {
+    ret = ret*x + poly[i]*i;
+  }
+  return ret;
+}
+
+/**
+ * Find the positive roots of a polynomial.
+ * @param[in]  poly    The coefficients of the polynomial to solve.
+ * @param[in]  degree  The degree of the polynomial.
+ * @param[out] x       An array in which to store the roots.  It should have
+ *                     dimension \p degree.
+ * @return The number of positive roots stored in \c x[].
+ */
+DMNSN_INTERNAL size_t dmnsn_polynomial_solve(const double poly[], size_t degree, double x[]);
+
+/**
+ * Output a polynomial.  The polynomial is printed as a function of x suitable
+ * for input into a CAS, and without a trailing newline.
+ * @param[in,out] file    The file to write to.
+ * @param[in]     poly    The coefficients of the polynomial to print.
+ * @param[in]     degree  The degree of the polynomial.
+ */
+DMNSN_INTERNAL void dmnsn_polynomial_print(FILE *file, const double poly[], size_t degree);
diff --git a/libdimension/internal/profile.h b/libdimension/internal/profile.h
new file mode 100644
index 0000000..0f59b8f
--- /dev/null
+++ b/libdimension/internal/profile.h
@@ -0,0 +1,68 @@
+/*************************************************************************
+ * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Built-in branch profiler.
+ */
+
+#ifndef DMNSN_INTERNAL_H
+#error "Please include \"internal.h\" instead of this header directly."
+#endif
+
+#include <stdbool.h>
+
+/**
+ * @def dmnsn_likely
+ * Indicate that a test is likely to succeed.
+ * @param test  The test to perform.
+ * @return The truth value of \p test.
+ */
+/**
+ * @def dmnsn_unlikely
+ * Indicate that a test is unlikely to succeed.
+ * @param test  The test to perform.
+ * @return The truth value of \p test.
+ */
+#ifdef DMNSN_PROFILE
+  #define dmnsn_likely(test)                                      \
+    dmnsn_expect(!!(test), true, DMNSN_FUNC, __FILE__, __LINE__)
+  #define dmnsn_unlikely(test)                                    \
+    dmnsn_expect(!!(test), false, DMNSN_FUNC, __FILE__, __LINE__)
+#elif DMNSN_GNUC
+  #define dmnsn_likely(test)   __builtin_expect(!!(test), true)
+  #define dmnsn_unlikely(test) __builtin_expect(!!(test), false)
+#else
+  #define dmnsn_likely(test)   (!!(test))
+  #define dmnsn_unlikely(test) (!!(test))
+#endif
+
+/**
+ * Record a test and its expected result.  Called by dmnsn_[un]likely();
+ * don't call directly.
+ * @param[in] result    The result of the test.
+ * @param[in] expected  The expected result of the test.
+ * @param[in] func      The name of the function the test occurs in.
+ * @param[in] file      The name of the file the test occurs in.
+ * @param[in] line      The line number on which the test occurs.
+ * @return \p result.
+ */
+DMNSN_INTERNAL bool dmnsn_expect(bool result, bool expected, const char *func,
+                                 const char *file, unsigned int line);
diff --git a/libdimension/internal/prtree.h b/libdimension/internal/prtree.h
new file mode 100644
index 0000000..7d85f44
--- /dev/null
+++ b/libdimension/internal/prtree.h
@@ -0,0 +1,38 @@
+/*************************************************************************
+ * Copyright (C) 2010-2012 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file.
+ * Priority R-trees.  PR-trees are a data structure introduced by Arge, de Berg,
+ * Haverkort, and Yi, which provides asymptotically optimal worst-case lookup,
+ * while remaining efficient with real-world data.  Their structure is derived
+ * from B-trees.
+ */
+
+#ifndef DMNSN_INTERNAL_PRTREE_H
+#define DMNSN_INTERNAL_PRTREE_H
+
+#include "internal.h"
+#include "internal/bvh.h"
+
+/// Create a PR-tree.
+DMNSN_INTERNAL dmnsn_bvh_node *dmnsn_new_prtree(const dmnsn_array *objects);
+
+#endif // DMNSN_INTERNAL_PRTREE_H
diff --git a/libdimension/internal/rgba.h b/libdimension/internal/rgba.h
new file mode 100644
index 0000000..964d622
--- /dev/null
+++ b/libdimension/internal/rgba.h
@@ -0,0 +1,55 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * RGBA canvas optimizer interface, used by image optimizers.
+ */
+
+#ifndef DMNSN_INTERNAL_RGBA_H
+#define DMNSN_INTERNAL_RGBA_H
+
+#include "internal.h"
+#include "dimension/canvas.h"
+#include <stdint.h>
+
+/// RGBA8 optimizer type.
+typedef struct dmnsn_rgba8_optimizer {
+  dmnsn_canvas_optimizer optimizer;
+  uint8_t data[];
+} dmnsn_rgba8_optimizer;
+
+/// RGBA16 optimizer type.
+typedef struct dmnsn_rgba16_optimizer {
+  dmnsn_canvas_optimizer optimizer;
+  uint16_t data[];
+} dmnsn_rgba16_optimizer;
+
+/// Apply the RGBA8 optimizer to a canvas.
+DMNSN_INTERNAL void dmnsn_rgba8_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
+/// Apply the RGBA16 optimizer to a canvas.
+DMNSN_INTERNAL void dmnsn_rgba16_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
+
+/// RGBA8 optimizer callback.
+DMNSN_INTERNAL void dmnsn_rgba8_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y);
+/// RGBA16 optimizer callback.
+DMNSN_INTERNAL void dmnsn_rgba16_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y);
+
+#endif // DMNSN_INTERNAL_RGBA_H
diff --git a/libdimension/lambertian.c b/libdimension/lambertian.c
deleted file mode 100644
index cced4a7..0000000
--- a/libdimension/lambertian.c
+++ /dev/null
@@ -1,57 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Diffuse finish.
- */
-
-#include "dimension.h"
-#include <math.h>
-#include <stdlib.h>
-
-/// Lambertian diffuse type.
-typedef struct dmnsn_lambertian {
-  dmnsn_diffuse diffuse;
-  double coeff;
-} dmnsn_lambertian;
-
-/// Diffuse finish callback.
-static dmnsn_color
-dmnsn_lambertian_diffuse_fn(const dmnsn_diffuse *diffuse,
-                            dmnsn_color light, dmnsn_color color,
-                            dmnsn_vector ray, dmnsn_vector normal)
-{
-  const dmnsn_lambertian *lambertian = (const dmnsn_lambertian *)diffuse;
-  double diffuse_factor = fabs((lambertian->coeff)*dmnsn_vector_dot(ray, normal));
-  return dmnsn_color_mul(diffuse_factor, dmnsn_color_illuminate(light, color));
-}
-
-dmnsn_diffuse *
-dmnsn_new_lambertian(dmnsn_pool *pool, double coeff)
-{
-  dmnsn_lambertian *lambertian = DMNSN_PALLOC(pool, dmnsn_lambertian);
-  lambertian->coeff = coeff;
-
-  dmnsn_diffuse *diffuse = &lambertian->diffuse;
-  dmnsn_init_diffuse(diffuse);
-  diffuse->diffuse_fn = dmnsn_lambertian_diffuse_fn;
-  return diffuse;
-}
diff --git a/libdimension/leopard.c b/libdimension/leopard.c
deleted file mode 100644
index a4000d2..0000000
--- a/libdimension/leopard.c
+++ /dev/null
@@ -1,46 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2011-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Leopard pattern.
- */
-
-#include "dimension-internal.h"
-#include <math.h>
-
-/// Leopard pattern callback.
-static double
-dmnsn_leopard_pattern_fn(const dmnsn_pattern *leopard, dmnsn_vector v)
-{
-  double val = (sin(v.x) + sin(v.y) + sin(v.z))/3.0;
-  return val*val;
-}
-
-/// The singleton instance.
-static dmnsn_pattern dmnsn_leopard_instance = {
-  .pattern_fn = dmnsn_leopard_pattern_fn,
-};
-
-dmnsn_pattern *
-dmnsn_new_leopard_pattern(dmnsn_pool *pool)
-{
-  return &dmnsn_leopard_instance;
-}
diff --git a/libdimension/light.c b/libdimension/light.c
deleted file mode 100644
index 14917de..0000000
--- a/libdimension/light.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Lights.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-// Allocate a new dummy light
-dmnsn_light *
-dmnsn_new_light(dmnsn_pool *pool)
-{
-  dmnsn_light *light = DMNSN_PALLOC(pool, dmnsn_light);
-  dmnsn_init_light(light);
-  return light;
-}
-
-// Initialize a light
-void
-dmnsn_init_light(dmnsn_light *light)
-{
-  light->direction_fn = NULL;
-  light->illumination_fn = NULL;
-  light->shadow_fn = NULL;
-}
diff --git a/libdimension/malloc.c b/libdimension/malloc.c
deleted file mode 100644
index 2e4969d..0000000
--- a/libdimension/malloc.c
+++ /dev/null
@@ -1,94 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Dynamic memory.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-#include <string.h>
-#include <stdatomic.h>
-
-#if DMNSN_DEBUG
-static atomic_size_t dmnsn_allocs = ATOMIC_VAR_INIT(0);
-#endif
-
-void *
-dmnsn_malloc(size_t size)
-{
-  void *ptr = malloc(size);
-  if (!ptr) {
-    dmnsn_error("Memory allocation failed.");
-  }
-
-#if DMNSN_DEBUG
-  atomic_fetch_add(&dmnsn_allocs, 1);
-#endif
-
-  return ptr;
-}
-
-void *
-dmnsn_realloc(void *ptr, size_t size)
-{
-#if DMNSN_DEBUG
-  if (!ptr) {
-    atomic_fetch_add(&dmnsn_allocs, 1);
-  }
-#endif
-
-  ptr = realloc(ptr, size);
-  if (!ptr) {
-    dmnsn_error("Memory allocation failed.");
-  }
-  return ptr;
-}
-
-char *
-dmnsn_strdup(const char *s)
-{
-  char *copy = dmnsn_malloc(strlen(s) + 1);
-  strcpy(copy, s);
-  return copy;
-}
-
-void
-dmnsn_free(void *ptr)
-{
-#if DMNSN_DEBUG
-  if (ptr) {
-    atomic_fetch_sub(&dmnsn_allocs, 1);
-  }
-#endif
-
-  free(ptr);
-}
-
-#if DMNSN_DEBUG
-DMNSN_LATE_DESTRUCTOR static void
-dmnsn_leak_check(void)
-{
-  if (atomic_load_explicit(&dmnsn_allocs, memory_order_relaxed) > 0) {
-    dmnsn_warning("Leaking memory.");
-  }
-}
-#endif
diff --git a/libdimension/map.c b/libdimension/map.c
deleted file mode 100644
index f5454b1..0000000
--- a/libdimension/map.c
+++ /dev/null
@@ -1,125 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Generic maps.
- */
-
-#include "dimension-internal.h"
-
-/// dmnsn_map definition.
-struct dmnsn_map {
-  size_t obj_size; ///< @internal The size of the mapped objects.
-  dmnsn_array *array; ///< @internal The map entries.
-};
-
-/// An [index, object] pair.
-typedef struct dmnsn_map_entry {
-  double n;
-  char object[];
-} dmnsn_map_entry;
-
-dmnsn_map *
-dmnsn_new_map(dmnsn_pool *pool, size_t size)
-{
-  dmnsn_map *map = DMNSN_PALLOC(pool, dmnsn_map);
-  map->obj_size = size;
-  map->array = dmnsn_palloc_array(pool, sizeof(dmnsn_map_entry) + size);
-  return map;
-}
-
-void
-dmnsn_map_add_entry(dmnsn_map *map, double n, const void *obj)
-{
-  dmnsn_map_entry *entry;
-  DMNSN_ALLOCA(entry, sizeof(dmnsn_map_entry) + map->obj_size);
-
-  entry->n = n;
-  memcpy(entry->object, obj, map->obj_size);
-
-  // Sorted insertion
-  size_t i;
-  for (i = dmnsn_array_size(map->array); i-- > 0;) {
-    dmnsn_map_entry *other = dmnsn_array_at(map->array, i);
-    if (other->n <= n) {
-      break;
-    }
-  }
-
-  dmnsn_array_insert(map->array, i + 1, entry);
-}
-
-size_t
-dmnsn_map_size(const dmnsn_map *map)
-{
-  return dmnsn_array_size(map->array);
-}
-
-void
-dmnsn_map_evaluate(const dmnsn_map *map, double n,
-                   double *val, void *obj1, void *obj2)
-{
-  dmnsn_assert(dmnsn_array_size(map->array) > 0,
-               "Attempt to evaluate empty map.");
-
-  const dmnsn_map_entry *entry = dmnsn_array_first(map->array);
-
-  double n1, n2 = 0.0;
-  const void *o1, *o2 = entry->object;
-
-  if (n < n2) {
-    *val = 0.0;
-    memcpy(obj1, o2, map->obj_size);
-    memcpy(obj2, o2, map->obj_size);
-    return;
-  }
-
-  ptrdiff_t skip = sizeof(dmnsn_map_entry) + map->obj_size;
-  for (const dmnsn_map_entry *last = dmnsn_array_last(map->array);
-       entry <= last;
-       entry = (const dmnsn_map_entry *)((const char *)entry + skip)) {
-    n1 = n2;
-    o1 = o2;
-
-    n2 = entry->n;
-    o2 = entry->object;
-
-    if (n < n2) {
-      *val = (n - n1)/(n2 - n1);
-      memcpy(obj1, o1, map->obj_size);
-      memcpy(obj2, o2, map->obj_size);
-      return;
-    }
-  }
-
-  *val = 1.0;
-  memcpy(obj1, o2, map->obj_size);
-  memcpy(obj2, o2, map->obj_size);
-}
-
-void
-dmnsn_map_apply(dmnsn_map *map, dmnsn_callback_fn *callback)
-{
-  for (size_t i = 0; i < dmnsn_array_size(map->array); ++i) {
-    dmnsn_map_entry *entry = dmnsn_array_at(map->array, i);
-    callback(entry->object);
-  }
-}
diff --git a/libdimension/math/matrix.c b/libdimension/math/matrix.c
new file mode 100644
index 0000000..25590d8
--- /dev/null
+++ b/libdimension/math/matrix.c
@@ -0,0 +1,388 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Matrix function implementations.
+ */
+
+#include "internal.h"
+#include "dimension/math.h"
+#include <math.h>
+
+// Identity matrix
+dmnsn_matrix
+dmnsn_identity_matrix(void)
+{
+  return dmnsn_new_matrix(1.0, 0.0, 0.0, 0.0,
+                          0.0, 1.0, 0.0, 0.0,
+                          0.0, 0.0, 1.0, 0.0);
+}
+
+// Scaling matrix
+dmnsn_matrix
+dmnsn_scale_matrix(dmnsn_vector s)
+{
+  return dmnsn_new_matrix(s.x, 0.0, 0.0, 0.0,
+                          0.0, s.y, 0.0, 0.0,
+                          0.0, 0.0, s.z, 0.0);
+}
+
+// Translation matrix
+dmnsn_matrix
+dmnsn_translation_matrix(dmnsn_vector d)
+{
+  return dmnsn_new_matrix(1.0, 0.0, 0.0, d.x,
+                          0.0, 1.0, 0.0, d.y,
+                          0.0, 0.0, 1.0, d.z);
+}
+
+// Left-handed rotation matrix; theta/|theta| = axis, |theta| = angle
+dmnsn_matrix
+dmnsn_rotation_matrix(dmnsn_vector theta)
+{
+  // Two trig calls, 25 multiplications, 13 additions
+
+  double angle = dmnsn_vector_norm(theta);
+  if (fabs(angle) < dmnsn_epsilon) {
+    return dmnsn_identity_matrix();
+  }
+  dmnsn_vector axis = dmnsn_vector_div(theta, angle);
+
+  // Shorthand to make dmnsn_new_matrix() call legible
+
+  double s = sin(angle);
+  double t = 1.0 - cos(angle);
+
+  double x = axis.x;
+  double y = axis.y;
+  double z = axis.z;
+
+  return dmnsn_new_matrix(
+    1.0 + t*(x*x - 1.0), -z*s + t*x*y,        y*s + t*x*z,         0.0,
+    z*s + t*x*y,         1.0 + t*(y*y - 1.0), -x*s + t*y*z,        0.0,
+    -y*s + t*x*z,        x*s + t*y*z,         1.0 + t*(z*z - 1.0), 0.0
+  );
+}
+
+// Find the angle between two vectors with respect to an axis
+static double
+dmnsn_axis_angle(dmnsn_vector from, dmnsn_vector to, dmnsn_vector axis)
+{
+  from = dmnsn_vector_sub(from, dmnsn_vector_proj(from, axis));
+  to   = dmnsn_vector_sub(to,   dmnsn_vector_proj(to, axis));
+
+  double fromnorm = dmnsn_vector_norm(from);
+  double tonorm   = dmnsn_vector_norm(to);
+  if (fromnorm < dmnsn_epsilon || tonorm < dmnsn_epsilon) {
+    return 0.0;
+  }
+
+  from = dmnsn_vector_div(from, fromnorm);
+  to   = dmnsn_vector_div(to,   tonorm);
+
+  double angle = acos(dmnsn_vector_dot(from, to));
+
+  if (dmnsn_vector_dot(dmnsn_vector_cross(from, to), axis) > 0.0) {
+    return angle;
+  } else {
+    return -angle;
+  }
+}
+
+// Alignment matrix
+dmnsn_matrix
+dmnsn_alignment_matrix(dmnsn_vector from, dmnsn_vector to,
+                       dmnsn_vector axis1, dmnsn_vector axis2)
+{
+  double theta1 = dmnsn_axis_angle(from, to, axis1);
+  dmnsn_matrix align1 = dmnsn_rotation_matrix(dmnsn_vector_mul(theta1, axis1));
+  from  = dmnsn_transform_direction(align1, from);
+  axis2 = dmnsn_transform_direction(align1, axis2);
+
+  double theta2 = dmnsn_axis_angle(from, to, axis2);
+  dmnsn_matrix align2 = dmnsn_rotation_matrix(dmnsn_vector_mul(theta2, axis2));
+
+  return dmnsn_matrix_mul(align2, align1);
+}
+
+// Matrix inversion helper functions
+
+/// A 2x2 matrix for inversion by partitioning.
+typedef struct { double n[2][2]; } dmnsn_matrix2;
+
+/// Construct a 2x2 matrix.
+static dmnsn_matrix2 dmnsn_new_matrix2(double a1, double a2,
+                                       double b1, double b2);
+/// Invert a 2x2 matrix.
+static dmnsn_matrix2 dmnsn_matrix2_inverse(dmnsn_matrix2 A);
+/// Negate a 2x2 matrix.
+static dmnsn_matrix2 dmnsn_matrix2_negate(dmnsn_matrix2 A);
+/// Subtract two 2x2 matricies.
+static dmnsn_matrix2 dmnsn_matrix2_sub(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs);
+/// Add two 2x2 matricies.
+static dmnsn_matrix2 dmnsn_matrix2_mul(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs);
+
+/// Invert a matrix with the slower cofactor algorithm, if partitioning failed.
+static dmnsn_matrix dmnsn_matrix_inverse_generic(dmnsn_matrix A);
+/// Get the [\p row, \p col] cofactor of A.
+static double dmnsn_matrix_cofactor(dmnsn_matrix A, size_t row, size_t col);
+
+// Invert a matrix, by partitioning
+dmnsn_matrix
+dmnsn_matrix_inverse(dmnsn_matrix A)
+{
+  // Use partitioning to invert a matrix:
+  //
+  //     [ P Q ] -1
+  //     [ R S ]
+  //
+  //   = [ PP QQ ]
+  //     [ RR SS ],
+  //
+  // with PP = inv(P) - inv(P)*Q*RR,
+  //      QQ = -inv(P)*Q*SS,
+  //      RR = -SS*R*inv(P), and
+  //      SS = inv(S - R*inv(P)*Q).
+
+  // The algorithm uses 2 inversions, 6 multiplications, and 2 subtractions,
+  // giving 52 multiplications, 34 additions, and 8 divisions.
+
+  dmnsn_matrix2 P, Q, R, S, Pi, RPi, PiQ, RPiQ, PP, QQ, RR, SS;
+  double Pdet = A.n[0][0]*A.n[1][1] - A.n[0][1]*A.n[1][0];
+
+  if (dmnsn_unlikely(fabs(Pdet) < dmnsn_epsilon)) {
+    // If P is close to singular, try a more generic algorithm; this is very
+    // unlikely, but not impossible, eg.
+    //   [ 1 1 0 0 ]
+    //   [ 1 1 1 0 ]
+    //   [ 0 1 1 0 ]
+    //   [ 0 0 0 1 ]
+    return dmnsn_matrix_inverse_generic(A);
+  }
+
+  // Partition the matrix
+  P = dmnsn_new_matrix2(A.n[0][0], A.n[0][1],
+                        A.n[1][0], A.n[1][1]);
+  Q = dmnsn_new_matrix2(A.n[0][2], A.n[0][3],
+                        A.n[1][2], A.n[1][3]);
+  R = dmnsn_new_matrix2(A.n[2][0], A.n[2][1],
+                        0.0,       0.0);
+  S = dmnsn_new_matrix2(A.n[2][2], A.n[2][3],
+                        0.0,       1.0);
+
+  // Do this inversion ourselves, since we already have the determinant
+  Pi = dmnsn_new_matrix2( P.n[1][1]/Pdet, -P.n[0][1]/Pdet,
+                         -P.n[1][0]/Pdet,  P.n[0][0]/Pdet);
+
+  // Calculate R*inv(P), inv(P)*Q, and R*inv(P)*Q
+  RPi  = dmnsn_matrix2_mul(R, Pi);
+  PiQ  = dmnsn_matrix2_mul(Pi, Q);
+  RPiQ = dmnsn_matrix2_mul(R, PiQ);
+
+  // Calculate the partitioned inverse
+  SS = dmnsn_matrix2_inverse(dmnsn_matrix2_sub(S, RPiQ));
+  RR = dmnsn_matrix2_negate(dmnsn_matrix2_mul(SS, RPi));
+  QQ = dmnsn_matrix2_negate(dmnsn_matrix2_mul(PiQ, SS));
+  PP = dmnsn_matrix2_sub(Pi, dmnsn_matrix2_mul(PiQ, RR));
+
+  // Reconstruct the matrix
+  return dmnsn_new_matrix(PP.n[0][0], PP.n[0][1], QQ.n[0][0], QQ.n[0][1],
+                          PP.n[1][0], PP.n[1][1], QQ.n[1][0], QQ.n[1][1],
+                          RR.n[0][0], RR.n[0][1], SS.n[0][0], SS.n[0][1]);
+}
+
+// For nice shorthand
+static dmnsn_matrix2
+dmnsn_new_matrix2(double a1, double a2, double b1, double b2)
+{
+  dmnsn_matrix2 m = { { { a1, a2 },
+                        { b1, b2 } } };
+  return m;
+}
+
+// Invert a 2x2 matrix
+static dmnsn_matrix2
+dmnsn_matrix2_inverse(dmnsn_matrix2 A)
+{
+  // 4 divisions, 2 multiplications, 1 addition
+  double det = A.n[0][0]*A.n[1][1] - A.n[0][1]*A.n[1][0];
+  return dmnsn_new_matrix2( A.n[1][1]/det, -A.n[0][1]/det,
+                           -A.n[1][0]/det,  A.n[0][0]/det);
+}
+
+// Also basically a shorthand
+static dmnsn_matrix2
+dmnsn_matrix2_negate(dmnsn_matrix2 A)
+{
+  return dmnsn_new_matrix2(-A.n[0][0], -A.n[0][1],
+                           -A.n[1][0], -A.n[1][1]);
+}
+
+// 2x2 matrix subtraction
+static dmnsn_matrix2
+dmnsn_matrix2_sub(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs)
+{
+  // 4 additions
+  return dmnsn_new_matrix2(
+    lhs.n[0][0] - rhs.n[0][0], lhs.n[0][1] - rhs.n[0][1],
+    lhs.n[1][0] - rhs.n[1][0], lhs.n[1][1] - rhs.n[1][1]
+  );
+}
+
+// 2x2 matrix multiplication
+static dmnsn_matrix2
+dmnsn_matrix2_mul(dmnsn_matrix2 lhs, dmnsn_matrix2 rhs)
+{
+  // 8 multiplications, 4 additions
+  return dmnsn_new_matrix2(
+    lhs.n[0][0]*rhs.n[0][0] + lhs.n[0][1]*rhs.n[1][0],
+      lhs.n[0][0]*rhs.n[0][1] + lhs.n[0][1]*rhs.n[1][1],
+    lhs.n[1][0]*rhs.n[0][0] + lhs.n[1][1]*rhs.n[1][0],
+      lhs.n[1][0]*rhs.n[0][1] + lhs.n[1][1]*rhs.n[1][1]
+  );
+}
+
+// Invert a matrix, if partitioning failed (|P| == 0)
+static dmnsn_matrix
+dmnsn_matrix_inverse_generic(dmnsn_matrix A)
+{
+  // For A = [ A'      b ]  A^-1 = [ A'^-1   -(A'^-1)*b ]
+  //         [ 0 ... 0 1 ],        [ 0 ... 0      1     ].
+  //
+  // Invert A' by calculating its adjucate.
+  dmnsn_matrix inv;
+  double det = 0.0, C;
+
+  // Perform a Laplace expansion along the first row to give us the adjugate's
+  // first column and the determinant
+  for (size_t j = 0; j < 3; ++j) {
+    C = dmnsn_matrix_cofactor(A, 0, j);
+    det += A.n[0][j]*C;
+    inv.n[j][0] = C;
+  }
+
+  // Divide the first column by the determinant
+  for (size_t j = 0; j < 3; ++j) {
+    inv.n[j][0] /= det;
+  }
+
+  // Find the rest of A'
+  for (size_t j = 0; j < 3; ++j) {
+    for (size_t i = 1; i < 3; ++i) {
+      inv.n[j][i] = dmnsn_matrix_cofactor(A, i, j)/det;
+    }
+    inv.n[j][3] = 0.0;
+  }
+
+  // Find the translational component of the inverse
+  for (size_t i = 0; i < 3; ++i) {
+    for (size_t j = 0; j < 3; ++j) {
+      inv.n[i][3] -= inv.n[i][j]*A.n[j][3];
+    }
+  }
+
+  return inv;
+}
+
+// Gives the cofactor at row, col; the determinant of the matrix formed from the
+// upper-left 3x3 corner of A by ignoring row `row' and column `col',
+// times (-1)^(row + col)
+static double
+dmnsn_matrix_cofactor(dmnsn_matrix A, size_t row, size_t col)
+{
+  // 2 multiplications, 1 addition
+  double n[4];
+  size_t k = 0;
+  for (size_t i = 0; i < 3; ++i) {
+    for (size_t j = 0; j < 3; ++j) {
+      if (i != row && j != col) {
+        n[k] = A.n[i][j];
+        ++k;
+      }
+    }
+  }
+
+  double C = n[0]*n[3] - n[1]*n[2];
+  if ((row + col)%2 == 0) {
+    return C;
+  } else {
+    return -C;
+  }
+}
+
+// 4x4 matrix multiplication
+dmnsn_matrix
+dmnsn_matrix_mul(dmnsn_matrix lhs, dmnsn_matrix rhs)
+{
+  // 36 multiplications, 27 additions
+  dmnsn_matrix r;
+
+  r.n[0][0] = lhs.n[0][0]*rhs.n[0][0] + lhs.n[0][1]*rhs.n[1][0] + lhs.n[0][2]*rhs.n[2][0];
+  r.n[0][1] = lhs.n[0][0]*rhs.n[0][1] + lhs.n[0][1]*rhs.n[1][1] + lhs.n[0][2]*rhs.n[2][1];
+  r.n[0][2] = lhs.n[0][0]*rhs.n[0][2] + lhs.n[0][1]*rhs.n[1][2] + lhs.n[0][2]*rhs.n[2][2];
+  r.n[0][3] = lhs.n[0][0]*rhs.n[0][3] + lhs.n[0][1]*rhs.n[1][3] + lhs.n[0][2]*rhs.n[2][3] + lhs.n[0][3];
+
+  r.n[1][0] = lhs.n[1][0]*rhs.n[0][0] + lhs.n[1][1]*rhs.n[1][0] + lhs.n[1][2]*rhs.n[2][0];
+  r.n[1][1] = lhs.n[1][0]*rhs.n[0][1] + lhs.n[1][1]*rhs.n[1][1] + lhs.n[1][2]*rhs.n[2][1];
+  r.n[1][2] = lhs.n[1][0]*rhs.n[0][2] + lhs.n[1][1]*rhs.n[1][2] + lhs.n[1][2]*rhs.n[2][2];
+  r.n[1][3] = lhs.n[1][0]*rhs.n[0][3] + lhs.n[1][1]*rhs.n[1][3] + lhs.n[1][2]*rhs.n[2][3] + lhs.n[1][3];
+
+  r.n[2][0] = lhs.n[2][0]*rhs.n[0][0] + lhs.n[2][1]*rhs.n[1][0] + lhs.n[2][2]*rhs.n[2][0];
+  r.n[2][1] = lhs.n[2][0]*rhs.n[0][1] + lhs.n[2][1]*rhs.n[1][1] + lhs.n[2][2]*rhs.n[2][1];
+  r.n[2][2] = lhs.n[2][0]*rhs.n[0][2] + lhs.n[2][1]*rhs.n[1][2] + lhs.n[2][2]*rhs.n[2][2];
+  r.n[2][3] = lhs.n[2][0]*rhs.n[0][3] + lhs.n[2][1]*rhs.n[1][3] + lhs.n[2][2]*rhs.n[2][3] + lhs.n[2][3];
+
+  return r;
+}
+
+// Give an axis-aligned box that contains the given box transformed by `lhs'
+dmnsn_aabb
+dmnsn_transform_aabb(dmnsn_matrix trans, dmnsn_aabb box)
+{
+  // Infinite/zero bounding box support
+  if (isinf(box.min.x)) {
+    return box;
+  }
+
+  // Taking the "absolute value" of the matrix saves some min/max calculations
+  for (int i = 0; i < 3; ++i) {
+    for (int j = 0; j < 3; ++j) {
+      trans.n[i][j] = fabs(trans.n[i][j]);
+    }
+  }
+
+  dmnsn_vector Mt = dmnsn_matrix_column(trans, 3);
+  dmnsn_aabb ret = { Mt, Mt };
+
+  dmnsn_vector Mz = dmnsn_matrix_column(trans, 2);
+  ret.min = dmnsn_vector_add(ret.min, dmnsn_vector_mul(box.min.z, Mz));
+  ret.max = dmnsn_vector_add(ret.max, dmnsn_vector_mul(box.max.z, Mz));
+
+  dmnsn_vector My = dmnsn_matrix_column(trans, 1);
+  ret.min = dmnsn_vector_add(ret.min, dmnsn_vector_mul(box.min.y, My));
+  ret.max = dmnsn_vector_add(ret.max, dmnsn_vector_mul(box.max.y, My));
+
+  dmnsn_vector Mx = dmnsn_matrix_column(trans, 0);
+  ret.min = dmnsn_vector_add(ret.min, dmnsn_vector_mul(box.min.x, Mx));
+  ret.max = dmnsn_vector_add(ret.max, dmnsn_vector_mul(box.max.x, Mx));
+
+  return ret;
+}
diff --git a/libdimension/math/polynomial.c b/libdimension/math/polynomial.c
new file mode 100644
index 0000000..09e9603
--- /dev/null
+++ b/libdimension/math/polynomial.c
@@ -0,0 +1,443 @@
+/*************************************************************************
+ * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Real root isolation algorithm based on work by Vincent, Uspensky, Collins and
+ * Akritas, Johnson, Krandick, and Rouillier and Zimmerman.
+ */
+
+#include "internal.h"
+#include "internal/polynomial.h"
+#include "dimension/math.h"
+#include <math.h>
+
+/// Get the real degree of a polynomial, ignoring leading zeros.
+static inline size_t
+dmnsn_real_degree(const double poly[], size_t degree)
+{
+  for (size_t i = degree + 1; i-- > 0;) {
+    if (dmnsn_likely(fabs(poly[i]) >= dmnsn_epsilon)) {
+      return i;
+    }
+  }
+
+  return 0;
+}
+
+/// Divide each coefficient by the leading coefficient.
+static inline void
+dmnsn_polynomial_normalize(double poly[], size_t degree)
+{
+  for (size_t i = 0; i < degree; ++i) {
+    poly[i] /= poly[degree];
+  }
+  poly[degree] = 1.0;
+}
+
+/// Eliminate trivial zero roots from \p poly[].
+static inline void
+dmnsn_eliminate_zero_roots(double **poly, size_t *degree)
+{
+  size_t i;
+  for (i = 0; i <= *degree; ++i) {
+    if (dmnsn_likely(fabs((*poly)[i]) >= dmnsn_epsilon)) {
+      break;
+    }
+  }
+
+  *poly += i;
+  *degree -= i;
+}
+
+/// Calculate a finite upper bound on the roots of a normalized polynomial.
+static inline double
+dmnsn_root_bound(const double poly[], size_t degree)
+{
+  double bound = fabs(poly[0]);
+  for (size_t i = 1; i < degree; ++i) {
+    bound = dmnsn_max(bound, fabs(poly[i]));
+  }
+  bound += 1.0;
+  return bound;
+}
+
+/// Copy a polynomial.
+static inline void
+dmnsn_polynomial_copy(double dest[], const double src[], size_t degree)
+{
+  for (size_t i = 0; i <= degree; ++i) {
+    dest[i] = src[i];
+  }
+}
+
+/// Transform a polynomial by P'(x) = P(x + 1).
+static inline void
+dmnsn_polynomial_translate(double poly[], size_t degree)
+{
+  for (size_t i = 0; i <= degree; ++i) {
+    for (size_t j = degree - i; j <= degree - 1; ++j) {
+      poly[j] += poly[j + 1];
+    }
+  }
+}
+
+/// Transform a polynomial by P'(x) = P(c*x).
+static inline void
+dmnsn_polynomial_scale(double poly[], size_t degree, double c)
+{
+  double factor = c;
+  for (size_t i = 1; i <= degree; ++i) {
+    poly[i] *= factor;
+    factor *= c;
+  }
+}
+
+/// Returns the result of Descartes' rule on x^degree * poly(1/(x + 1)).
+static size_t
+dmnsn_descartes_bound(const double poly[], size_t degree)
+{
+  // Copy the polynomial so we can be destructive
+  double p[degree + 1];
+  dmnsn_polynomial_copy(p, poly, degree);
+
+  // Calculate poly(1/(1/x + 1)) which avoids reversal
+  for (size_t i = 1; i <= degree; ++i) {
+    for (size_t j = i; j >= 1; --j) {
+      p[j] += p[j - 1];
+    }
+  }
+
+  // Find the number of sign changes in p[]
+  size_t changes = 0;
+  int lastsign = dmnsn_sgn(p[0]);
+  for (size_t i = 1; changes <= 1 && i <= degree; ++i) {
+    int sign = dmnsn_sgn(p[i]);
+    if (sign != 0 && sign != lastsign) {
+      ++changes;
+      lastsign = sign;
+    }
+  }
+
+  return changes;
+}
+
+/// Depth-first search of possible isolating intervals.
+static size_t
+dmnsn_root_bounds_recursive(double poly[], size_t degree, double *c, double *k,
+                            double bounds[][2], size_t nbounds)
+{
+  size_t s = dmnsn_descartes_bound(poly, degree);
+  if (s >= 2) {
+    // Get the left child
+    dmnsn_polynomial_scale(poly, degree, 1.0/2.0);
+    *c *= 2.0;
+    *k /= 2.0;
+    double currc = *c, currk = *k;
+
+    // Test the left child
+    size_t n = dmnsn_root_bounds_recursive(poly, degree, c, k, bounds, nbounds);
+    if (nbounds == n) {
+      return n;
+    }
+    bounds += n;
+    nbounds -= n;
+
+    // Get the right child from the last tested polynomial
+    dmnsn_polynomial_translate(poly, degree);
+    dmnsn_polynomial_scale(poly, degree, currk/(*k));
+    *c = currc + 1.0;
+    *k = currk;
+
+    // Test the right child
+    n += dmnsn_root_bounds_recursive(poly, degree, c, k, bounds, nbounds);
+    return n;
+  } else if (s == 1) {
+    bounds[0][0] = (*c)*(*k);
+    bounds[0][1] = (*c + 1.0)*(*k);
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/// Find ranges that contain a single root.
+static size_t
+dmnsn_root_bounds(const double poly[], size_t degree, double bounds[][2],
+                  size_t nbounds)
+{
+  // Copy the polynomial so we can be destructive
+  double p[degree + 1];
+  dmnsn_polynomial_copy(p, poly, degree);
+
+  // Scale the roots to within (0, 1]
+  double bound = dmnsn_root_bound(p, degree);
+  dmnsn_polynomial_scale(p, degree, bound);
+
+  // Bounding intervals are of the form (c*k, (c + 1)*k)
+  double c = 0.0, k = 1.0;
+
+  // Isolate the roots
+  size_t n = dmnsn_root_bounds_recursive(p, degree, &c, &k, bounds, nbounds);
+
+  // Scale the roots back to within (0, bound]
+  for (size_t i = 0; i < n; ++i) {
+    bounds[i][0] *= bound;
+    bounds[i][1] *= bound;
+  }
+
+  return n;
+}
+
+/// Maximum number of iterations in dmnsn_bisect_root() before bailout.
+#define DMNSN_BISECT_ITERATIONS 64
+
+/// Use the false position method to find a root in a range that contains
+/// exactly one root.
+static inline double
+dmnsn_bisect_root(const double poly[], size_t degree, double min, double max)
+{
+  double evmin = dmnsn_polynomial_evaluate(poly, degree, min);
+  double evmax = dmnsn_polynomial_evaluate(poly, degree, max);
+
+  // Handle equal bounds, and equal values at the bounds.
+  if (dmnsn_unlikely(fabs(evmax - evmin) < dmnsn_epsilon)) {
+    return (min + max)/2.0;
+  }
+
+  double evinitial = dmnsn_min(fabs(evmin), fabs(evmax));
+  double mid, evmid;
+  int lastsign = 0;
+
+  for (size_t i = 0; i < DMNSN_BISECT_ITERATIONS; ++i) {
+    mid = (min*evmax - max*evmin)/(evmax - evmin);
+    evmid = dmnsn_polynomial_evaluate(poly, degree, mid);
+    int sign = dmnsn_sgn(evmid);
+
+    if ((fabs(evmid) < fabs(mid)*dmnsn_epsilon
+         // This condition improves stability when one of the bounds is close to
+         // a different root than we are trying to find
+         && fabs(evmid) <= evinitial)
+        || max - min < fabs(mid)*dmnsn_epsilon)
+    {
+      break;
+    }
+
+    if (mid < min) {
+      // This can happen due to numerical instability in the root bounding
+      // algorithm, so behave like the normal secant method
+      max = min;
+      evmax = evmin;
+      min = mid;
+      evmin = evmid;
+    } else if (mid > max) {
+      min = max;
+      evmin = evmax;
+      max = mid;
+      evmax = evmid;
+    } else if (sign == dmnsn_sgn(evmax)) {
+      max = mid;
+      evmax = evmid;
+      if (sign == lastsign) {
+        // Don't allow the algorithm to keep the same endpoint for three
+        // iterations in a row; this ensures superlinear convergence
+        evmin /= 2.0;
+      }
+    } else {
+      min = mid;
+      evmin = evmid;
+      if (sign == lastsign) {
+        evmax /= 2.0;
+      }
+    }
+
+    lastsign = sign;
+  }
+
+  return mid;
+}
+
+/// Use synthetic division to eliminate the root \p r from \p poly[].
+static inline size_t
+dmnsn_eliminate_root(double poly[], size_t degree, double r)
+{
+  double rem = poly[degree];
+  for (size_t i = degree; i-- > 0;) {
+    double temp = poly[i];
+    poly[i] = rem;
+    rem = temp + r*rem;
+  }
+  return degree - 1;
+}
+
+/// Solve a normalized linear polynomial algebraically.
+static inline size_t
+dmnsn_solve_linear(const double poly[2], double x[1])
+{
+  x[0] = -poly[0];
+  if (x[0] >= dmnsn_epsilon)
+    return 1;
+  else
+    return 0;
+}
+
+/// Solve a normalized quadratic polynomial algebraically.
+static inline size_t
+dmnsn_solve_quadratic(const double poly[3], double x[2])
+{
+  double disc = poly[1]*poly[1] - 4.0*poly[0];
+  if (disc >= 0.0) {
+    double s = sqrt(disc);
+    x[0] = (-poly[1] + s)/2.0;
+    x[1] = (-poly[1] - s)/2.0;
+
+    if (x[1] >= dmnsn_epsilon)
+      return 2;
+    else if (x[0] >= dmnsn_epsilon)
+      return 1;
+    else
+      return 0;
+  } else {
+    return 0;
+  }
+}
+
+/// Solve a normalized cubic polynomial algebraically.
+static inline size_t
+dmnsn_solve_cubic(double poly[4], double x[3])
+{
+  // Reduce to a monic trinomial (t^3 + p*t + q, t = x + b/3)
+  double b2 = poly[2]*poly[2];
+  double p = poly[1] - b2/3.0;
+  double q = poly[0] - poly[2]*(9.0*poly[1] - 2.0*b2)/27.0;
+
+  double disc = 4.0*p*p*p + 27.0*q*q;
+  double bdiv3 = poly[2]/3.0;
+
+  if (disc < 0.0) {
+    // Three real roots -- this implies p < 0
+    double msqrtp3 = -sqrt(-p/3.0);
+    double theta = acos(3*q/(2*p*msqrtp3))/3.0;
+
+    // Store the roots in order from largest to smallest
+    x[2] = 2.0*msqrtp3*cos(theta) - bdiv3;
+    x[0] = -2.0*msqrtp3*cos(4.0*atan(1.0)/3.0 - theta) - bdiv3;
+    x[1] = -(x[0] + x[2] + poly[2]);
+
+    if (x[2] >= dmnsn_epsilon)
+      return 3;
+    else if (x[1] >= dmnsn_epsilon)
+      return 2;
+  } else if (disc > 0.0) {
+    // One real root
+    double cbrtdiscq = cbrt(sqrt(disc/108.0) + fabs(q)/2.0);
+    double abst = cbrtdiscq - p/(3.0*cbrtdiscq);
+
+    if (q >= 0) {
+      x[0] = -abst - bdiv3;
+    } else {
+      x[0] = abst - bdiv3;
+    }
+  } else if (fabs(p) < dmnsn_epsilon) {
+    // Equation is a perfect cube
+    x[0] = -bdiv3;
+  } else {
+    // Two real roots; one duplicate
+    double t1 = -(3.0*q)/(2.0*p), t2 = -2.0*t1;
+    x[0] = dmnsn_max(t1, t2) - bdiv3;
+    x[1] = dmnsn_min(t1, t2) - bdiv3;
+    if (x[1] >= dmnsn_epsilon)
+      return 2;
+  }
+
+  if (x[0] >= dmnsn_epsilon)
+    return 1;
+  else
+    return 0;
+}
+
+// Solve a polynomial
+DMNSN_HOT size_t
+dmnsn_polynomial_solve(const double poly[], size_t degree, double x[])
+{
+  // Copy the polynomial so we can be destructive
+  double copy[degree + 1], *p = copy;
+  dmnsn_polynomial_copy(p, poly, degree);
+
+  // Index into x[]
+  size_t i = 0;
+
+  // Account for leading zero coefficients
+  degree = dmnsn_real_degree(p, degree);
+  // Normalize the leading coefficient to 1.0
+  dmnsn_polynomial_normalize(p, degree);
+  // Eliminate simple zero roots
+  dmnsn_eliminate_zero_roots(&p, &degree);
+
+  static const size_t max_algebraic = 3;
+  if (degree > max_algebraic) {
+    // Find isolating intervals for (degree - max_algebraic) roots of p[]
+    double ranges[degree - max_algebraic][2];
+    size_t n = dmnsn_root_bounds(p, degree, ranges, degree - max_algebraic);
+
+    for (size_t j = 0; j < n; ++j) {
+      // Bisect within the found range
+      double r = dmnsn_bisect_root(p, degree, ranges[j][0], ranges[j][1]);
+
+      // Use synthetic division to eliminate the root `r'
+      degree = dmnsn_eliminate_root(p, degree, r);
+
+      // Store the found root
+      x[i] = r;
+      ++i;
+    }
+  }
+
+  switch (degree) {
+  case 1:
+    i += dmnsn_solve_linear(p, x + i);
+    break;
+  case 2:
+    i += dmnsn_solve_quadratic(p, x + i);
+    break;
+  case 3:
+    i += dmnsn_solve_cubic(p, x + i);
+    break;
+  }
+
+  return i;
+}
+
+// Print a polynomial
+void
+dmnsn_polynomial_print(FILE *file, const double poly[], size_t degree)
+{
+  for (size_t i = degree + 1; i-- > 0;) {
+    if (i < degree) {
+      fprintf(file, (poly[i] >= 0.0) ? " + " : " - ");
+    }
+    fprintf(file, "%.17g", fabs(poly[i]));
+    if (i >= 2) {
+      fprintf(file, "*x^%zu", i);
+    } else if (i == 1) {
+      fprintf(file, "*x");
+    }
+  }
+}
diff --git a/libdimension/model/camera.c b/libdimension/model/camera.c
new file mode 100644
index 0000000..8d05c28
--- /dev/null
+++ b/libdimension/model/camera.c
@@ -0,0 +1,52 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Cameras.
+ */
+
+#include "internal.h"
+#include "dimension/model.h"
+#include <stdlib.h>
+
+// Allocate a new dummy camera
+dmnsn_camera *
+dmnsn_new_camera(dmnsn_pool *pool)
+{
+  dmnsn_camera *camera = DMNSN_PALLOC(pool, dmnsn_camera);
+  dmnsn_init_camera(camera);
+  return camera;
+}
+
+// Initialize a camera
+void
+dmnsn_init_camera(dmnsn_camera *camera)
+{
+  camera->trans = dmnsn_identity_matrix();
+}
+
+// Invoke the camera ray function
+dmnsn_ray
+dmnsn_camera_ray(const dmnsn_camera *camera, double x, double y)
+{
+  dmnsn_ray ray = camera->ray_fn(camera, x, y);
+  return dmnsn_transform_ray(camera->trans, ray);
+}
diff --git a/libdimension/model/cameras/perspective.c b/libdimension/model/cameras/perspective.c
new file mode 100644
index 0000000..6f1a9fc
--- /dev/null
+++ b/libdimension/model/cameras/perspective.c
@@ -0,0 +1,47 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Perspective cameras.
+ */
+
+#include "dimension/model.h"
+#include <stdlib.h>
+
+/// Perspective camera ray callback.
+static dmnsn_ray
+dmnsn_perspective_camera_ray_fn(const dmnsn_camera *camera, double x, double y)
+{
+  dmnsn_ray l = dmnsn_new_ray(
+    dmnsn_zero,
+    dmnsn_new_vector(x - 0.5, y - 0.5, 1.0)
+  );
+  return l;
+}
+
+// Create a new perspective camera.
+dmnsn_camera *
+dmnsn_new_perspective_camera(dmnsn_pool *pool)
+{
+  dmnsn_camera *camera = dmnsn_new_camera(pool);
+  camera->ray_fn = dmnsn_perspective_camera_ray_fn;
+  return camera;
+}
diff --git a/libdimension/model/finish.c b/libdimension/model/finish.c
new file mode 100644
index 0000000..a5dbaf9
--- /dev/null
+++ b/libdimension/model/finish.c
@@ -0,0 +1,103 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Finishes.
+ */
+
+#include "internal.h"
+#include "dimension/model.h"
+
+dmnsn_ambient *
+dmnsn_new_ambient(dmnsn_pool *pool, dmnsn_color ambient_light)
+{
+  dmnsn_ambient *ambient = DMNSN_PALLOC(pool, dmnsn_ambient);
+  ambient->ambient = ambient_light;
+  return ambient;
+}
+
+dmnsn_diffuse *
+dmnsn_new_diffuse(dmnsn_pool *pool)
+{
+  dmnsn_diffuse *diffuse = DMNSN_PALLOC(pool, dmnsn_diffuse);
+  dmnsn_init_diffuse(diffuse);
+  return diffuse;
+}
+
+void
+dmnsn_init_diffuse(dmnsn_diffuse *diffuse)
+{
+}
+
+dmnsn_specular *
+dmnsn_new_specular(dmnsn_pool *pool)
+{
+  dmnsn_specular *specular = DMNSN_PALLOC(pool, dmnsn_specular);
+  dmnsn_init_specular(specular);
+  return specular;
+}
+
+void
+dmnsn_init_specular(dmnsn_specular *specular)
+{
+}
+
+dmnsn_reflection *
+dmnsn_new_reflection(dmnsn_pool *pool)
+{
+  dmnsn_reflection *reflection = DMNSN_PALLOC(pool, dmnsn_reflection);
+  dmnsn_init_reflection(reflection);
+  return reflection;
+}
+
+void
+dmnsn_init_reflection(dmnsn_reflection *reflection)
+{
+}
+
+dmnsn_finish
+dmnsn_new_finish(void)
+{
+  dmnsn_finish finish = {
+    .ambient = NULL,
+    .diffuse = NULL,
+    .specular = NULL,
+    .reflection = NULL,
+  };
+  return finish;
+}
+
+void
+dmnsn_finish_cascade(const dmnsn_finish *default_finish, dmnsn_finish *finish)
+{
+  if (!finish->ambient) {
+    finish->ambient = default_finish->ambient;
+  }
+  if (!finish->diffuse) {
+    finish->diffuse = default_finish->diffuse;
+  }
+  if (!finish->specular) {
+    finish->specular = default_finish->specular;
+  }
+  if (!finish->reflection) {
+    finish->reflection = default_finish->reflection;
+  }
+}
diff --git a/libdimension/model/finishes/lambertian.c b/libdimension/model/finishes/lambertian.c
new file mode 100644
index 0000000..9e041ed
--- /dev/null
+++ b/libdimension/model/finishes/lambertian.c
@@ -0,0 +1,57 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Diffuse finish.
+ */
+
+#include "dimension/model.h"
+#include <math.h>
+#include <stdlib.h>
+
+/// Lambertian diffuse type.
+typedef struct dmnsn_lambertian {
+  dmnsn_diffuse diffuse;
+  double coeff;
+} dmnsn_lambertian;
+
+/// Diffuse finish callback.
+static dmnsn_color
+dmnsn_lambertian_diffuse_fn(const dmnsn_diffuse *diffuse,
+                            dmnsn_color light, dmnsn_color color,
+                            dmnsn_vector ray, dmnsn_vector normal)
+{
+  const dmnsn_lambertian *lambertian = (const dmnsn_lambertian *)diffuse;
+  double diffuse_factor = fabs((lambertian->coeff)*dmnsn_vector_dot(ray, normal));
+  return dmnsn_color_mul(diffuse_factor, dmnsn_color_illuminate(light, color));
+}
+
+dmnsn_diffuse *
+dmnsn_new_lambertian(dmnsn_pool *pool, double coeff)
+{
+  dmnsn_lambertian *lambertian = DMNSN_PALLOC(pool, dmnsn_lambertian);
+  lambertian->coeff = coeff;
+
+  dmnsn_diffuse *diffuse = &lambertian->diffuse;
+  dmnsn_init_diffuse(diffuse);
+  diffuse->diffuse_fn = dmnsn_lambertian_diffuse_fn;
+  return diffuse;
+}
diff --git a/libdimension/model/finishes/phong.c b/libdimension/model/finishes/phong.c
new file mode 100644
index 0000000..6b1b1e1
--- /dev/null
+++ b/libdimension/model/finishes/phong.c
@@ -0,0 +1,69 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Phong highlights.
+ */
+
+#include "dimension/model.h"
+#include <stdlib.h>
+
+/// Phone specular type.
+typedef struct dmnsn_phong {
+  dmnsn_specular specular;
+  double coeff;
+  double exp;
+} dmnsn_phong;
+
+/// Phong specular highlight callback.
+static dmnsn_color
+dmnsn_phong_specular_fn(const dmnsn_specular *specular,
+                        dmnsn_color light, dmnsn_color color,
+                        dmnsn_vector ray, dmnsn_vector normal,
+                        dmnsn_vector viewer)
+{
+  const dmnsn_phong *phong = (const dmnsn_phong *)specular;
+
+  dmnsn_vector proj = dmnsn_vector_mul(2*dmnsn_vector_dot(ray, normal), normal);
+  dmnsn_vector reflected = dmnsn_vector_sub(proj, ray);
+
+  double specular_factor = dmnsn_vector_dot(reflected, viewer);
+  if (specular_factor < 0.0) {
+    return dmnsn_black;
+  }
+
+  specular_factor = pow(specular_factor, phong->exp);
+  return dmnsn_color_mul(phong->coeff*specular_factor, light);
+}
+
+// A phong finish
+dmnsn_specular *
+dmnsn_new_phong(dmnsn_pool *pool, double coeff, double exp)
+{
+  dmnsn_phong *phong = DMNSN_PALLOC(pool, dmnsn_phong);
+  phong->coeff = coeff;
+  phong->exp = exp;
+
+  dmnsn_specular *specular = &phong->specular;
+  dmnsn_init_specular(specular);
+  specular->specular_fn = dmnsn_phong_specular_fn;
+  return specular;
+}
diff --git a/libdimension/model/finishes/reflection.c b/libdimension/model/finishes/reflection.c
new file mode 100644
index 0000000..afaae8f
--- /dev/null
+++ b/libdimension/model/finishes/reflection.c
@@ -0,0 +1,64 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Reflective finish.
+ */
+
+#include "dimension/model.h"
+#include <math.h>
+#include <stdlib.h>
+
+/// Basic reflective finish type.
+typedef struct dmnsn_basic_reflection {
+  dmnsn_reflection reflection;
+  dmnsn_color min, max;
+  double falloff;
+} dmnsn_basic_reflection;
+
+/// Reflective finish callback.
+static dmnsn_color
+dmnsn_basic_reflection_fn(const dmnsn_reflection *reflection,
+                          dmnsn_color reflect, dmnsn_color color,
+                          dmnsn_vector ray, dmnsn_vector normal)
+{
+  const dmnsn_basic_reflection *basic = (const dmnsn_basic_reflection *)reflection;
+  double coeff = pow(fabs(dmnsn_vector_dot(ray, normal)), basic->falloff);
+
+  return dmnsn_color_illuminate(
+    dmnsn_color_gradient(basic->min, basic->max, coeff),
+    reflect
+  );
+}
+
+dmnsn_reflection *
+dmnsn_new_basic_reflection(dmnsn_pool *pool, dmnsn_color min, dmnsn_color max, double falloff)
+{
+  dmnsn_basic_reflection *basic = DMNSN_PALLOC(pool, dmnsn_basic_reflection);
+  basic->min = min;
+  basic->max = max;
+  basic->falloff = falloff;
+
+  dmnsn_reflection *reflection = &basic->reflection;
+  dmnsn_init_reflection(reflection);
+  reflection->reflection_fn = dmnsn_basic_reflection_fn;
+  return reflection;
+}
diff --git a/libdimension/model/interior.c b/libdimension/model/interior.c
new file mode 100644
index 0000000..82db3ea
--- /dev/null
+++ b/libdimension/model/interior.c
@@ -0,0 +1,47 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Interiors.
+ */
+
+#include "internal.h"
+#include "dimension/model.h"
+#include <stdlib.h>
+
+// Allocate an interior
+dmnsn_interior *
+dmnsn_new_interior(dmnsn_pool *pool)
+{
+  dmnsn_interior *interior = DMNSN_PALLOC(pool, dmnsn_interior);
+  interior->ior = 1.0;
+  return interior;
+}
+
+// Cascade an interior
+void
+dmnsn_interior_cascade(dmnsn_interior *default_interior,
+                       dmnsn_interior **interiorp)
+{
+  if (!*interiorp) {
+    *interiorp = default_interior;
+  }
+}
diff --git a/libdimension/model/light.c b/libdimension/model/light.c
new file mode 100644
index 0000000..6143b3c
--- /dev/null
+++ b/libdimension/model/light.c
@@ -0,0 +1,46 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Lights.
+ */
+
+#include "internal.h"
+#include "dimension/model.h"
+#include <stdlib.h>
+
+// Allocate a new dummy light
+dmnsn_light *
+dmnsn_new_light(dmnsn_pool *pool)
+{
+  dmnsn_light *light = DMNSN_PALLOC(pool, dmnsn_light);
+  dmnsn_init_light(light);
+  return light;
+}
+
+// Initialize a light
+void
+dmnsn_init_light(dmnsn_light *light)
+{
+  light->direction_fn = NULL;
+  light->illumination_fn = NULL;
+  light->shadow_fn = NULL;
+}
diff --git a/libdimension/model/lights/point_light.c b/libdimension/model/lights/point_light.c
new file mode 100644
index 0000000..08aae67
--- /dev/null
+++ b/libdimension/model/lights/point_light.c
@@ -0,0 +1,72 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Point lights.
+ */
+
+#include "dimension/model.h"
+#include <stdlib.h>
+
+/// Point light type.
+typedef struct dmnsn_point_light {
+  dmnsn_light light;
+  dmnsn_vector origin;
+  dmnsn_color color;
+} dmnsn_point_light;
+
+/// Point light direction callback.
+static dmnsn_vector
+dmnsn_point_light_direction_fn(const dmnsn_light *light, dmnsn_vector v)
+{
+  const dmnsn_point_light *point_light = (const dmnsn_point_light *)light;
+  return dmnsn_vector_sub(point_light->origin, v);
+}
+
+/// Point light illumination callback.
+static dmnsn_color
+dmnsn_point_light_illumination_fn(const dmnsn_light *light, dmnsn_vector v)
+{
+  const dmnsn_point_light *point_light = (const dmnsn_point_light *)light;
+  return point_light->color;
+}
+
+/// Point light illumination callback.
+static bool
+dmnsn_point_light_shadow_fn(const dmnsn_light *light, double t)
+{
+  return t < 1.0;
+}
+
+dmnsn_light *
+dmnsn_new_point_light(dmnsn_pool *pool, dmnsn_vector x0, dmnsn_color color)
+{
+  dmnsn_point_light *point_light = DMNSN_PALLOC(pool, dmnsn_point_light);
+  point_light->origin = x0;
+  point_light->color = color;
+
+  dmnsn_light *light = &point_light->light;
+  dmnsn_init_light(light);
+  light->direction_fn = dmnsn_point_light_direction_fn;
+  light->illumination_fn = dmnsn_point_light_illumination_fn;
+  light->shadow_fn = dmnsn_point_light_shadow_fn;
+  return light;
+}
diff --git a/libdimension/model/object.c b/libdimension/model/object.c
new file mode 100644
index 0000000..0473f54
--- /dev/null
+++ b/libdimension/model/object.c
@@ -0,0 +1,110 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Objects.
+ */
+
+#include "internal.h"
+#include "dimension/model.h"
+#include <stdlib.h>
+
+// Allocate a dummy object
+dmnsn_object *
+dmnsn_new_object(dmnsn_pool *pool)
+{
+  dmnsn_object *object = DMNSN_PALLOC(pool, dmnsn_object);
+  dmnsn_init_object(object);
+  return object;
+}
+
+// Initialize a dmnsn_object field
+void
+dmnsn_init_object(dmnsn_object *object)
+{
+  object->vtable = NULL;
+  object->texture = NULL;
+  object->interior = NULL;
+  object->trans = dmnsn_identity_matrix();
+  object->intrinsic_trans = dmnsn_identity_matrix();
+  object->children = NULL;
+  object->split_children = false;
+  object->precomputed = false;
+}
+
+/// Recursively precompute objects.
+static void
+dmnsn_object_precompute_recursive(dmnsn_object *object, dmnsn_matrix pigment_trans)
+{
+  dmnsn_assert(!object->precomputed, "Object double-precomputed.");
+  object->precomputed = true;
+
+  const dmnsn_object_vtable *vtable = object->vtable;
+  dmnsn_assert(vtable->intersection_fn, "Missing intersection function.");
+  dmnsn_assert(vtable->inside_fn, "Missing inside function.");
+  dmnsn_assert(vtable->bounding_fn || vtable->precompute_fn, "Missing bounding and precompute function.");
+
+  // Initialize the texture
+  if (!object->texture->initialized) {
+    dmnsn_texture_initialize(object->texture);
+  }
+
+  dmnsn_matrix total_trans = dmnsn_matrix_mul(object->trans, object->intrinsic_trans);
+
+  // Precompute the object's children
+  if (object->children) {
+    DMNSN_ARRAY_FOREACH (dmnsn_object **, child, object->children) {
+      dmnsn_matrix saved_trans = (*child)->trans;
+      (*child)->trans = dmnsn_matrix_mul(total_trans, saved_trans);
+
+      dmnsn_matrix child_pigment_trans;
+      if ((*child)->texture == NULL || (*child)->texture->pigment == NULL) {
+        // Don't transform cascaded pigments with the child object
+        child_pigment_trans = pigment_trans;
+      } else {
+        child_pigment_trans = dmnsn_matrix_inverse((*child)->trans);
+      }
+
+      dmnsn_texture_cascade(object->texture, &(*child)->texture);
+      dmnsn_interior_cascade(object->interior, &(*child)->interior);
+      dmnsn_object_precompute_recursive(*child, child_pigment_trans);
+      (*child)->trans = saved_trans;
+    }
+  }
+
+  // Precalculate object values
+  object->pigment_trans = pigment_trans;
+  object->trans_inv = dmnsn_matrix_inverse(total_trans);
+  if (vtable->bounding_fn) {
+    object->aabb = vtable->bounding_fn(object, total_trans);
+  }
+  if (vtable->precompute_fn) {
+    vtable->precompute_fn(object);
+  }
+}
+
+// Precompute object properties
+void
+dmnsn_object_precompute(dmnsn_object *object)
+{
+  dmnsn_matrix pigment_trans = dmnsn_matrix_inverse(object->trans);
+  dmnsn_object_precompute_recursive(object, pigment_trans);
+}
diff --git a/libdimension/model/objects/cone.c b/libdimension/model/objects/cone.c
new file mode 100644
index 0000000..26e59ca
--- /dev/null
+++ b/libdimension/model/objects/cone.c
@@ -0,0 +1,207 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Cones/cylinders.
+ */
+
+#include "internal/polynomial.h"
+#include "dimension/model.h"
+#include <math.h>
+
+/// Cone type.
+typedef struct dmnsn_cone {
+  dmnsn_object object;
+  double r1, r2;
+} dmnsn_cone;
+
+/// Intersection callback for a cone.
+static bool
+dmnsn_cone_intersection_fn(const dmnsn_object *object, dmnsn_ray l,
+                           dmnsn_intersection *intersection)
+{
+  const dmnsn_cone *cone = (const dmnsn_cone *)object;
+  double r1 = cone->r1, r2 = cone->r2;
+
+  // Solve (x0 + nx*t)^2 + (z0 + nz*t)^2 == (((r2 - r1)*(y0 + ny*t) + r1 + r2)/2)^2
+  double poly[3], x[2];
+  poly[2] = l.n.x*l.n.x + l.n.z*l.n.z - l.n.y*l.n.y*(r2 - r1)*(r2 - r1)/4.0;
+  poly[1] = 2.0*(l.n.x*l.x0.x + l.n.z*l.x0.z)
+            - l.n.y*(r2 - r1)*(l.x0.y*(r2 - r1) + r2 + r1)/2.0;
+  poly[0] = l.x0.x*l.x0.x + l.x0.z*l.x0.z
+            - (l.x0.y*(r2 - r1) + r2 + r1)*(l.x0.y*(r2 - r1) + r2 + r1)/4.0;
+
+  size_t n = dmnsn_polynomial_solve(poly, 2, x);
+
+  if (n > 0) {
+    double t = x[0];
+    dmnsn_vector p;
+    if (n == 2) {
+      t = dmnsn_min(t, x[1]);
+      p = dmnsn_ray_point(l, t);
+
+      if (p.y <= -1.0 || p.y >= 1.0) {
+        t = dmnsn_max(x[0], x[1]);
+        p = dmnsn_ray_point(l, t);
+      }
+    } else {
+      p = dmnsn_ray_point(l, t);
+    }
+
+    if (t >= 0.0 && p.y >= -1.0 && p.y <= 1.0) {
+      double r = ((r2 - r1)*p.y + r1 + r2)/2.0;
+      dmnsn_vector norm = dmnsn_new_vector(p.x, -r*(r2 - r1)/2.0, p.z);
+      intersection->t      = t;
+      intersection->normal = norm;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/// Inside callback for a cone.
+static bool
+dmnsn_cone_inside_fn(const dmnsn_object *object, dmnsn_vector point)
+{
+  const dmnsn_cone *cone = (const dmnsn_cone *)object;
+  double r1 = cone->r1, r2 = cone->r2;
+  double r = (point.y*(r2 - r1) + r1 + r2)/2.0;
+  return point.x*point.x + point.z*point.z < r*r
+         && point.y > -1.0 && point.y < 1.0;
+}
+
+/// Cone bounding callback.
+static dmnsn_aabb
+dmnsn_cone_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  const dmnsn_cone *cone = (const dmnsn_cone *)object;
+
+  double rmax = dmnsn_max(cone->r1, cone->r2);
+  dmnsn_aabb box = dmnsn_symmetric_aabb(dmnsn_new_vector(rmax, 1.0, rmax));
+  return dmnsn_transform_aabb(trans, box);
+}
+
+/// Cone vtable.
+static const dmnsn_object_vtable dmnsn_cone_vtable = {
+  .intersection_fn = dmnsn_cone_intersection_fn,
+  .inside_fn = dmnsn_cone_inside_fn,
+  .bounding_fn = dmnsn_cone_bounding_fn,
+};
+
+/// Cone cap type.
+typedef struct dmnsn_cone_cap {
+  dmnsn_object object;
+  double r;
+} dmnsn_cone_cap;
+
+/// Cone cap intersection function.
+static bool
+dmnsn_cone_cap_intersection_fn(const dmnsn_object *object, dmnsn_ray l,
+                               dmnsn_intersection *intersection)
+{
+  if (l.n.y != 0.0) {
+    const dmnsn_cone_cap *cap = (const dmnsn_cone_cap *)object;
+    double r = cap->r;
+    double t = -l.x0.y/l.n.y;
+    dmnsn_vector p = dmnsn_ray_point(l, t);
+    if (t >= 0.0 && p.x*p.x + p.z*p.z <= r*r) {
+      intersection->t      = t;
+      intersection->normal = dmnsn_new_vector(0.0, -1.0, 0.0);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/// Inside callback for a cone cap.
+static bool
+dmnsn_cone_cap_inside_fn(const dmnsn_object *object, dmnsn_vector point)
+{
+  return false;
+}
+
+/// Cone cap bounding callback.
+static dmnsn_aabb
+dmnsn_cone_cap_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  const dmnsn_cone_cap *cap = (const dmnsn_cone_cap *)object;
+  dmnsn_aabb box = dmnsn_symmetric_aabb(dmnsn_new_vector(cap->r, 0.0, cap->r));
+  return dmnsn_transform_aabb(trans, box);
+}
+
+/// Cone cap vtable.
+static const dmnsn_object_vtable dmnsn_cone_cap_vtable = {
+  .intersection_fn = dmnsn_cone_cap_intersection_fn,
+  .inside_fn = dmnsn_cone_cap_inside_fn,
+  .bounding_fn = dmnsn_cone_cap_bounding_fn,
+};
+
+/// Allocate a new cone cap.
+dmnsn_object *
+dmnsn_new_cone_cap(dmnsn_pool *pool, double r)
+{
+  dmnsn_cone_cap *cap = DMNSN_PALLOC(pool, dmnsn_cone_cap);
+  cap->r = r;
+
+  dmnsn_object *object = &cap->object;
+  dmnsn_init_object(object);
+  object->vtable = &dmnsn_cone_cap_vtable;
+  return object;
+}
+
+// Allocate a new cone object
+dmnsn_object *
+dmnsn_new_cone(dmnsn_pool *pool, double r1, double r2, bool open)
+{
+  dmnsn_cone *cone = DMNSN_PALLOC(pool, dmnsn_cone);
+  cone->r1 = r1;
+  cone->r2 = r2;
+
+  dmnsn_object *object = &cone->object;
+  dmnsn_init_object(object);
+  object->vtable = &dmnsn_cone_vtable;
+
+  if (open) {
+    return object;
+  }
+
+  // Implement closed cones as a union with the caps
+  dmnsn_object *cap1 = dmnsn_new_cone_cap(pool, r1);
+  dmnsn_object *cap2 = dmnsn_new_cone_cap(pool, r2);
+  cap1->intrinsic_trans = dmnsn_translation_matrix(
+    dmnsn_new_vector(0.0, -1.0, 0.0)
+  );
+  cap2->intrinsic_trans = dmnsn_translation_matrix(
+    dmnsn_new_vector(0.0, +1.0, 0.0)
+  );
+  // Flip the normal around for the top cap
+  cap2->intrinsic_trans.n[1][1] = -1.0;
+
+  dmnsn_array *withcaps = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
+  dmnsn_array_push(withcaps, &cone);
+  dmnsn_array_push(withcaps, &cap1);
+  dmnsn_array_push(withcaps, &cap2);
+  dmnsn_object *cone_cap_union = dmnsn_new_csg_union(pool, withcaps);
+
+  return cone_cap_union;
+}
diff --git a/libdimension/model/objects/csg.c b/libdimension/model/objects/csg.c
new file mode 100644
index 0000000..15008c0
--- /dev/null
+++ b/libdimension/model/objects/csg.c
@@ -0,0 +1,334 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Constructive solid geometry.
+ */
+
+#include "internal.h"
+#include "internal/bvh.h"
+#include "dimension/model.h"
+#include <stdlib.h>
+
+////////////
+// Unions //
+////////////
+
+typedef struct {
+  dmnsn_object object;
+  dmnsn_bvh *bvh;
+} dmnsn_csg_union;
+
+/// CSG union intersection callback.
+static bool
+dmnsn_csg_union_intersection_fn(const dmnsn_object *object,
+                                dmnsn_ray ray,
+                                dmnsn_intersection *intersection)
+{
+  const dmnsn_csg_union *csg = (const dmnsn_csg_union *)object;
+  return dmnsn_bvh_intersection(csg->bvh, ray, intersection, true);
+}
+
+/// CSG union inside callback.
+static bool
+dmnsn_csg_union_inside_fn(const dmnsn_object *object, dmnsn_vector point)
+{
+  const dmnsn_csg_union *csg = (const dmnsn_csg_union *)object;
+  return dmnsn_bvh_inside(csg->bvh, point);
+}
+
+/// CSG union precomputation callback.
+static void
+dmnsn_csg_union_precompute_fn(dmnsn_object *object)
+{
+  dmnsn_csg_union *csg = (dmnsn_csg_union *)object;
+  csg->object.trans_inv = dmnsn_identity_matrix();
+
+  dmnsn_bvh *bvh = dmnsn_new_bvh(csg->object.children, DMNSN_BVH_PRTREE);
+  csg->bvh = bvh;
+  csg->object.aabb = dmnsn_bvh_aabb(bvh);
+}
+
+/// CSG union vtable.
+static const dmnsn_object_vtable dmnsn_csg_union_vtable = {
+  .intersection_fn = dmnsn_csg_union_intersection_fn,
+  .inside_fn = dmnsn_csg_union_inside_fn,
+  .precompute_fn = dmnsn_csg_union_precompute_fn,
+};
+
+/// CSG union destruction callback.
+static void
+dmnsn_csg_union_cleanup(void *ptr)
+{
+  dmnsn_csg_union *csg = ptr;
+  dmnsn_delete_bvh(csg->bvh);
+}
+
+// Bulk-load a union
+dmnsn_object *
+dmnsn_new_csg_union(dmnsn_pool *pool, dmnsn_array *objects)
+{
+  dmnsn_csg_union *csg = DMNSN_PALLOC_TIDY(pool, dmnsn_csg_union, dmnsn_csg_union_cleanup);
+  csg->bvh = NULL;
+
+  dmnsn_object *object = &csg->object;
+  dmnsn_init_object(object);
+
+  object->vtable = &dmnsn_csg_union_vtable;
+  object->children = objects;
+  object->split_children = true;
+
+  return object;
+}
+
+/**
+ * Generic CSG intersection callback.
+ * @param[in]  csg           The CSG object.
+ * @param[in]  ray           The intersection ray.
+ * @param[out] intersection  The intersection data.
+ * @param[in]  inside1       Whether the first object is allowed inside the
+ *                           second object.
+ * @param[in]  inside2       Whether the second object is allowed inside the
+ *                           first object.
+ * @return Whether \p ray intersected \p csg.
+ */
+static bool
+dmnsn_csg_intersection_fn(const dmnsn_object *csg, dmnsn_ray ray,
+                          dmnsn_intersection *intersection,
+                          bool inside1, bool inside2)
+{
+  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
+  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
+
+  dmnsn_intersection i1, i2;
+  bool is_i1 = dmnsn_object_intersection(A, ray, &i1);
+  bool is_i2 = dmnsn_object_intersection(B, ray, &i2);
+
+  double oldt = 0.0;
+  while (is_i1) {
+    i1.ray = ray;
+    i1.t += oldt;
+    oldt = i1.t + dmnsn_epsilon;
+
+    dmnsn_vector point = dmnsn_ray_point(i1.ray, i1.t);
+    if (inside2 ^ dmnsn_object_inside(B, point)) {
+      dmnsn_ray newray = ray;
+      newray.x0 = dmnsn_ray_point(ray, i1.t);
+      newray    = dmnsn_ray_add_epsilon(newray);
+      is_i1 = dmnsn_object_intersection(A, newray, &i1);
+    } else {
+      break;
+    }
+  }
+
+  oldt = 0.0;
+  while (is_i2) {
+    i2.ray = ray;
+    i2.t += oldt;
+    oldt = i2.t + dmnsn_epsilon;
+
+    dmnsn_vector point = dmnsn_ray_point(i2.ray, i2.t);
+    if (inside1 ^ dmnsn_object_inside(A, point)) {
+      dmnsn_ray newray = ray;
+      newray.x0 = dmnsn_ray_point(ray, i2.t);
+      newray    = dmnsn_ray_add_epsilon(newray);
+      is_i2 = dmnsn_object_intersection(B, newray, &i2);
+    } else {
+      break;
+    }
+  }
+
+  if (is_i1 && is_i2) {
+    if (i1.t < i2.t) {
+      *intersection = i1;
+    } else {
+      *intersection = i2;
+    }
+  } else if (is_i1) {
+    *intersection = i1;
+  } else if (is_i2) {
+    *intersection = i2;
+  } else {
+    return false;
+  }
+
+  return true;
+}
+
+///////////////////
+// Intersections //
+///////////////////
+
+/// CSG intersection intersection callback.
+static bool
+dmnsn_csg_intersection_intersection_fn(const dmnsn_object *csg,
+                                       dmnsn_ray ray,
+                                       dmnsn_intersection *intersection)
+{
+  return dmnsn_csg_intersection_fn(csg, ray, intersection, true, true);
+}
+
+/// CSG intersection inside callback.
+static bool
+dmnsn_csg_intersection_inside_fn(const dmnsn_object *csg, dmnsn_vector point)
+{
+  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
+  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
+  return dmnsn_object_inside(A, point) && dmnsn_object_inside(B, point);
+}
+
+/// CSG intersection precomputation callback.
+static void
+dmnsn_csg_intersection_precompute_fn(dmnsn_object *csg)
+{
+  dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
+  dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
+
+  csg->trans_inv = dmnsn_identity_matrix();
+  csg->aabb.min = dmnsn_vector_max(A->aabb.min, B->aabb.min);
+  csg->aabb.max = dmnsn_vector_min(A->aabb.max, B->aabb.max);
+}
+
+/// CSG intersection vtable.
+static const dmnsn_object_vtable dmnsn_csg_intersection_vtable = {
+  .intersection_fn = dmnsn_csg_intersection_intersection_fn,
+  .inside_fn = dmnsn_csg_intersection_inside_fn,
+  .precompute_fn = dmnsn_csg_intersection_precompute_fn,
+};
+
+dmnsn_object *
+dmnsn_new_csg_intersection(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B)
+{
+  dmnsn_object *csg = dmnsn_new_object(pool);
+  csg->vtable = &dmnsn_csg_intersection_vtable;
+
+  csg->children = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
+  dmnsn_array_push(csg->children, &A);
+  dmnsn_array_push(csg->children, &B);
+
+  return csg;
+}
+
+/////////////////
+// Differences //
+/////////////////
+
+/// CSG difference intersection callback.
+static bool
+dmnsn_csg_difference_intersection_fn(const dmnsn_object *csg,
+                                     dmnsn_ray ray,
+                                     dmnsn_intersection *intersection)
+{
+  return dmnsn_csg_intersection_fn(csg, ray, intersection, true, false);
+}
+
+/// CSG difference inside callback.
+static bool
+dmnsn_csg_difference_inside_fn(const dmnsn_object *csg, dmnsn_vector point)
+{
+  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
+  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
+  return dmnsn_object_inside(A, point)  && !dmnsn_object_inside(B, point);
+}
+
+/// CSG difference precomputation callback.
+static void
+dmnsn_csg_difference_precompute_fn(dmnsn_object *csg)
+{
+  dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
+
+  csg->trans_inv = dmnsn_identity_matrix();
+  csg->aabb = A->aabb;
+}
+
+/// CSG difference vtable.
+static const dmnsn_object_vtable dmnsn_csg_difference_vtable = {
+  .intersection_fn = dmnsn_csg_difference_intersection_fn,
+  .inside_fn = dmnsn_csg_difference_inside_fn,
+  .precompute_fn = dmnsn_csg_difference_precompute_fn,
+};
+
+dmnsn_object *
+dmnsn_new_csg_difference(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B)
+{
+  dmnsn_object *csg = dmnsn_new_object(pool);
+  csg->vtable = &dmnsn_csg_difference_vtable;
+
+  csg->children = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
+  dmnsn_array_push(csg->children, &A);
+  dmnsn_array_push(csg->children, &B);
+
+  return csg;
+}
+
+////////////
+// Merges //
+////////////
+
+/// CSG merge intersection callback.
+static bool
+dmnsn_csg_merge_intersection_fn(const dmnsn_object *csg,
+                                dmnsn_ray ray,
+                                dmnsn_intersection *intersection)
+{
+  return dmnsn_csg_intersection_fn(csg, ray, intersection, false, false);
+}
+
+/// CSG merge inside callback.
+static bool
+dmnsn_csg_merge_inside_fn(const dmnsn_object *csg, dmnsn_vector point)
+{
+  const dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
+  const dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
+  return dmnsn_object_inside(A, point) || dmnsn_object_inside(B, point);
+}
+
+/// CSG merge precomputation callback.
+static void
+dmnsn_csg_merge_precompute_fn(dmnsn_object *csg)
+{
+  dmnsn_object *A = *(dmnsn_object **)dmnsn_array_first(csg->children);
+  dmnsn_object *B = *(dmnsn_object **)dmnsn_array_last(csg->children);
+
+  csg->trans_inv = dmnsn_identity_matrix();
+  csg->aabb.min = dmnsn_vector_min(A->aabb.min, B->aabb.min);
+  csg->aabb.max = dmnsn_vector_max(A->aabb.max, B->aabb.max);
+}
+
+/// CSG merge vtable.
+static const dmnsn_object_vtable dmnsn_csg_merge_vtable = {
+  .intersection_fn = dmnsn_csg_merge_intersection_fn,
+  .inside_fn = dmnsn_csg_merge_inside_fn,
+  .precompute_fn = dmnsn_csg_merge_precompute_fn,
+};
+
+dmnsn_object *
+dmnsn_new_csg_merge(dmnsn_pool *pool, dmnsn_object *A, dmnsn_object *B)
+{
+  dmnsn_object *csg = dmnsn_new_object(pool);
+  csg->vtable = &dmnsn_csg_merge_vtable;
+
+  csg->children = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
+  dmnsn_array_push(csg->children, &A);
+  dmnsn_array_push(csg->children, &B);
+
+  return csg;
+}
diff --git a/libdimension/model/objects/cube.c b/libdimension/model/objects/cube.c
new file mode 100644
index 0000000..7d6fe0f
--- /dev/null
+++ b/libdimension/model/objects/cube.c
@@ -0,0 +1,154 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Cubes.
+ */
+
+#include "dimension/model.h"
+#include <math.h>
+
+/// Intersection callback for a cube.
+static bool
+dmnsn_cube_intersection_fn(const dmnsn_object *cube, dmnsn_ray ray,
+                           dmnsn_intersection *intersection)
+{
+  // Clip the given ray against the X, Y, and Z slabs
+
+  dmnsn_vector nmin, nmax;
+  double tmin, tmax;
+
+  double tx1 = (-1.0 - ray.x0.x)/ray.n.x;
+  double tx2 = (+1.0 - ray.x0.x)/ray.n.x;
+
+  if (tx1 < tx2) {
+    tmin = tx1;
+    tmax = tx2;
+    nmin = dmnsn_new_vector(-1.0, 0.0, 0.0);
+    nmax = dmnsn_new_vector(+1.0, 0.0, 0.0);
+  } else {
+    tmin = tx2;
+    tmax = tx1;
+    nmin = dmnsn_new_vector(+1.0, 0.0, 0.0);
+    nmax = dmnsn_new_vector(-1.0, 0.0, 0.0);
+  }
+
+  if (tmin > tmax)
+    return false;
+
+  double ty1 = (-1.0 - ray.x0.y)/ray.n.y;
+  double ty2 = (+1.0 - ray.x0.y)/ray.n.y;
+
+  if (ty1 < ty2) {
+    if (ty1 > tmin) {
+      tmin = ty1;
+      nmin = dmnsn_new_vector(0.0, -1.0, 0.0);
+    }
+    if (ty2 < tmax) {
+      tmax = ty2;
+      nmax = dmnsn_new_vector(0.0, +1.0, 0.0);
+    }
+  } else {
+    if (ty2 > tmin) {
+      tmin = ty2;
+      nmin = dmnsn_new_vector(0.0, +1.0, 0.0);
+    }
+    if (ty1 < tmax) {
+      tmax = ty1;
+      nmax = dmnsn_new_vector(0.0, -1.0, 0.0);
+    }
+  }
+
+  if (tmin > tmax)
+    return false;
+
+  double tz1 = (-1.0 - ray.x0.z)/ray.n.z;
+  double tz2 = (+1.0 - ray.x0.z)/ray.n.z;
+
+  if (tz1 < tz2) {
+    if (tz1 > tmin) {
+      tmin = tz1;
+      nmin = dmnsn_new_vector(0.0, 0.0, -1.0);
+    }
+    if (tz2 < tmax) {
+      tmax = tz2;
+      nmax = dmnsn_new_vector(0.0, 0.0, +1.0);
+    }
+  } else {
+    if (tz2 > tmin) {
+      tmin = tz2;
+      nmin = dmnsn_new_vector(0.0, 0.0, +1.0);
+    }
+    if (tz1 < tmax) {
+      tmax = tz1;
+      nmax = dmnsn_new_vector(0.0, 0.0, -1.0);
+    }
+  }
+
+  if (tmin > tmax)
+    return false;
+
+  if (tmin < 0.0) {
+    tmin = tmax;
+    nmin = nmax;
+  }
+
+  if (tmin >= 0.0) {
+    intersection->t      = tmin;
+    intersection->normal = nmin;
+    return true;
+  } else {
+    return false;
+  }
+}
+
+/// Inside callback for a cube.
+static bool
+dmnsn_cube_inside_fn(const dmnsn_object *cube, dmnsn_vector point)
+{
+  return point.x > -1.0 && point.x < 1.0
+      && point.y > -1.0 && point.y < 1.0
+      && point.z > -1.0 && point.z < 1.0;
+}
+
+/// Boundary callback for a cube.
+static dmnsn_aabb
+dmnsn_cube_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  dmnsn_aabb box = dmnsn_symmetric_aabb(dmnsn_new_vector(1.0, 1.0, 1.0));
+  return dmnsn_transform_aabb(trans, box);
+}
+
+/// Cube vtable.
+static const dmnsn_object_vtable dmnsn_cube_vtable = {
+  .intersection_fn = dmnsn_cube_intersection_fn,
+  .inside_fn = dmnsn_cube_inside_fn,
+  .bounding_fn = dmnsn_cube_bounding_fn,
+};
+
+// Allocate a new cube object
+dmnsn_object *
+dmnsn_new_cube(dmnsn_pool *pool)
+{
+  dmnsn_object *cube = dmnsn_new_object(pool);
+  cube->vtable = &dmnsn_cube_vtable;
+  return cube;
+}
diff --git a/libdimension/model/objects/plane.c b/libdimension/model/objects/plane.c
new file mode 100644
index 0000000..b34d8aa
--- /dev/null
+++ b/libdimension/model/objects/plane.c
@@ -0,0 +1,88 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Planes.
+ */
+
+#include "dimension/model.h"
+#include <math.h>
+#include <stdlib.h>
+
+/// Plane type.
+typedef struct {
+  dmnsn_object object;
+  dmnsn_vector normal;
+} dmnsn_plane;
+
+/// Returns the closest intersection of `ray' with `plane'.
+static bool
+dmnsn_plane_intersection_fn(const dmnsn_object *object, dmnsn_ray ray,
+                            dmnsn_intersection *intersection)
+{
+  const dmnsn_plane *plane = (const dmnsn_plane *)object;
+  dmnsn_vector normal = plane->normal;
+
+  double den = dmnsn_vector_dot(ray.n, normal);
+  if (den != 0.0) {
+    double t = -dmnsn_vector_dot(ray.x0, normal)/den;
+    if (t >= 0.0) {
+      intersection->t      = t;
+      intersection->normal = normal;
+      return true;
+    }
+  }
+  return false;
+}
+
+/// Return whether a point is inside a plane.
+static bool
+dmnsn_plane_inside_fn(const dmnsn_object *object, dmnsn_vector point)
+{
+  const dmnsn_plane *plane = (const dmnsn_plane *)object;
+  return dmnsn_vector_dot(point, plane->normal) < 0.0;
+}
+
+/// Plane bounding callback.
+static dmnsn_aabb
+dmnsn_plane_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  return dmnsn_infinite_aabb();
+}
+
+/// Plane vtable.
+static const dmnsn_object_vtable dmnsn_plane_vtable = {
+  .intersection_fn = dmnsn_plane_intersection_fn,
+  .inside_fn = dmnsn_plane_inside_fn,
+  .bounding_fn = dmnsn_plane_bounding_fn,
+};
+
+dmnsn_object *
+dmnsn_new_plane(dmnsn_pool *pool, dmnsn_vector normal)
+{
+  dmnsn_plane *plane = DMNSN_PALLOC(pool, dmnsn_plane);
+  plane->normal = normal;
+
+  dmnsn_object *object = &plane->object;
+  dmnsn_init_object(object);
+  object->vtable = &dmnsn_plane_vtable;
+  return object;
+}
diff --git a/libdimension/model/objects/sphere.c b/libdimension/model/objects/sphere.c
new file mode 100644
index 0000000..e1ca784
--- /dev/null
+++ b/libdimension/model/objects/sphere.c
@@ -0,0 +1,116 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Spheres.
+ */
+
+#include "internal.h"
+#include "internal/polynomial.h"
+#include "dimension/model.h"
+
+/// Sphere intersection callback.
+static bool
+dmnsn_sphere_intersection_fn(const dmnsn_object *sphere, dmnsn_ray l,
+                             dmnsn_intersection *intersection)
+{
+  // Solve (x0 + nx*t)^2 + (y0 + ny*t)^2 + (z0 + nz*t)^2 == 1
+  double poly[3], x[2];
+  poly[2] = dmnsn_vector_dot(l.n, l.n);
+  poly[1] = 2.0*dmnsn_vector_dot(l.n, l.x0);
+  poly[0] = dmnsn_vector_dot(l.x0, l.x0) - 1.0;
+
+  size_t n = dmnsn_polynomial_solve(poly, 2, x);
+  if (n == 0) {
+    return false;
+  }
+
+  double t = x[0];
+  // Optimize for the case where we're outside the sphere
+  if (dmnsn_likely(n == 2)) {
+    t = dmnsn_min(t, x[1]);
+  }
+
+  intersection->t = t;
+  intersection->normal = dmnsn_ray_point(l, t);
+  return true;
+}
+
+/// Sphere inside callback.
+static bool
+dmnsn_sphere_inside_fn(const dmnsn_object *sphere, dmnsn_vector point)
+{
+  return point.x*point.x + point.y*point.y + point.z*point.z < 1.0;
+}
+
+/// Helper for sphere bounding box calculation.
+static inline double
+dmnsn_implicit_dot(double row[4])
+{
+  double ret = 0.0;
+  for (int i = 0; i < 3; ++i) {
+    ret += row[i]*row[i];
+  }
+  return ret;
+}
+
+/// Sphere bounding callback.
+static dmnsn_aabb
+dmnsn_sphere_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  // Get a tight bound using the quadric representation of a sphere.  For
+  // details, see
+  // http://tavianator.com/2014/06/exact-bounding-boxes-for-spheres-ellipsoids
+
+  dmnsn_aabb box;
+
+  double cx = trans.n[0][3];
+  double dx = sqrt(dmnsn_implicit_dot(trans.n[0]));
+  box.min.x = cx - dx;
+  box.max.x = cx + dx;
+
+  double cy = trans.n[1][3];
+  double dy = sqrt(dmnsn_implicit_dot(trans.n[1]));
+  box.min.y = cy - dy;
+  box.max.y = cy + dy;
+
+  double cz = trans.n[2][3];
+  double dz = sqrt(dmnsn_implicit_dot(trans.n[2]));
+  box.min.z = cz - dz;
+  box.max.z = cz + dz;
+
+  return box;
+}
+
+/// Sphere vtable.
+static const dmnsn_object_vtable dmnsn_sphere_vtable = {
+  .intersection_fn = dmnsn_sphere_intersection_fn,
+  .inside_fn = dmnsn_sphere_inside_fn,
+  .bounding_fn = dmnsn_sphere_bounding_fn,
+};
+
+dmnsn_object *
+dmnsn_new_sphere(dmnsn_pool *pool)
+{
+  dmnsn_object *sphere = dmnsn_new_object(pool);
+  sphere->vtable = &dmnsn_sphere_vtable;
+  return sphere;
+}
diff --git a/libdimension/model/objects/torus.c b/libdimension/model/objects/torus.c
new file mode 100644
index 0000000..b4baebd
--- /dev/null
+++ b/libdimension/model/objects/torus.c
@@ -0,0 +1,173 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Torii.  A special case of a quartic.
+ */
+
+#include "internal/polynomial.h"
+#include "dimension/model.h"
+
+/// Torus type.
+typedef struct {
+  dmnsn_object object;
+  double major, minor;
+} dmnsn_torus;
+
+/// Bound the torus in a cylindrical shell.
+static inline bool
+dmnsn_torus_bound_intersection(const dmnsn_torus *torus, dmnsn_ray l)
+{
+  double R = torus->major, r = torus->minor;
+  double rmax = R + r, rmin = R - r;
+  double rmax2 = rmax*rmax, rmin2 = rmin*rmin;
+
+  // Try the caps first
+  double tlower = (-r - l.x0.y)/l.n.y;
+  double tupper = (+r - l.x0.y)/l.n.y;
+  dmnsn_vector lower = dmnsn_ray_point(l, tlower);
+  dmnsn_vector upper = dmnsn_ray_point(l, tupper);
+  double ldist2 = lower.x*lower.x + lower.z*lower.z;
+  double udist2 = upper.x*upper.x + upper.z*upper.z;
+  if ((ldist2 < rmin2 || ldist2 > rmax2) && (udist2 < rmin2 || udist2 > rmax2)) {
+    // No valid intersection with the caps, try the cylinder walls
+    double dist2 = l.x0.x*l.x0.x + l.x0.z*l.x0.z;
+    double bigcyl[3], smallcyl[3];
+    bigcyl[2]   = smallcyl[2] = l.n.x*l.n.x + l.n.z*l.n.z;
+    bigcyl[1]   = smallcyl[1] = 2.0*(l.n.x*l.x0.x + l.n.z*l.x0.z);
+    bigcyl[0]   = dist2 - rmax2;
+    smallcyl[0] = dist2 - rmin2;
+
+    double x[4];
+    size_t n = dmnsn_polynomial_solve(bigcyl, 2, x);
+    n += dmnsn_polynomial_solve(smallcyl, 2, x + n);
+
+    size_t i;
+    for (i = 0; i < n; ++i) {
+      dmnsn_vector p = dmnsn_ray_point(l, x[i]);
+      if (p.y >= -r && p.y <= r)
+        break;
+    }
+
+    if (i == n) {
+      // No valid intersection found
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/// Torus intersection callback.
+static bool
+dmnsn_torus_intersection_fn(const dmnsn_object *object, dmnsn_ray l,
+                            dmnsn_intersection *intersection)
+{
+  const dmnsn_torus *torus = (const dmnsn_torus *)object;
+  double R = torus->major, r = torus->minor;
+  double RR = R*R, rr = r*r;
+
+  if (!dmnsn_torus_bound_intersection(torus, l)) {
+    return false;
+  }
+
+  // This bit of algebra here is correct
+  dmnsn_vector x0mod = dmnsn_new_vector(l.x0.x, -l.x0.y, l.x0.z);
+  dmnsn_vector nmod  = dmnsn_new_vector(l.n.x,  -l.n.y,  l.n.z);
+  double nn      = dmnsn_vector_dot(l.n, l.n);
+  double nx0     = dmnsn_vector_dot(l.n, l.x0);
+  double x0x0    = dmnsn_vector_dot(l.x0, l.x0);
+  double x0x0mod = dmnsn_vector_dot(l.x0, x0mod);
+  double nx0mod  = dmnsn_vector_dot(l.n, x0mod);
+  double nnmod   = dmnsn_vector_dot(l.n, nmod);
+
+  double poly[5];
+  poly[4] = nn*nn;
+  poly[3] = 4*nn*nx0;
+  poly[2] = 2.0*(nn*(x0x0 - rr) + 2.0*nx0*nx0 - RR*nnmod);
+  poly[1] = 4.0*(nx0*(x0x0 - rr) - RR*nx0mod);
+  poly[0] = x0x0*x0x0 + RR*(RR - 2.0*x0x0mod) - rr*(2.0*(RR + x0x0) - rr);
+
+  double x[4];
+  size_t n = dmnsn_polynomial_solve(poly, 4, x);
+  if (n == 0)
+    return false;
+
+  double t = x[0];
+  for (size_t i = 1; i < n; ++i) {
+    t = dmnsn_min(t, x[i]);
+  }
+
+  if (t < 0.0) {
+    return false;
+  }
+
+  dmnsn_vector p = dmnsn_ray_point(l, t);
+  dmnsn_vector center = dmnsn_vector_mul(
+    R,
+    dmnsn_vector_normalized(dmnsn_new_vector(p.x, 0.0, p.z))
+  );
+  dmnsn_vector normal = dmnsn_vector_sub(p, center);
+
+  intersection->t      = t;
+  intersection->normal = normal;
+  return true;
+}
+
+/// Torus inside callback.
+static bool
+dmnsn_torus_inside_fn(const dmnsn_object *object, dmnsn_vector point)
+{
+  const dmnsn_torus *torus = (const dmnsn_torus *)object;
+  double dmajor = torus->major - sqrt(point.x*point.x + point.z*point.z);
+  return dmajor*dmajor + point.y*point.y < torus->minor*torus->minor;
+}
+
+/// Torus bounding callback.
+static dmnsn_aabb
+dmnsn_torus_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  const dmnsn_torus *torus = (const dmnsn_torus *)object;
+
+  double extent = torus->major + torus->minor;
+  dmnsn_aabb box = dmnsn_symmetric_aabb(dmnsn_new_vector(extent, torus->minor, extent));
+  return dmnsn_transform_aabb(trans, box);
+}
+
+/// Torus vtable.
+static const dmnsn_object_vtable dmnsn_torus_vtable = {
+  .intersection_fn = dmnsn_torus_intersection_fn,
+  .inside_fn = dmnsn_torus_inside_fn,
+  .bounding_fn = dmnsn_torus_bounding_fn,
+};
+
+dmnsn_object *
+dmnsn_new_torus(dmnsn_pool *pool, double major, double minor)
+{
+  dmnsn_torus *torus = DMNSN_PALLOC(pool, dmnsn_torus);
+  torus->major = major;
+  torus->minor = minor;
+
+  dmnsn_object *object = &torus->object;
+  dmnsn_init_object(object);
+  object->vtable = &dmnsn_torus_vtable;
+  return object;
+}
diff --git a/libdimension/model/objects/triangle.c b/libdimension/model/objects/triangle.c
new file mode 100644
index 0000000..5af3301
--- /dev/null
+++ b/libdimension/model/objects/triangle.c
@@ -0,0 +1,163 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Triangles.  See
+ * http://tavianator.com/2014/05/a-beautiful-raytriangle-intersection-method/
+ * for a description of the intersection algorithm.
+ */
+
+#include "internal.h"
+#include "dimension/model.h"
+
+/// Optimized ray/triangle intersection test.
+static inline bool
+dmnsn_ray_triangle_intersection(dmnsn_ray l, double *t, double *u, double *v)
+{
+  // See the change of basis in dmnsn_triangle_basis()
+  *t = -l.x0.z/l.n.z;
+  *u = l.x0.x + (*t)*l.n.x;
+  *v = l.x0.y + (*t)*l.n.y;
+  return *t >= 0.0 && *u >= 0.0 && *v >= 0.0 && *u + *v <= 1.0;
+}
+
+/// Triangle intersection callback.
+DMNSN_HOT static bool
+dmnsn_triangle_intersection_fn(const dmnsn_object *object, dmnsn_ray l,
+                               dmnsn_intersection *intersection)
+{
+  double t, u, v;
+  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
+    intersection->t = t;
+    intersection->normal = dmnsn_z;
+    return true;
+  }
+
+  return false;
+}
+
+/// Triangle inside callback.
+static bool
+dmnsn_triangle_inside_fn(const dmnsn_object *object, dmnsn_vector point)
+{
+  return false;
+}
+
+/// Triangle bounding callback.
+static dmnsn_aabb
+dmnsn_triangle_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  dmnsn_vector a = dmnsn_transform_point(trans, dmnsn_zero);
+  dmnsn_vector b = dmnsn_transform_point(trans, dmnsn_x);
+  dmnsn_vector c = dmnsn_transform_point(trans, dmnsn_y);
+
+  dmnsn_aabb box = dmnsn_new_aabb(a, a);
+  box = dmnsn_aabb_swallow(box, b);
+  box = dmnsn_aabb_swallow(box, c);
+  return box;
+}
+
+/// Triangle vtable.
+static const dmnsn_object_vtable dmnsn_triangle_vtable = {
+  .intersection_fn = dmnsn_triangle_intersection_fn,
+  .inside_fn = dmnsn_triangle_inside_fn,
+  .bounding_fn = dmnsn_triangle_bounding_fn,
+};
+
+/// Smooth triangle type.
+typedef struct {
+  dmnsn_object object;
+  dmnsn_vector na, nab, nac;
+} dmnsn_smooth_triangle;
+
+/// Smooth triangle intersection callback.
+DMNSN_HOT static bool
+dmnsn_smooth_triangle_intersection_fn(const dmnsn_object *object, dmnsn_ray l,
+                                      dmnsn_intersection *intersection)
+{
+  const dmnsn_smooth_triangle *triangle = (const dmnsn_smooth_triangle *)object;
+
+  double t, u, v;
+  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
+    intersection->t = t;
+    intersection->normal = dmnsn_vector_add(
+      triangle->na,
+      dmnsn_vector_add(
+        dmnsn_vector_mul(u, triangle->nab),
+        dmnsn_vector_mul(v, triangle->nac)
+      )
+    );
+    return true;
+  }
+
+  return false;
+}
+
+/// Smooth triangle vtable.
+static const dmnsn_object_vtable dmnsn_smooth_triangle_vtable = {
+  .intersection_fn = dmnsn_smooth_triangle_intersection_fn,
+  .inside_fn = dmnsn_triangle_inside_fn,
+  .bounding_fn = dmnsn_triangle_bounding_fn,
+};
+
+/// Make a change-of-basis matrix.
+static inline dmnsn_matrix
+dmnsn_triangle_basis(dmnsn_vector vertices[3])
+{
+  // The new vector space has corners at <1, 0, 0>, <0, 1, 0>, and 0,
+  // corresponding to the basis (ab, ac, ab X ac).
+  dmnsn_vector ab = dmnsn_vector_sub(vertices[1], vertices[0]);
+  dmnsn_vector ac = dmnsn_vector_sub(vertices[2], vertices[0]);
+  dmnsn_vector normal = dmnsn_vector_cross(ab, ac);
+  return dmnsn_new_matrix4(ab, ac, normal, vertices[0]);
+}
+
+dmnsn_object *
+dmnsn_new_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3])
+{
+  dmnsn_object *object = dmnsn_new_object(pool);
+  object->vtable = &dmnsn_triangle_vtable;
+  object->intrinsic_trans = dmnsn_triangle_basis(vertices);
+  return object;
+}
+
+dmnsn_object *
+dmnsn_new_smooth_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3], dmnsn_vector normals[3])
+{
+  dmnsn_matrix P = dmnsn_triangle_basis(vertices);
+
+  // Transform the given normals.
+  dmnsn_vector na = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[0]));
+  dmnsn_vector nb = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[1]));
+  dmnsn_vector nc = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[2]));
+
+  dmnsn_smooth_triangle *triangle = DMNSN_PALLOC(pool, dmnsn_smooth_triangle);
+  triangle->na  = na;
+  triangle->nab = dmnsn_vector_sub(nb, na);
+  triangle->nac = dmnsn_vector_sub(nc, na);
+
+  dmnsn_object *object = &triangle->object;
+  dmnsn_init_object(object);
+  object->vtable = &dmnsn_smooth_triangle_vtable;
+  object->intrinsic_trans = P;
+
+  return object;
+}
diff --git a/libdimension/model/objects/triangle_fan.c b/libdimension/model/objects/triangle_fan.c
new file mode 100644
index 0000000..93768a9
--- /dev/null
+++ b/libdimension/model/objects/triangle_fan.c
@@ -0,0 +1,347 @@
+/*************************************************************************
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Triangle fans.  See
+ * http://tavianator.com/2014/05/a-beautiful-raymesh-intersection-algorithm/
+ * for a description of the intersection algorithm.
+ */
+
+#include "internal.h"
+#include "dimension/model.h"
+
+/// Triangle fan type.
+typedef struct {
+  dmnsn_object object;
+  size_t ncoeffs;
+  double coeffs[][6];
+} dmnsn_triangle_fan;
+
+/// Change basis from one triangle to the next.
+static inline dmnsn_vector
+dmnsn_change_basis(const double coeffs[6], dmnsn_vector v)
+{
+  return dmnsn_new_vector(
+    coeffs[0]*v.x + coeffs[1]*v.z + v.y,
+    coeffs[2]*v.x + coeffs[3]*v.z,
+    coeffs[4]*v.x + coeffs[5]*v.z
+  );
+}
+
+/// Change basis from one triangle to the next for a normal vector.
+static inline dmnsn_vector
+dmnsn_change_normal_basis(const double coeffs[6], dmnsn_vector n)
+{
+  return dmnsn_new_vector(
+    coeffs[0]*n.x + coeffs[2]*n.y + coeffs[4]*n.z,
+    n.x,
+    coeffs[1]*n.x + coeffs[3]*n.y + coeffs[5]*n.z
+  );
+}
+
+/// Change basis from one triangle to the next for a ray
+static inline dmnsn_ray
+dmnsn_change_ray_basis(const double coeffs[6], dmnsn_ray l)
+{
+  return dmnsn_new_ray(dmnsn_change_basis(coeffs, l.x0), dmnsn_change_basis(coeffs, l.n));
+}
+
+/// Store the compressed incremental matrix.
+static inline void
+dmnsn_compress_coeffs(double coeffs[6], dmnsn_matrix incremental)
+{
+  coeffs[0] = incremental.n[0][0];
+  coeffs[1] = incremental.n[0][2];
+  coeffs[2] = incremental.n[1][0];
+  coeffs[3] = incremental.n[1][2];
+  coeffs[4] = incremental.n[2][0];
+  coeffs[5] = incremental.n[2][2];
+}
+
+/// Decompress the incremental matrix.
+static inline dmnsn_matrix
+dmnsn_decompress_coeffs(const double coeffs[6])
+{
+  dmnsn_matrix incremental = dmnsn_new_matrix(
+    coeffs[0], 1.0, coeffs[1], 0.0,
+    coeffs[2], 0.0, coeffs[3], 0.0,
+    coeffs[4], 0.0, coeffs[5], 0.0
+  );
+  return incremental;
+}
+
+/// Make a change-of-basis matrix for a triangle.
+static inline dmnsn_matrix
+dmnsn_triangle_basis(dmnsn_vector a, dmnsn_vector ab, dmnsn_vector ac)
+{
+  dmnsn_vector normal = dmnsn_vector_cross(ab, ac);
+  return dmnsn_new_matrix4(ab, ac, normal, a);
+}
+
+/// Optimized ray/triangle intersection test.
+static inline bool
+dmnsn_ray_triangle_intersection(dmnsn_ray l, double *t, double *u, double *v)
+{
+  *t = -l.x0.z/l.n.z;
+  *u = l.x0.x + (*t)*l.n.x;
+  *v = l.x0.y + (*t)*l.n.y;
+  return *t >= 0.0 && *u >= 0.0 && *v >= 0.0 && *u + *v <= 1.0;
+}
+
+/// Triangle fan intersection callback.
+DMNSN_HOT static bool
+dmnsn_triangle_fan_intersection_fn(const dmnsn_object *object, dmnsn_ray l, dmnsn_intersection *intersection)
+{
+  const dmnsn_triangle_fan *fan = (const dmnsn_triangle_fan *)object;
+
+  double t, u, v;
+
+  double best_t = INFINITY;
+  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
+    best_t = t;
+  }
+
+  dmnsn_vector normal = dmnsn_z;
+  dmnsn_vector best_normal = normal;
+
+  for (size_t i = 0; i < fan->ncoeffs; ++i) {
+    const double *coeffs = fan->coeffs[i];
+    l = dmnsn_change_ray_basis(coeffs, l);
+    normal = dmnsn_change_normal_basis(coeffs, normal);
+
+    if (dmnsn_ray_triangle_intersection(l, &t, &u, &v) && t < best_t) {
+      best_t = t;
+      best_normal = normal;
+    }
+  }
+
+  if (!isinf(best_t)) {
+    intersection->t = t;
+    intersection->normal = best_normal;
+    return true;
+  }
+
+  return false;
+}
+
+/// Triangle fan inside callback.
+static bool
+dmnsn_triangle_fan_inside_fn(const dmnsn_object *object, dmnsn_vector point)
+{
+  return false;
+}
+
+/// Computes the bounding box for the first triangle
+static inline dmnsn_aabb
+dmnsn_bound_first_triangle(dmnsn_matrix trans)
+{
+  dmnsn_vector a = dmnsn_transform_point(trans, dmnsn_zero);
+  dmnsn_vector b = dmnsn_transform_point(trans, dmnsn_x);
+  dmnsn_vector c = dmnsn_transform_point(trans, dmnsn_y);
+
+  dmnsn_aabb box = dmnsn_new_aabb(a, a);
+  box = dmnsn_aabb_swallow(box, b);
+  box = dmnsn_aabb_swallow(box, c);
+
+  return box;
+}
+
+/// Triangle fan bounding callback.
+static dmnsn_aabb
+dmnsn_triangle_fan_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  const dmnsn_triangle_fan *fan = (const dmnsn_triangle_fan *)object;
+
+  dmnsn_aabb box = dmnsn_bound_first_triangle(trans);
+
+  for (size_t i = 0; i < fan->ncoeffs; ++i) {
+    dmnsn_matrix incremental = dmnsn_decompress_coeffs(fan->coeffs[i]);
+    trans = dmnsn_matrix_mul(trans, dmnsn_matrix_inverse(incremental));
+    dmnsn_vector vertex = dmnsn_transform_point(trans, dmnsn_y);
+    box = dmnsn_aabb_swallow(box, vertex);
+  }
+
+  return box;
+}
+
+/// Triangle fan vtable.
+static dmnsn_object_vtable dmnsn_triangle_fan_vtable = {
+  .intersection_fn = dmnsn_triangle_fan_intersection_fn,
+  .inside_fn = dmnsn_triangle_fan_inside_fn,
+  .bounding_fn = dmnsn_triangle_fan_bounding_fn,
+};
+
+/// Smooth triangle fan type.
+typedef struct dmnsn_smooth_triangle_fan {
+  dmnsn_object object;
+  dmnsn_vector na, nab, nac;
+  size_t ncoeffs;
+  double coeffs[][9]; ///< 0-6 is same as dmnsn_triangle_fan, 6-9 is the normal
+} dmnsn_smooth_triangle_fan;
+
+/// Smooth triangle fan intersection callback.
+DMNSN_HOT static bool
+dmnsn_smooth_triangle_fan_intersection_fn(const dmnsn_object *object, dmnsn_ray l, dmnsn_intersection *intersection)
+{
+  const dmnsn_smooth_triangle_fan *fan = (const dmnsn_smooth_triangle_fan *)object;
+
+  dmnsn_vector nab = fan->nab;
+  dmnsn_vector nac = fan->nac;
+
+  double t, u, v;
+
+  double best_t = INFINITY;
+  dmnsn_vector best_normal;
+  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
+    best_t = t;
+    best_normal = dmnsn_vector_add(dmnsn_vector_mul(u, nab), dmnsn_vector_mul(v, nac));
+  }
+
+  for (size_t i = 0; i < fan->ncoeffs; ++i) {
+    const double *coeffs = fan->coeffs[i];
+    l = dmnsn_change_ray_basis(coeffs, l);
+    nab = nac;
+    nac = dmnsn_new_vector(coeffs[6], coeffs[7], coeffs[8]);
+
+    if (dmnsn_ray_triangle_intersection(l, &t, &u, &v) && t < best_t) {
+      best_t = t;
+      best_normal = dmnsn_vector_add(dmnsn_vector_mul(u, nab), dmnsn_vector_mul(v, nac));
+    }
+  }
+
+  if (!isinf(best_t)) {
+    intersection->t = t;
+    intersection->normal = dmnsn_vector_add(fan->na, best_normal);
+    return true;
+  }
+
+  return false;
+}
+
+/// Smooth triangle fan bounding callback.
+static dmnsn_aabb
+dmnsn_smooth_triangle_fan_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
+{
+  const dmnsn_smooth_triangle_fan *fan = (const dmnsn_smooth_triangle_fan *)object;
+
+  dmnsn_aabb box = dmnsn_bound_first_triangle(trans);
+
+  for (size_t i = 0; i < fan->ncoeffs; ++i) {
+    dmnsn_matrix incremental = dmnsn_decompress_coeffs(fan->coeffs[i]);
+    trans = dmnsn_matrix_mul(trans, dmnsn_matrix_inverse(incremental));
+    dmnsn_vector vertex = dmnsn_transform_point(trans, dmnsn_y);
+    box = dmnsn_aabb_swallow(box, vertex);
+  }
+
+  return box;
+}
+
+/// Smooth triangle fan vtable.
+static dmnsn_object_vtable dmnsn_smooth_triangle_fan_vtable = {
+  .intersection_fn = dmnsn_smooth_triangle_fan_intersection_fn,
+  .inside_fn = dmnsn_triangle_fan_inside_fn,
+  .bounding_fn = dmnsn_smooth_triangle_fan_bounding_fn,
+};
+
+dmnsn_object *
+dmnsn_new_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], size_t nvertices)
+{
+  dmnsn_assert(nvertices >= 3, "Not enough vertices for one triangle");
+
+  size_t ncoeffs = nvertices - 3;
+  dmnsn_triangle_fan *fan = dmnsn_palloc(pool, sizeof(dmnsn_triangle_fan) + ncoeffs*sizeof(double[6]));
+  fan->ncoeffs = ncoeffs;
+
+  dmnsn_object *object = &fan->object;
+  dmnsn_init_object(object);
+  object->vtable = &dmnsn_triangle_fan_vtable;
+
+  // Compute the initial matrix and the coefficients
+  dmnsn_vector a = vertices[0];
+  dmnsn_vector ab = dmnsn_vector_sub(vertices[1], a);
+  dmnsn_vector ac = dmnsn_vector_sub(vertices[2], a);
+  dmnsn_matrix P = dmnsn_triangle_basis(a, ab, ac);
+  object->intrinsic_trans = P;
+
+  for (size_t i = 0; i < ncoeffs; ++i) {
+    ab = ac;
+    ac = dmnsn_vector_sub(vertices[i + 3], a);
+
+    dmnsn_matrix newP = dmnsn_triangle_basis(a, ab, ac);
+    dmnsn_matrix incremental = dmnsn_matrix_mul(dmnsn_matrix_inverse(newP), P);
+    dmnsn_compress_coeffs(fan->coeffs[i], incremental);
+
+    P = newP;
+  }
+
+  return object;
+}
+
+dmnsn_object *
+dmnsn_new_smooth_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], dmnsn_vector normals[], size_t nvertices)
+{
+  dmnsn_assert(nvertices >= 3, "Not enough vertices for one triangle");
+
+  size_t ncoeffs = nvertices - 3;
+  dmnsn_smooth_triangle_fan *fan = dmnsn_palloc(pool, sizeof(dmnsn_smooth_triangle_fan) + ncoeffs*sizeof(double[9]));
+  fan->ncoeffs = ncoeffs;
+
+  dmnsn_object *object = &fan->object;
+  dmnsn_init_object(object);
+  object->vtable = &dmnsn_smooth_triangle_fan_vtable;
+
+  // Compute the initial matrix
+  dmnsn_vector a = vertices[0];
+  dmnsn_vector ab = dmnsn_vector_sub(vertices[1], a);
+  dmnsn_vector ac = dmnsn_vector_sub(vertices[2], a);
+  dmnsn_matrix P = dmnsn_triangle_basis(a, ab, ac);
+  dmnsn_matrix Pabc = P;
+  object->intrinsic_trans = P;
+
+  // Transform the first three normals
+  dmnsn_vector na = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[0]));
+  dmnsn_vector nb = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[1]));
+  dmnsn_vector nc = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[2]));
+  fan->na = na;
+  fan->nab = dmnsn_vector_sub(nb, na);
+  fan->nac = dmnsn_vector_sub(nc, na);
+
+  // Compute the coefficients
+  for (size_t i = 0; i < ncoeffs; ++i) {
+    ab = ac;
+    ac = dmnsn_vector_sub(vertices[i + 3], a);
+
+    dmnsn_matrix newP = dmnsn_triangle_basis(a, ab, ac);
+    dmnsn_matrix incremental = dmnsn_matrix_mul(dmnsn_matrix_inverse(newP), P);
+    double *coeffs = fan->coeffs[i];
+    dmnsn_compress_coeffs(coeffs, incremental);
+
+    nc = dmnsn_vector_normalized(dmnsn_transform_normal(Pabc, normals[i + 3]));
+    dmnsn_vector nac = dmnsn_vector_sub(nc, na);
+    coeffs[6] = nac.x;
+    coeffs[7] = nac.y;
+    coeffs[8] = nac.z;
+
+    P = newP;
+  }
+
+  return object;
+}
diff --git a/libdimension/model/pigment.c b/libdimension/model/pigment.c
new file mode 100644
index 0000000..a8c7c0b
--- /dev/null
+++ b/libdimension/model/pigment.c
@@ -0,0 +1,72 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pigments.
+ */
+
+#include "dimension/model.h"
+
+// Allocate a dummy pigment
+dmnsn_pigment *
+dmnsn_new_pigment(dmnsn_pool *pool)
+{
+  dmnsn_pigment *pigment = DMNSN_PALLOC(pool, dmnsn_pigment);
+  dmnsn_init_pigment(pigment);
+  return pigment;
+}
+
+// Initialize a pigment
+void
+dmnsn_init_pigment(dmnsn_pigment *pigment)
+{
+  pigment->pigment_fn = NULL;
+  pigment->initialize_fn = NULL;
+  pigment->trans = dmnsn_identity_matrix();
+  pigment->quick_color = DMNSN_TCOLOR(dmnsn_black);
+  pigment->initialized = false;
+}
+
+// Precompute pigment properties
+void
+dmnsn_pigment_initialize(dmnsn_pigment *pigment)
+{
+  dmnsn_assert(!pigment->initialized, "Pigment double-initialized.");
+  pigment->initialized = true;
+
+  if (pigment->initialize_fn) {
+    pigment->initialize_fn(pigment);
+  }
+
+  pigment->trans_inv = dmnsn_matrix_inverse(pigment->trans);
+}
+
+// Evaluate a pigment
+dmnsn_tcolor
+dmnsn_pigment_evaluate(const dmnsn_pigment *pigment, dmnsn_vector v)
+{
+  if (pigment->pigment_fn) {
+    dmnsn_vector v_trans = dmnsn_transform_point(pigment->trans_inv, v);
+    return pigment->pigment_fn(pigment, v_trans);
+  } else {
+    return pigment->quick_color;
+  }
+}
diff --git a/libdimension/model/pigments/canvas_pigment.c b/libdimension/model/pigments/canvas_pigment.c
new file mode 100644
index 0000000..bb83b0a
--- /dev/null
+++ b/libdimension/model/pigments/canvas_pigment.c
@@ -0,0 +1,57 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Image maps.
+ */
+
+#include "dimension/model.h"
+
+/// Canvas pigment type.
+typedef struct dmnsn_canvas_pigment {
+  dmnsn_pigment pigment;
+  dmnsn_canvas *canvas;
+} dmnsn_canvas_pigment;
+
+/// Canvas pigment color callback.
+static dmnsn_tcolor
+dmnsn_canvas_pigment_fn(const dmnsn_pigment *pigment, dmnsn_vector v)
+{
+  const dmnsn_canvas_pigment *canvas_pigment = (const dmnsn_canvas_pigment *)pigment;
+  dmnsn_canvas *canvas = canvas_pigment->canvas;
+
+  size_t x = llround((fmod(v.x, 1.0) + 1.0)*(canvas->width  - 1));
+  size_t y = llround((fmod(v.y, 1.0) + 1.0)*(canvas->height - 1));
+  return dmnsn_canvas_get_pixel(canvas, x%canvas->width, y%canvas->height);
+}
+
+// Create a canvas color
+dmnsn_pigment *
+dmnsn_new_canvas_pigment(dmnsn_pool *pool, dmnsn_canvas *canvas)
+{
+  dmnsn_canvas_pigment *canvas_pigment = DMNSN_PALLOC(pool, dmnsn_canvas_pigment);
+  canvas_pigment->canvas = canvas;
+
+  dmnsn_pigment *pigment = &canvas_pigment->pigment;
+  dmnsn_init_pigment(pigment);
+  pigment->pigment_fn = dmnsn_canvas_pigment_fn;
+  return pigment;
+}
diff --git a/libdimension/model/pigments/pigment_map.c b/libdimension/model/pigments/pigment_map.c
new file mode 100644
index 0000000..a74a66a
--- /dev/null
+++ b/libdimension/model/pigments/pigment_map.c
@@ -0,0 +1,96 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Pigment-mapped pigment patterns.
+ */
+
+#include "dimension/model.h"
+
+/// Initialize a pigment in a pigment map.
+static void
+dmnsn_initialize_mapped_pigment(void *ptr)
+{
+  dmnsn_pigment **pigment = ptr;
+  dmnsn_pigment_initialize(*pigment);
+}
+
+dmnsn_map *
+dmnsn_new_pigment_map(dmnsn_pool *pool)
+{
+  return dmnsn_new_map(pool, sizeof(dmnsn_pigment *));
+}
+
+/// Pigment map type.
+typedef struct dmnsn_pigment_map {
+  dmnsn_pigment pigment;
+  dmnsn_pattern *pattern;
+  dmnsn_map *map;
+  dmnsn_pigment_map_flags flags;
+} dmnsn_pigment_map;
+
+/// pigment_map pigment callback.
+static dmnsn_tcolor
+dmnsn_pigment_map_pigment_fn(const dmnsn_pigment *pigment, dmnsn_vector v)
+{
+  const dmnsn_pigment_map *pigment_map = (const dmnsn_pigment_map *)pigment;
+  double n;
+  dmnsn_pigment *pigment1, *pigment2;
+  dmnsn_map_evaluate(pigment_map->map,
+                     dmnsn_pattern_value(pigment_map->pattern, v),
+                     &n, &pigment1, &pigment2);
+  dmnsn_tcolor color1 = dmnsn_pigment_evaluate(pigment1, v);
+  dmnsn_tcolor color2 = dmnsn_pigment_evaluate(pigment2, v);
+
+  if (pigment_map->flags == DMNSN_PIGMENT_MAP_SRGB) {
+    color1.c = dmnsn_color_to_sRGB(color1.c);
+    color2.c = dmnsn_color_to_sRGB(color2.c);
+  }
+  dmnsn_tcolor ret = dmnsn_tcolor_gradient(color1, color2, n);
+  if (pigment_map->flags == DMNSN_PIGMENT_MAP_SRGB) {
+    ret.c = dmnsn_color_from_sRGB(ret.c);
+  }
+
+  return ret;
+}
+
+/// pigment_map initialization callback.
+static void
+dmnsn_pigment_map_initialize_fn(dmnsn_pigment *pigment)
+{
+  dmnsn_pigment_map *pigment_map = (dmnsn_pigment_map *)pigment;
+  dmnsn_map_apply(pigment_map->map, dmnsn_initialize_mapped_pigment);
+}
+
+dmnsn_pigment *
+dmnsn_new_pigment_map_pigment(dmnsn_pool *pool, dmnsn_pattern *pattern, dmnsn_map *map, dmnsn_pigment_map_flags flags)
+{
+  dmnsn_pigment_map *pigment_map = DMNSN_PALLOC(pool, dmnsn_pigment_map);
+  pigment_map->pattern = pattern;
+  pigment_map->map = map;
+  pigment_map->flags = flags;
+
+  dmnsn_pigment *pigment = &pigment_map->pigment;
+  dmnsn_init_pigment(pigment);
+  pigment->pigment_fn = dmnsn_pigment_map_pigment_fn;
+  pigment->initialize_fn = dmnsn_pigment_map_initialize_fn;
+  return pigment;
+}
diff --git a/libdimension/model/pigments/solid_pigment.c b/libdimension/model/pigments/solid_pigment.c
new file mode 100644
index 0000000..bdbd724
--- /dev/null
+++ b/libdimension/model/pigments/solid_pigment.c
@@ -0,0 +1,35 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Solid color pigments.
+ */
+
+#include "dimension/model.h"
+
+// Create a solid color
+dmnsn_pigment *
+dmnsn_new_solid_pigment(dmnsn_pool *pool, dmnsn_tcolor color)
+{
+  dmnsn_pigment *pigment = dmnsn_new_pigment(pool);
+  pigment->quick_color = color;
+  return pigment;
+}
diff --git a/libdimension/model/scene.c b/libdimension/model/scene.c
new file mode 100644
index 0000000..875c376
--- /dev/null
+++ b/libdimension/model/scene.c
@@ -0,0 +1,79 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Scenes.
+ */
+
+#include "internal.h"
+#include "internal/platform.h"
+#include "dimension/model.h"
+#include <stdlib.h>
+
+// Allocate an empty scene
+dmnsn_scene *
+dmnsn_new_scene(dmnsn_pool *pool)
+{
+  dmnsn_scene *scene = DMNSN_PALLOC(pool, dmnsn_scene);
+
+  scene->background       = NULL;
+  scene->default_texture  = dmnsn_new_texture(pool);
+  scene->default_interior = dmnsn_new_interior(pool);
+  scene->canvas           = NULL;
+  scene->region_x         = 0;
+  scene->region_y         = 0;
+  scene->outer_width      = 0;
+  scene->outer_height     = 0;
+  scene->objects          = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
+  scene->lights           = DMNSN_PALLOC_ARRAY(pool, dmnsn_light *);
+  scene->camera           = NULL;
+  scene->quality          = DMNSN_RENDER_FULL;
+  scene->reclimit         = 5;
+  scene->adc_bailout      = 1.0/255.0;
+  scene->nthreads         = dmnsn_ncpus();
+  scene->initialized      = false;
+
+  return scene;
+}
+
+void
+dmnsn_scene_initialize(dmnsn_scene *scene)
+{
+  dmnsn_assert(!scene->initialized, "Scene double-initialized.");
+  scene->initialized = true;
+
+  if (scene->outer_width == 0) {
+    scene->outer_width = scene->canvas->width;
+  }
+  if (scene->outer_height == 0) {
+    scene->outer_height = scene->canvas->height;
+  }
+
+  dmnsn_pigment_initialize(scene->background);
+
+  dmnsn_texture_initialize(scene->default_texture);
+
+  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, scene->objects) {
+    dmnsn_texture_cascade(scene->default_texture, &(*object)->texture);
+    dmnsn_interior_cascade(scene->default_interior, &(*object)->interior);
+    dmnsn_object_precompute(*object);
+  }
+}
diff --git a/libdimension/model/texture.c b/libdimension/model/texture.c
new file mode 100644
index 0000000..b7eb7ef
--- /dev/null
+++ b/libdimension/model/texture.c
@@ -0,0 +1,68 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Textures.
+ */
+
+#include "dimension/model.h"
+
+dmnsn_texture *
+dmnsn_new_texture(dmnsn_pool *pool)
+{
+  dmnsn_texture *texture = DMNSN_PALLOC(pool, dmnsn_texture);
+  texture->pigment = NULL;
+  texture->finish = dmnsn_new_finish();
+  texture->trans = dmnsn_identity_matrix();
+  texture->initialized = false;
+  return texture;
+}
+
+void
+dmnsn_texture_initialize(dmnsn_texture *texture)
+{
+  dmnsn_assert(!texture->initialized, "Texture double-initialized.");
+  texture->initialized = true;
+
+  texture->trans_inv = dmnsn_matrix_inverse(texture->trans);
+
+  if (!texture->pigment->initialized) {
+    texture->pigment->trans = dmnsn_matrix_mul(texture->trans,
+                                               texture->pigment->trans);
+    dmnsn_pigment_initialize(texture->pigment);
+  }
+}
+
+void
+dmnsn_texture_cascade(dmnsn_texture *default_texture, dmnsn_texture **texturep)
+{
+  if (!*texturep) {
+    *texturep = default_texture;
+  }
+
+  dmnsn_texture *texture = *texturep;
+
+  if (!texture->pigment) {
+    texture->pigment = default_texture->pigment;
+  }
+
+  dmnsn_finish_cascade(&default_texture->finish, &texture->finish);
+}
diff --git a/libdimension/object.c b/libdimension/object.c
deleted file mode 100644
index a67b8cb..0000000
--- a/libdimension/object.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Objects.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-// Allocate a dummy object
-dmnsn_object *
-dmnsn_new_object(dmnsn_pool *pool)
-{
-  dmnsn_object *object = DMNSN_PALLOC(pool, dmnsn_object);
-  dmnsn_init_object(object);
-  return object;
-}
-
-// Initialize a dmnsn_object field
-void
-dmnsn_init_object(dmnsn_object *object)
-{
-  object->vtable = NULL;
-  object->texture = NULL;
-  object->interior = NULL;
-  object->trans = dmnsn_identity_matrix();
-  object->intrinsic_trans = dmnsn_identity_matrix();
-  object->children = NULL;
-  object->split_children = false;
-  object->precomputed = false;
-}
-
-/// Recursively precompute objects.
-static void
-dmnsn_object_precompute_recursive(dmnsn_object *object, dmnsn_matrix pigment_trans)
-{
-  dmnsn_assert(!object->precomputed, "Object double-precomputed.");
-  object->precomputed = true;
-
-  const dmnsn_object_vtable *vtable = object->vtable;
-  dmnsn_assert(vtable->intersection_fn, "Missing intersection function.");
-  dmnsn_assert(vtable->inside_fn, "Missing inside function.");
-  dmnsn_assert(vtable->bounding_fn || vtable->precompute_fn, "Missing bounding and precompute function.");
-
-  // Initialize the texture
-  if (!object->texture->initialized) {
-    dmnsn_texture_initialize(object->texture);
-  }
-
-  dmnsn_matrix total_trans = dmnsn_matrix_mul(object->trans, object->intrinsic_trans);
-
-  // Precompute the object's children
-  if (object->children) {
-    DMNSN_ARRAY_FOREACH (dmnsn_object **, child, object->children) {
-      dmnsn_matrix saved_trans = (*child)->trans;
-      (*child)->trans = dmnsn_matrix_mul(total_trans, saved_trans);
-
-      dmnsn_matrix child_pigment_trans;
-      if ((*child)->texture == NULL || (*child)->texture->pigment == NULL) {
-        // Don't transform cascaded pigments with the child object
-        child_pigment_trans = pigment_trans;
-      } else {
-        child_pigment_trans = dmnsn_matrix_inverse((*child)->trans);
-      }
-
-      dmnsn_texture_cascade(object->texture, &(*child)->texture);
-      dmnsn_interior_cascade(object->interior, &(*child)->interior);
-      dmnsn_object_precompute_recursive(*child, child_pigment_trans);
-      (*child)->trans = saved_trans;
-    }
-  }
-
-  // Precalculate object values
-  object->pigment_trans = pigment_trans;
-  object->trans_inv = dmnsn_matrix_inverse(total_trans);
-  if (vtable->bounding_fn) {
-    object->bounding_box = vtable->bounding_fn(object, total_trans);
-  }
-  if (vtable->precompute_fn) {
-    vtable->precompute_fn(object);
-  }
-}
-
-// Precompute object properties
-void
-dmnsn_object_precompute(dmnsn_object *object)
-{
-  dmnsn_matrix pigment_trans = dmnsn_matrix_inverse(object->trans);
-  dmnsn_object_precompute_recursive(object, pigment_trans);
-}
diff --git a/libdimension/pattern.c b/libdimension/pattern.c
deleted file mode 100644
index 22f5c13..0000000
--- a/libdimension/pattern.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Patterns.
- */
-
-#include "dimension-internal.h"
-
-dmnsn_pattern *
-dmnsn_new_pattern(dmnsn_pool *pool)
-{
-  dmnsn_pattern *pattern = DMNSN_PALLOC(pool, dmnsn_pattern);
-  dmnsn_init_pattern(pattern);
-  return pattern;
-}
-
-void
-dmnsn_init_pattern(dmnsn_pattern *pattern)
-{
-}
-
-double
-dmnsn_pattern_value(const dmnsn_pattern *pattern, dmnsn_vector v)
-{
-  return pattern->pattern_fn(pattern, v);
-}
diff --git a/libdimension/pattern/checker.c b/libdimension/pattern/checker.c
new file mode 100644
index 0000000..cce9623
--- /dev/null
+++ b/libdimension/pattern/checker.c
@@ -0,0 +1,63 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Checker pattern.
+ */
+
+#include "dimension/pattern.h"
+
+/// Checker pattern callback.
+static double
+dmnsn_checker_pattern_fn(const dmnsn_pattern *checker, dmnsn_vector v)
+{
+  double xmod = fmod(v.x, 2.0);
+  double ymod = fmod(v.y, 2.0);
+  double zmod = fmod(v.z, 2.0);
+
+  if (xmod < -dmnsn_epsilon)
+    xmod += 2.0;
+  if (ymod < -dmnsn_epsilon)
+    ymod += 2.0;
+  if (zmod < -dmnsn_epsilon)
+    zmod += 2.0;
+
+  // Return 0 when an even number of coordinates are in [0, 1), 1 otherwise
+  unsigned int n = 0;
+  if (xmod >= 1.0)
+    ++n;
+  if (ymod >= 1.0)
+    ++n;
+  if (zmod >= 1.0)
+    ++n;
+  return (n%2 == 0) ? 0.0 : 1.0;
+}
+
+/// The singleton instance.
+static dmnsn_pattern dmnsn_checker_instance = {
+  .pattern_fn = dmnsn_checker_pattern_fn,
+};
+
+dmnsn_pattern *
+dmnsn_new_checker_pattern(dmnsn_pool *pool)
+{
+  return &dmnsn_checker_instance;
+}
diff --git a/libdimension/pattern/gradient.c b/libdimension/pattern/gradient.c
new file mode 100644
index 0000000..f7a2b97
--- /dev/null
+++ b/libdimension/pattern/gradient.c
@@ -0,0 +1,56 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Gradient pattern.
+ */
+
+#include "dimension/pattern.h"
+
+/// Gradient pattern type.
+typedef struct dmnns_gradient {
+  dmnsn_pattern pattern;
+  dmnsn_vector orientation;
+} dmnsn_gradient;
+
+/// Gradient pattern callback.
+static double
+dmnsn_gradient_pattern_fn(const dmnsn_pattern *pattern, dmnsn_vector v)
+{
+  const dmnsn_gradient *gradient = (const dmnsn_gradient *)pattern;
+  double n = fmod(dmnsn_vector_dot(gradient->orientation, v), 1.0);
+  if (n < -dmnsn_epsilon) {
+    n += 1.0;
+  }
+  return n;
+}
+
+dmnsn_pattern *
+dmnsn_new_gradient_pattern(dmnsn_pool *pool, dmnsn_vector orientation)
+{
+  dmnsn_gradient *gradient = DMNSN_PALLOC(pool, dmnsn_gradient);
+  gradient->orientation = dmnsn_vector_normalized(orientation);
+
+  dmnsn_pattern *pattern = &gradient->pattern;
+  dmnsn_init_pattern(pattern);
+  pattern->pattern_fn = dmnsn_gradient_pattern_fn;
+  return pattern;
+}
diff --git a/libdimension/pattern/leopard.c b/libdimension/pattern/leopard.c
new file mode 100644
index 0000000..1a7bce0
--- /dev/null
+++ b/libdimension/pattern/leopard.c
@@ -0,0 +1,46 @@
+/*************************************************************************
+ * Copyright (C) 2011-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Leopard pattern.
+ */
+
+#include "dimension/pattern.h"
+#include <math.h>
+
+/// Leopard pattern callback.
+static double
+dmnsn_leopard_pattern_fn(const dmnsn_pattern *leopard, dmnsn_vector v)
+{
+  double val = (sin(v.x) + sin(v.y) + sin(v.z))/3.0;
+  return val*val;
+}
+
+/// The singleton instance.
+static dmnsn_pattern dmnsn_leopard_instance = {
+  .pattern_fn = dmnsn_leopard_pattern_fn,
+};
+
+dmnsn_pattern *
+dmnsn_new_leopard_pattern(dmnsn_pool *pool)
+{
+  return &dmnsn_leopard_instance;
+}
diff --git a/libdimension/pattern/map.c b/libdimension/pattern/map.c
new file mode 100644
index 0000000..ac07960
--- /dev/null
+++ b/libdimension/pattern/map.c
@@ -0,0 +1,126 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Generic maps.
+ */
+
+#include "internal.h"
+#include "dimension/pattern.h"
+
+/// dmnsn_map definition.
+struct dmnsn_map {
+  size_t obj_size; ///< @internal The size of the mapped objects.
+  dmnsn_array *array; ///< @internal The map entries.
+};
+
+/// An [index, object] pair.
+typedef struct dmnsn_map_entry {
+  double n;
+  char object[];
+} dmnsn_map_entry;
+
+dmnsn_map *
+dmnsn_new_map(dmnsn_pool *pool, size_t size)
+{
+  dmnsn_map *map = DMNSN_PALLOC(pool, dmnsn_map);
+  map->obj_size = size;
+  map->array = dmnsn_palloc_array(pool, sizeof(dmnsn_map_entry) + size);
+  return map;
+}
+
+void
+dmnsn_map_add_entry(dmnsn_map *map, double n, const void *obj)
+{
+  dmnsn_map_entry *entry;
+  DMNSN_ALLOCA(entry, sizeof(dmnsn_map_entry) + map->obj_size);
+
+  entry->n = n;
+  memcpy(entry->object, obj, map->obj_size);
+
+  // Sorted insertion
+  size_t i;
+  for (i = dmnsn_array_size(map->array); i-- > 0;) {
+    dmnsn_map_entry *other = dmnsn_array_at(map->array, i);
+    if (other->n <= n) {
+      break;
+    }
+  }
+
+  dmnsn_array_insert(map->array, i + 1, entry);
+}
+
+size_t
+dmnsn_map_size(const dmnsn_map *map)
+{
+  return dmnsn_array_size(map->array);
+}
+
+void
+dmnsn_map_evaluate(const dmnsn_map *map, double n,
+                   double *val, void *obj1, void *obj2)
+{
+  dmnsn_assert(dmnsn_array_size(map->array) > 0,
+               "Attempt to evaluate empty map.");
+
+  const dmnsn_map_entry *entry = dmnsn_array_first(map->array);
+
+  double n1, n2 = 0.0;
+  const void *o1, *o2 = entry->object;
+
+  if (n < n2) {
+    *val = 0.0;
+    memcpy(obj1, o2, map->obj_size);
+    memcpy(obj2, o2, map->obj_size);
+    return;
+  }
+
+  ptrdiff_t skip = sizeof(dmnsn_map_entry) + map->obj_size;
+  for (const dmnsn_map_entry *last = dmnsn_array_last(map->array);
+       entry <= last;
+       entry = (const dmnsn_map_entry *)((const char *)entry + skip)) {
+    n1 = n2;
+    o1 = o2;
+
+    n2 = entry->n;
+    o2 = entry->object;
+
+    if (n < n2) {
+      *val = (n - n1)/(n2 - n1);
+      memcpy(obj1, o1, map->obj_size);
+      memcpy(obj2, o2, map->obj_size);
+      return;
+    }
+  }
+
+  *val = 1.0;
+  memcpy(obj1, o2, map->obj_size);
+  memcpy(obj2, o2, map->obj_size);
+}
+
+void
+dmnsn_map_apply(dmnsn_map *map, dmnsn_callback_fn *callback)
+{
+  for (size_t i = 0; i < dmnsn_array_size(map->array); ++i) {
+    dmnsn_map_entry *entry = dmnsn_array_at(map->array, i);
+    callback(entry->object);
+  }
+}
diff --git a/libdimension/pattern/pattern.c b/libdimension/pattern/pattern.c
new file mode 100644
index 0000000..60e1ae7
--- /dev/null
+++ b/libdimension/pattern/pattern.c
@@ -0,0 +1,45 @@
+/*************************************************************************
+ * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Patterns.
+ */
+
+#include "dimension/pattern.h"
+
+dmnsn_pattern *
+dmnsn_new_pattern(dmnsn_pool *pool)
+{
+  dmnsn_pattern *pattern = DMNSN_PALLOC(pool, dmnsn_pattern);
+  dmnsn_init_pattern(pattern);
+  return pattern;
+}
+
+void
+dmnsn_init_pattern(dmnsn_pattern *pattern)
+{
+}
+
+double
+dmnsn_pattern_value(const dmnsn_pattern *pattern, dmnsn_vector v)
+{
+  return pattern->pattern_fn(pattern, v);
+}
diff --git a/libdimension/perspective.c b/libdimension/perspective.c
deleted file mode 100644
index 518e52e..0000000
--- a/libdimension/perspective.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Perspective cameras.
- */
-
-#include "dimension.h"
-#include <stdlib.h>
-
-/// Perspective camera ray callback.
-static dmnsn_line
-dmnsn_perspective_camera_ray_fn(const dmnsn_camera *camera, double x, double y)
-{
-  dmnsn_line l = dmnsn_new_line(
-    dmnsn_zero,
-    dmnsn_new_vector(x - 0.5, y - 0.5, 1.0)
-  );
-  return l;
-}
-
-// Create a new perspective camera.
-dmnsn_camera *
-dmnsn_new_perspective_camera(dmnsn_pool *pool)
-{
-  dmnsn_camera *camera = dmnsn_new_camera(pool);
-  camera->ray_fn = dmnsn_perspective_camera_ray_fn;
-  return camera;
-}
diff --git a/libdimension/phong.c b/libdimension/phong.c
deleted file mode 100644
index 10e5b1a..0000000
--- a/libdimension/phong.c
+++ /dev/null
@@ -1,69 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Phong highlights.
- */
-
-#include "dimension.h"
-#include <stdlib.h>
-
-/// Phone specular type.
-typedef struct dmnsn_phong {
-  dmnsn_specular specular;
-  double coeff;
-  double exp;
-} dmnsn_phong;
-
-/// Phong specular highlight callback.
-static dmnsn_color
-dmnsn_phong_specular_fn(const dmnsn_specular *specular,
-                        dmnsn_color light, dmnsn_color color,
-                        dmnsn_vector ray, dmnsn_vector normal,
-                        dmnsn_vector viewer)
-{
-  const dmnsn_phong *phong = (const dmnsn_phong *)specular;
-
-  dmnsn_vector proj = dmnsn_vector_mul(2*dmnsn_vector_dot(ray, normal), normal);
-  dmnsn_vector reflected = dmnsn_vector_sub(proj, ray);
-
-  double specular_factor = dmnsn_vector_dot(reflected, viewer);
-  if (specular_factor < 0.0) {
-    return dmnsn_black;
-  }
-
-  specular_factor = pow(specular_factor, phong->exp);
-  return dmnsn_color_mul(phong->coeff*specular_factor, light);
-}
-
-// A phong finish
-dmnsn_specular *
-dmnsn_new_phong(dmnsn_pool *pool, double coeff, double exp)
-{
-  dmnsn_phong *phong = DMNSN_PALLOC(pool, dmnsn_phong);
-  phong->coeff = coeff;
-  phong->exp = exp;
-
-  dmnsn_specular *specular = &phong->specular;
-  dmnsn_init_specular(specular);
-  specular->specular_fn = dmnsn_phong_specular_fn;
-  return specular;
-}
diff --git a/libdimension/pigment.c b/libdimension/pigment.c
deleted file mode 100644
index 5fae4f6..0000000
--- a/libdimension/pigment.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pigments.
- */
-
-#include "dimension-internal.h"
-
-// Allocate a dummy pigment
-dmnsn_pigment *
-dmnsn_new_pigment(dmnsn_pool *pool)
-{
-  dmnsn_pigment *pigment = DMNSN_PALLOC(pool, dmnsn_pigment);
-  dmnsn_init_pigment(pigment);
-  return pigment;
-}
-
-// Initialize a pigment
-void
-dmnsn_init_pigment(dmnsn_pigment *pigment)
-{
-  pigment->pigment_fn = NULL;
-  pigment->initialize_fn = NULL;
-  pigment->trans = dmnsn_identity_matrix();
-  pigment->quick_color = DMNSN_TCOLOR(dmnsn_black);
-  pigment->initialized = false;
-}
-
-// Precompute pigment properties
-void
-dmnsn_pigment_initialize(dmnsn_pigment *pigment)
-{
-  dmnsn_assert(!pigment->initialized, "Pigment double-initialized.");
-  pigment->initialized = true;
-
-  if (pigment->initialize_fn) {
-    pigment->initialize_fn(pigment);
-  }
-
-  pigment->trans_inv = dmnsn_matrix_inverse(pigment->trans);
-}
-
-// Evaluate a pigment
-dmnsn_tcolor
-dmnsn_pigment_evaluate(const dmnsn_pigment *pigment, dmnsn_vector v)
-{
-  if (pigment->pigment_fn) {
-    dmnsn_vector v_trans = dmnsn_transform_point(pigment->trans_inv, v);
-    return pigment->pigment_fn(pigment, v_trans);
-  } else {
-    return pigment->quick_color;
-  }
-}
diff --git a/libdimension/pigment_map.c b/libdimension/pigment_map.c
deleted file mode 100644
index 71de4e0..0000000
--- a/libdimension/pigment_map.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Pigment-mapped pigment patterns.
- */
-
-#include "dimension.h"
-
-/// Initialize a pigment in a pigment map.
-static void
-dmnsn_initialize_mapped_pigment(void *ptr)
-{
-  dmnsn_pigment **pigment = ptr;
-  dmnsn_pigment_initialize(*pigment);
-}
-
-dmnsn_map *
-dmnsn_new_pigment_map(dmnsn_pool *pool)
-{
-  return dmnsn_new_map(pool, sizeof(dmnsn_pigment *));
-}
-
-/// Pigment map type.
-typedef struct dmnsn_pigment_map {
-  dmnsn_pigment pigment;
-  dmnsn_pattern *pattern;
-  dmnsn_map *map;
-  dmnsn_pigment_map_flags flags;
-} dmnsn_pigment_map;
-
-/// pigment_map pigment callback.
-static dmnsn_tcolor
-dmnsn_pigment_map_pigment_fn(const dmnsn_pigment *pigment, dmnsn_vector v)
-{
-  const dmnsn_pigment_map *pigment_map = (const dmnsn_pigment_map *)pigment;
-  double n;
-  dmnsn_pigment *pigment1, *pigment2;
-  dmnsn_map_evaluate(pigment_map->map,
-                     dmnsn_pattern_value(pigment_map->pattern, v),
-                     &n, &pigment1, &pigment2);
-  dmnsn_tcolor color1 = dmnsn_pigment_evaluate(pigment1, v);
-  dmnsn_tcolor color2 = dmnsn_pigment_evaluate(pigment2, v);
-
-  if (pigment_map->flags == DMNSN_PIGMENT_MAP_SRGB) {
-    color1.c = dmnsn_color_to_sRGB(color1.c);
-    color2.c = dmnsn_color_to_sRGB(color2.c);
-  }
-  dmnsn_tcolor ret = dmnsn_tcolor_gradient(color1, color2, n);
-  if (pigment_map->flags == DMNSN_PIGMENT_MAP_SRGB) {
-    ret.c = dmnsn_color_from_sRGB(ret.c);
-  }
-
-  return ret;
-}
-
-/// pigment_map initialization callback.
-static void
-dmnsn_pigment_map_initialize_fn(dmnsn_pigment *pigment)
-{
-  dmnsn_pigment_map *pigment_map = (dmnsn_pigment_map *)pigment;
-  dmnsn_map_apply(pigment_map->map, dmnsn_initialize_mapped_pigment);
-}
-
-dmnsn_pigment *
-dmnsn_new_pigment_map_pigment(dmnsn_pool *pool, dmnsn_pattern *pattern, dmnsn_map *map, dmnsn_pigment_map_flags flags)
-{
-  dmnsn_pigment_map *pigment_map = DMNSN_PALLOC(pool, dmnsn_pigment_map);
-  pigment_map->pattern = pattern;
-  pigment_map->map = map;
-  pigment_map->flags = flags;
-
-  dmnsn_pigment *pigment = &pigment_map->pigment;
-  dmnsn_init_pigment(pigment);
-  pigment->pigment_fn = dmnsn_pigment_map_pigment_fn;
-  pigment->initialize_fn = dmnsn_pigment_map_initialize_fn;
-  return pigment;
-}
diff --git a/libdimension/plane.c b/libdimension/plane.c
deleted file mode 100644
index 5428485..0000000
--- a/libdimension/plane.c
+++ /dev/null
@@ -1,88 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Planes.
- */
-
-#include "dimension.h"
-#include <math.h>
-#include <stdlib.h>
-
-/// Plane type.
-typedef struct {
-  dmnsn_object object;
-  dmnsn_vector normal;
-} dmnsn_plane;
-
-/// Returns the closest intersection of `line' with `plane'.
-static bool
-dmnsn_plane_intersection_fn(const dmnsn_object *object, dmnsn_line line,
-                            dmnsn_intersection *intersection)
-{
-  const dmnsn_plane *plane = (const dmnsn_plane *)object;
-  dmnsn_vector normal = plane->normal;
-
-  double den = dmnsn_vector_dot(line.n, normal);
-  if (den != 0.0) {
-    double t = -dmnsn_vector_dot(line.x0, normal)/den;
-    if (t >= 0.0) {
-      intersection->t      = t;
-      intersection->normal = normal;
-      return true;
-    }
-  }
-  return false;
-}
-
-/// Return whether a point is inside a plane.
-static bool
-dmnsn_plane_inside_fn(const dmnsn_object *object, dmnsn_vector point)
-{
-  const dmnsn_plane *plane = (const dmnsn_plane *)object;
-  return dmnsn_vector_dot(point, plane->normal) < 0.0;
-}
-
-/// Plane bounding callback.
-static dmnsn_bounding_box
-dmnsn_plane_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  return dmnsn_infinite_bounding_box();
-}
-
-/// Plane vtable.
-static const dmnsn_object_vtable dmnsn_plane_vtable = {
-  .intersection_fn = dmnsn_plane_intersection_fn,
-  .inside_fn = dmnsn_plane_inside_fn,
-  .bounding_fn = dmnsn_plane_bounding_fn,
-};
-
-dmnsn_object *
-dmnsn_new_plane(dmnsn_pool *pool, dmnsn_vector normal)
-{
-  dmnsn_plane *plane = DMNSN_PALLOC(pool, dmnsn_plane);
-  plane->normal = normal;
-
-  dmnsn_object *object = &plane->object;
-  dmnsn_init_object(object);
-  object->vtable = &dmnsn_plane_vtable;
-  return object;
-}
diff --git a/libdimension/platform.c b/libdimension/platform.c
deleted file mode 100644
index b5d23cc..0000000
--- a/libdimension/platform.c
+++ /dev/null
@@ -1,185 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Platform abstractions.
- */
-
-#define _GNU_SOURCE
-#include "dimension-internal.h"
-
-#if HAVE_UNISTD_H
-  #include <unistd.h>
-#endif
-#ifdef _WIN32
-  #include <windows.h>
-#endif
-#if DMNSN_BACKTRACE
-  #include <execinfo.h>    // For backtrace() etc.
-#endif
-#if DMNSN_GETTID
-  #include <sys/syscall.h> // For gettid() where supported
-#endif
-#if DMNSN_SCHED_GETAFFINITY
-  #include <sched.h>       // For sched_getaffinity()
-#endif
-#if DMNSN_TIMES
-  #include <sys/times.h>
-#endif
-#if DMNSN_GETRUSAGE
-  #include <sys/time.h>
-  #include <sys/resource.h>
-#endif
-
-void
-dmnsn_backtrace(FILE *file)
-{
-#if DMNSN_BACKTRACE
-#define DMNSN_BACKTRACE_SIZE 128
-  void *buffer[DMNSN_BACKTRACE_SIZE];
-
-  int nptrs = backtrace(buffer, DMNSN_BACKTRACE_SIZE);
-  int fd = fileno(file);
-  if (fd != -1) {
-    backtrace_symbols_fd(buffer, nptrs, fd);
-  }
-#endif
-}
-
-bool
-dmnsn_is_main_thread(void)
-{
-#if DMNSN_GETTID
-  return getpid() == syscall(SYS_gettid);
-#else
-  return true;
-#endif
-}
-
-bool
-dmnsn_is_little_endian(void)
-{
-  // Undefined behaviour, but quite portable
-  union {
-    unsigned int i;
-    unsigned char c;
-  } u = { .i = 1 };
-  return u.c == 1;
-}
-
-size_t
-dmnsn_ncpus(void)
-{
-#if DMNSN_SCHED_GETAFFINITY
-  cpu_set_t cpuset;
-  if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) {
-    return CPU_COUNT(&cpuset);
-  } else {
-    dmnsn_warning("sched_getaffinity() failed.");
-    return 1;
-  }
-#elif DMNSN_SC_NPROCESSORS_ONLN
-  long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
-  if (nprocs > 0) {
-    return nprocs;
-  } else {
-    dmnsn_warning("sysconf(_SC_NPROCESSORS_ONLN) failed.");
-    return 1;
-  }
-#elif defined(_WIN32)
-  SYSTEM_INFO sysinfo;
-  GetSystemInfo(&sysinfo);
-  return sysinfo.dwNumberOfProcessors;
-#else
-  return 1;
-#endif
-}
-
-#if DMNSN_GETRUSAGE
-/// Convert a struct timeval to a double.
-static inline double
-dmnsn_timeval2double(struct timeval tv)
-{
-  return tv.tv_sec + tv.tv_usec/1.0e6;
-}
-#endif
-
-void
-dmnsn_get_times(dmnsn_timer *timer)
-{
-#if DMNSN_GETRUSAGE
-  struct timeval real;
-  gettimeofday(&real, NULL);
-
-  struct rusage usage;
-  if (getrusage(RUSAGE_SELF, &usage) == 0) {
-    timer->real   = dmnsn_timeval2double(real);
-    timer->user   = dmnsn_timeval2double(usage.ru_utime);
-    timer->system = dmnsn_timeval2double(usage.ru_stime);
-  } else {
-    dmnsn_warning("getrusage() failed.");
-    timer->real = timer->user = timer->system = 0.0;
-  }
-#elif DMNSN_TIMES
-  static long clk_tck = 0;
-
-  // Figure out the clock ticks per second
-  if (!clk_tck) {
-    clk_tck = sysconf(_SC_CLK_TCK);
-    if (clk_tck == -1) {
-      dmnsn_warning("sysconf(_SC_CLK_TCK) failed.");
-      clk_tck = 1000000L;
-    }
-  }
-
-  struct tms buf;
-  clock_t real = times(&buf);
-  if (real == (clock_t)-1) {
-    dmnsn_warning("times() failed.");
-    timer->real = timer->user = timer->system = 0.0;
-  } else {
-    timer->real   = (double)real/clk_tck;
-    timer->user   = (double)buf.tms_utime/clk_tck;
-    timer->system = (double)buf.tms_stime/clk_tck;
-  }
-#elif defined(_WIN32)
-  FILETIME real;
-  GetSystemTimeAsFileTime(&real);
-
-  FILETIME user, system, creation, exit;
-  HANDLE current_process = GetCurrentProcess();
-  if (GetProcessTimes(current_process,
-                      &creation, &exit, &system, &user) != 0)
-  {
-    timer->real
-      = (((uint64_t)real.dwHighDateTime << 32) + real.dwLowDateTime)/1.0e7;
-    timer->user
-      = (((uint64_t)user.dwHighDateTime << 32) + user.dwLowDateTime)/1.0e7;
-    timer->system
-      = (((uint64_t)system.dwHighDateTime << 32) + system.dwLowDateTime)/1.0e7;
-  } else {
-    dmnsn_warning("GetProcessTimes() failed.");
-    timer->real = timer->user = timer->system = 0.0;
-  }
-#else
-  timer->real = timer->user = timer->system = 0.0;
-#endif
-}
diff --git a/libdimension/platform.h b/libdimension/platform.h
deleted file mode 100644
index c03ae52..0000000
--- a/libdimension/platform.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Platform abstractions.
- */
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-
-/**
- * Print a stack trace, if implemented for the current platform.
- * @param[in,out] file  The file to which to write the stack trace.
- */
-DMNSN_INTERNAL void dmnsn_backtrace(FILE *file);
-
-/**
- * Is the calling thread the main thread?
- * @return Whether this is the main execution thread, or \c true if we can't
- *         tell.
- */
-DMNSN_INTERNAL bool dmnsn_is_main_thread(void);
-
-/**
- * Are we running on a little-endian computer?
- * @return Whether the current architecture is little-endian.
- */
-DMNSN_INTERNAL bool dmnsn_is_little_endian(void);
-
-/**
- * How many CPUs are available?
- * @return The number of CPUs available to dimension.
- */
-DMNSN_INTERNAL size_t dmnsn_ncpus(void);
-
-/**
- * Calculate process times.
- * @param[out] timer  The timer in which to store the current total process
- *                    times.
- */
-DMNSN_INTERNAL void dmnsn_get_times(dmnsn_timer *timer);
diff --git a/libdimension/platform/platform.c b/libdimension/platform/platform.c
new file mode 100644
index 0000000..84d9bf0
--- /dev/null
+++ b/libdimension/platform/platform.c
@@ -0,0 +1,187 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Platform abstractions.
+ */
+
+#define _GNU_SOURCE
+#include "internal.h"
+#include "internal/platform.h"
+#include "dimension/platform.h"
+
+#if HAVE_UNISTD_H
+  #include <unistd.h>
+#endif
+#ifdef _WIN32
+  #include <windows.h>
+#endif
+#if DMNSN_BACKTRACE
+  #include <execinfo.h>    // For backtrace() etc.
+#endif
+#if DMNSN_GETTID
+  #include <sys/syscall.h> // For gettid() where supported
+#endif
+#if DMNSN_SCHED_GETAFFINITY
+  #include <sched.h>       // For sched_getaffinity()
+#endif
+#if DMNSN_TIMES
+  #include <sys/times.h>
+#endif
+#if DMNSN_GETRUSAGE
+  #include <sys/time.h>
+  #include <sys/resource.h>
+#endif
+
+void
+dmnsn_backtrace(FILE *file)
+{
+#if DMNSN_BACKTRACE
+#define DMNSN_BACKTRACE_SIZE 128
+  void *buffer[DMNSN_BACKTRACE_SIZE];
+
+  int nptrs = backtrace(buffer, DMNSN_BACKTRACE_SIZE);
+  int fd = fileno(file);
+  if (fd != -1) {
+    backtrace_symbols_fd(buffer, nptrs, fd);
+  }
+#endif
+}
+
+bool
+dmnsn_is_main_thread(void)
+{
+#if DMNSN_GETTID
+  return getpid() == syscall(SYS_gettid);
+#else
+  return true;
+#endif
+}
+
+bool
+dmnsn_is_little_endian(void)
+{
+  // Undefined behaviour, but quite portable
+  union {
+    unsigned int i;
+    unsigned char c;
+  } u = { .i = 1 };
+  return u.c == 1;
+}
+
+size_t
+dmnsn_ncpus(void)
+{
+#if DMNSN_SCHED_GETAFFINITY
+  cpu_set_t cpuset;
+  if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) {
+    return CPU_COUNT(&cpuset);
+  } else {
+    dmnsn_warning("sched_getaffinity() failed.");
+    return 1;
+  }
+#elif DMNSN_SC_NPROCESSORS_ONLN
+  long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
+  if (nprocs > 0) {
+    return nprocs;
+  } else {
+    dmnsn_warning("sysconf(_SC_NPROCESSORS_ONLN) failed.");
+    return 1;
+  }
+#elif defined(_WIN32)
+  SYSTEM_INFO sysinfo;
+  GetSystemInfo(&sysinfo);
+  return sysinfo.dwNumberOfProcessors;
+#else
+  return 1;
+#endif
+}
+
+#if DMNSN_GETRUSAGE
+/// Convert a struct timeval to a double.
+static inline double
+dmnsn_timeval2double(struct timeval tv)
+{
+  return tv.tv_sec + tv.tv_usec/1.0e6;
+}
+#endif
+
+void
+dmnsn_get_times(dmnsn_timer *timer)
+{
+#if DMNSN_GETRUSAGE
+  struct timeval real;
+  gettimeofday(&real, NULL);
+
+  struct rusage usage;
+  if (getrusage(RUSAGE_SELF, &usage) == 0) {
+    timer->real   = dmnsn_timeval2double(real);
+    timer->user   = dmnsn_timeval2double(usage.ru_utime);
+    timer->system = dmnsn_timeval2double(usage.ru_stime);
+  } else {
+    dmnsn_warning("getrusage() failed.");
+    timer->real = timer->user = timer->system = 0.0;
+  }
+#elif DMNSN_TIMES
+  static long clk_tck = 0;
+
+  // Figure out the clock ticks per second
+  if (!clk_tck) {
+    clk_tck = sysconf(_SC_CLK_TCK);
+    if (clk_tck == -1) {
+      dmnsn_warning("sysconf(_SC_CLK_TCK) failed.");
+      clk_tck = 1000000L;
+    }
+  }
+
+  struct tms buf;
+  clock_t real = times(&buf);
+  if (real == (clock_t)-1) {
+    dmnsn_warning("times() failed.");
+    timer->real = timer->user = timer->system = 0.0;
+  } else {
+    timer->real   = (double)real/clk_tck;
+    timer->user   = (double)buf.tms_utime/clk_tck;
+    timer->system = (double)buf.tms_stime/clk_tck;
+  }
+#elif defined(_WIN32)
+  FILETIME real;
+  GetSystemTimeAsFileTime(&real);
+
+  FILETIME user, system, creation, exit;
+  HANDLE current_process = GetCurrentProcess();
+  if (GetProcessTimes(current_process,
+                      &creation, &exit, &system, &user) != 0)
+  {
+    timer->real
+      = (((uint64_t)real.dwHighDateTime << 32) + real.dwLowDateTime)/1.0e7;
+    timer->user
+      = (((uint64_t)user.dwHighDateTime << 32) + user.dwLowDateTime)/1.0e7;
+    timer->system
+      = (((uint64_t)system.dwHighDateTime << 32) + system.dwLowDateTime)/1.0e7;
+  } else {
+    dmnsn_warning("GetProcessTimes() failed.");
+    timer->real = timer->user = timer->system = 0.0;
+  }
+#else
+  timer->real = timer->user = timer->system = 0.0;
+#endif
+}
diff --git a/libdimension/platform/timer.c b/libdimension/platform/timer.c
new file mode 100644
index 0000000..2d91469
--- /dev/null
+++ b/libdimension/platform/timer.c
@@ -0,0 +1,43 @@
+/*************************************************************************
+ * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * Performance counter.
+ */
+
+#include "internal/platform.h"
+#include "dimension/platform.h"
+
+void
+dmnsn_timer_start(dmnsn_timer *timer)
+{
+  dmnsn_get_times(timer);
+}
+
+void
+dmnsn_timer_stop(dmnsn_timer *timer)
+{
+  dmnsn_timer now;
+  dmnsn_get_times(&now);
+  timer->real   = now.real   - timer->real;
+  timer->user   = now.user   - timer->user;
+  timer->system = now.system - timer->system;
+}
diff --git a/libdimension/png-stubs.c b/libdimension/png-stubs.c
deleted file mode 100644
index 9c752a5..0000000
--- a/libdimension/png-stubs.c
+++ /dev/null
@@ -1,62 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Stubs for PNG functions when compiled with --disable-png.
- */
-
-#include "dimension.h"
-#include <errno.h>
-
-int
-dmnsn_png_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
-{
-  errno = ENOSYS;
-  return -1;
-}
-
-int
-dmnsn_png_write_canvas(const dmnsn_canvas *canvas, FILE *file)
-{
-  errno = ENOSYS;
-  return -1;
-}
-
-dmnsn_future *
-dmnsn_png_write_canvas_async(const dmnsn_canvas *canvas, FILE *file)
-{
-  errno = ENOSYS;
-  return NULL;
-}
-
-dmnsn_canvas *
-dmnsn_png_read_canvas(dmnsn_pool *pool, FILE *file)
-{
-  errno = ENOSYS;
-  return NULL;
-}
-
-dmnsn_future *
-dmnsn_png_read_canvas_async(dmnsn_canvas **canvas, dmnsn_pool *pool, FILE *file)
-{
-  errno = ENOSYS;
-  return NULL;
-}
diff --git a/libdimension/png.c b/libdimension/png.c
deleted file mode 100644
index 6b73738..0000000
--- a/libdimension/png.c
+++ /dev/null
@@ -1,390 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * PNG import/export.
- */
-
-#include "dimension-internal.h"
-#include <png.h>
-#include <errno.h>
-#include <setjmp.h>
-#include <stdlib.h>
-#include <stdint.h>
-
-int
-dmnsn_png_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
-{
-  dmnsn_rgba16_optimize_canvas(pool, canvas);
-  return 0;
-}
-
-/// Payload type for PNG write thread callback.
-typedef struct {
-  dmnsn_future *future;
-  const dmnsn_canvas *canvas;
-  FILE *file;
-} dmnsn_png_write_payload;
-
-/// Payload type for PNG read thread callback.
-typedef struct {
-  dmnsn_future *future;
-  dmnsn_canvas **canvas;
-  dmnsn_pool *pool;
-  FILE *file;
-} dmnsn_png_read_payload;
-
-/// PNG write thread callback.
-static int dmnsn_png_write_canvas_thread(void *ptr);
-/// PNG read thread callback.
-static int dmnsn_png_read_canvas_thread(void *ptr);
-
-int
-dmnsn_png_write_canvas(const dmnsn_canvas *canvas, FILE *file)
-{
-  dmnsn_future *future = dmnsn_png_write_canvas_async(canvas, file);
-  return dmnsn_future_join(future);
-}
-
-dmnsn_future *
-dmnsn_png_write_canvas_async(const dmnsn_canvas *canvas, FILE *file)
-{
-  dmnsn_future *future = dmnsn_new_future();
-
-  dmnsn_png_write_payload *payload = DMNSN_MALLOC(dmnsn_png_write_payload);
-  payload->future = future;
-  payload->canvas = canvas;
-  payload->file   = file;
-
-  // Create the worker thread
-  dmnsn_new_thread(future, dmnsn_png_write_canvas_thread, payload);
-
-  return future;
-}
-
-// Read a canvas from the PNG file `file'.  Return NULL on error.
-dmnsn_canvas *
-dmnsn_png_read_canvas(dmnsn_pool *pool, FILE *file)
-{
-  dmnsn_canvas *canvas;
-  dmnsn_future *future = dmnsn_png_read_canvas_async(&canvas, pool, file);
-  dmnsn_future_join(future);
-  return canvas;
-}
-
-// Read a canvas from a png file in the background
-dmnsn_future *
-dmnsn_png_read_canvas_async(dmnsn_canvas **canvas, dmnsn_pool *pool, FILE *file)
-{
-  dmnsn_future *future = dmnsn_new_future();
-  dmnsn_png_read_payload *payload = DMNSN_MALLOC(dmnsn_png_read_payload);
-
-  payload->future = future;
-  payload->canvas = canvas;
-  *payload->canvas = NULL;
-  payload->pool = pool;
-  payload->file = file;
-
-  // Create the worker thread
-  dmnsn_new_thread(future, dmnsn_png_read_canvas_thread, payload);
-
-  return future;
-}
-
-//////////////////////
-// Thread callbacks //
-//////////////////////
-
-// Write a PNG file
-static int
-dmnsn_png_write_canvas_thread(void *ptr)
-{
-  dmnsn_png_write_payload *payload = ptr;
-
-  png_uint_32 width = payload->canvas->width;
-  png_uint_32 height = payload->canvas->height;
-
-  dmnsn_future_set_total(payload->future, height);
-
-  png_structp png_ptr
-    = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-  if (!png_ptr) {
-    // Couldn't create libpng write struct
-    dmnsn_free(payload);
-    return -1;
-  }
-
-  png_infop info_ptr = png_create_info_struct(png_ptr);
-  if (!info_ptr) {
-    // Couldn't create libpng info struct
-    png_destroy_write_struct(&png_ptr, NULL);
-    dmnsn_free(payload);
-    return -1;
-  }
-
-  // libpng will longjmp here if it encounters an error from here on
-  uint16_t *row = NULL;
-  if (setjmp(png_jmpbuf(png_ptr))) {
-    // libpng error
-    dmnsn_free(row);
-    png_destroy_write_struct(&png_ptr, &info_ptr);
-    dmnsn_free(payload);
-    return -1;
-  }
-
-  // Associate file with the libpng write struct
-  png_init_io(png_ptr, payload->file);
-
-  // Set header correctly for 16-bit sRGB image
-  png_set_IHDR(png_ptr, info_ptr, width, height, 16,
-               PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
-               PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
-  png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_ABSOLUTE);
-
-  // We think of transparency in the opposite way that PNG does
-  png_set_invert_alpha(png_ptr);
-
-  // Write the info struct
-  png_write_info(png_ptr, info_ptr);
-
-  if (dmnsn_is_little_endian()) {
-    // We are little-endian; swap the byte order of the pixels
-    png_set_swap(png_ptr);
-  }
-
-  // Check if we can optimize this
-  dmnsn_rgba16_optimizer *rgba16 = (dmnsn_rgba16_optimizer *)dmnsn_canvas_find_optimizer(payload->canvas, dmnsn_rgba16_optimizer_fn);
-  if (rgba16) {
-    for (size_t y = 0; y < height; ++y) {
-      // Invert the rows.  PNG coordinates are fourth quadrant.
-      uint16_t *row_opt = rgba16->data + 4*(height - y - 1)*width;
-      png_write_row(png_ptr, (png_bytep)row_opt);
-      dmnsn_future_increment(payload->future);
-    }
-
-    // Finish the PNG file
-    png_write_end(png_ptr, info_ptr);
-    png_destroy_write_struct(&png_ptr, &info_ptr);
-    dmnsn_free(payload);
-    return 0;
-  }
-
-  // Allocate the temporary row of RGBA values
-  row = dmnsn_malloc(4*sizeof(uint16_t)*width);
-
-  // Write the pixels
-  for (size_t y = 0; y < height; ++y) {
-    for (size_t x = 0; x < width; ++x) {
-      // Invert the rows.  PNG coordinates are fourth quadrant.
-      dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(payload->canvas,
-                                                   x, height - y - 1);
-      tcolor = dmnsn_tcolor_remove_filter(tcolor);
-      tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
-      tcolor = dmnsn_tcolor_clamp(tcolor);
-
-      row[4*x]     = lround(tcolor.c.R*UINT16_MAX);
-      row[4*x + 1] = lround(tcolor.c.G*UINT16_MAX);
-      row[4*x + 2] = lround(tcolor.c.B*UINT16_MAX);
-      row[4*x + 3] = lround(tcolor.T*UINT16_MAX);
-    }
-
-    // Write the row
-    png_write_row(png_ptr, (png_bytep)row);
-    dmnsn_future_increment(payload->future);
-  }
-
-  // Finish the PNG file
-  png_write_end(png_ptr, info_ptr);
-
-  dmnsn_free(row);
-  png_destroy_write_struct(&png_ptr, &info_ptr);
-  dmnsn_free(payload);
-  return 0;
-}
-
-/// Thread-specific pointer to the appropriate dmnsn_future* for
-/// dmnsn_png_read_row_callback.
-static __thread dmnsn_future *dmnsn_tl_png_read_future;
-
-/// Callback to increment the progress after a row has been read.
-static void
-dmnsn_png_read_row_callback(png_structp png_ptr, png_uint_32 row, int pass)
-{
-  dmnsn_future_increment(dmnsn_tl_png_read_future);
-}
-
-// Read a PNG file
-static int
-dmnsn_png_read_canvas_thread(void *ptr)
-{
-  dmnsn_png_read_payload *payload = ptr;
-  dmnsn_tl_png_read_future = payload->future;
-
-  png_byte header[8];
-  if (fread(header, 1, 8, payload->file) != 8) {
-    dmnsn_free(payload);
-    return -1;
-  }
-  if (png_sig_cmp(header, 0, 8)) {
-    // payload->file is not a PNG file
-    dmnsn_free(payload);
-    errno = EINVAL;
-    return -1;
-  }
-
-  // Create the libpng read struct
-  png_structp png_ptr
-    = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-  if (!png_ptr) {
-    dmnsn_free(payload);
-    return -1;
-  }
-
-  // Create the libpng info struct
-  png_infop info_ptr = png_create_info_struct(png_ptr);
-  if (!info_ptr) {
-    png_destroy_read_struct(&png_ptr, NULL, NULL);
-    dmnsn_free(payload);
-    return -1;
-  }
-
-  // libpng will longjmp here if it encounters an error from here on
-  png_bytep image = NULL;
-  png_bytep *row_pointers = NULL;
-  if (setjmp(png_jmpbuf(png_ptr))) {
-    // libpng error
-    dmnsn_free(row_pointers);
-    dmnsn_free(image);
-    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-    dmnsn_free(payload);
-    return -1;
-  }
-
-  // Associate the read struct with the file, and tell it we've already checked
-  // 8 bytes of signature
-  png_init_io(png_ptr, payload->file);
-  png_set_sig_bytes(png_ptr, 8);
-
-  // Read the PNG header into info struct
-  png_read_info(png_ptr, info_ptr);
-
-  // Get useful information from the info struct
-  png_uint_32 width, height;
-  int bit_depth, color_type, interlace_type, compression_type, filter_method;
-  png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
-               &interlace_type, &compression_type, &filter_method);
-  int number_of_passes = png_set_interlace_handling(png_ptr);
-
-  dmnsn_future_set_total(payload->future, (number_of_passes + 1)*height);
-  png_set_read_status_fn(png_ptr, dmnsn_png_read_row_callback);
-
-  // - Convert paletted images to RGB.
-  // - Convert a tRNS chunk to an alpha channel
-  // - Convert grayscale to RGB
-  // - Invert the alpha channel
-  if (color_type == PNG_COLOR_TYPE_PALETTE) {
-    png_set_palette_to_rgb(png_ptr);
-  }
-  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
-    png_set_tRNS_to_alpha(png_ptr);
-  }
-  if (color_type == PNG_COLOR_TYPE_GRAY
-      || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
-  {
-    png_set_gray_to_rgb(png_ptr);
-  }
-  png_set_invert_alpha(png_ptr);
-
-  // Update the info struct
-  png_read_update_info(png_ptr, info_ptr);
-
-  // Get bytes/image row
-  png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
-
-  // Allocate the temporary image buffer
-  image = dmnsn_malloc(rowbytes*height);
-
-  // Allocate and set an array of pointers to rows in image
-  row_pointers = dmnsn_malloc(sizeof(png_bytep)*height);
-
-  for (size_t y = 0; y < height; ++y) {
-    row_pointers[y] = image + y*rowbytes;
-  }
-
-  // Read the image to memory all at once.  At the expense of greater memory
-  // use, this handles interlacing for us.
-  png_read_image(png_ptr, row_pointers);
-
-  // Allocate the canvas
-  *payload->canvas = dmnsn_new_canvas(payload->pool, width, height);
-
-  // Now we convert the image to our canvas format.  This depends on the image
-  // bit depth (which has been scaled up to at least 8 or 16), and the presence
-  // of an alpha channel.
-  for (size_t y = 0; y < height; ++y) {
-    for (size_t x = 0; x < width; ++x) {
-      dmnsn_tcolor tcolor;
-      tcolor.F = 0.0;
-
-      if (color_type & PNG_COLOR_MASK_ALPHA) {
-        if (bit_depth == 16) {
-          png_bytep png_pixel = image + 8*(y*width + x);
-          tcolor.c.R = (double)((png_pixel[0] << 8) + png_pixel[1])/UINT16_MAX;
-          tcolor.c.G = (double)((png_pixel[2] << 8) + png_pixel[3])/UINT16_MAX;
-          tcolor.c.B = (double)((png_pixel[4] << 8) + png_pixel[5])/UINT16_MAX;
-          tcolor.T   = (double)((png_pixel[6] << 8) + png_pixel[7])/UINT16_MAX;
-        } else {
-          png_bytep png_pixel = image + 4*(y*width + x);
-          tcolor.c.R = (double)png_pixel[0]/UINT8_MAX;
-          tcolor.c.G = (double)png_pixel[1]/UINT8_MAX;
-          tcolor.c.B = (double)png_pixel[2]/UINT8_MAX;
-          tcolor.T   = (double)png_pixel[3]/UINT8_MAX;
-        }
-      } else {
-        tcolor.T = 0.0;
-
-        if (bit_depth == 16) {
-          png_bytep png_pixel = image + 6*(y*width + x);
-          tcolor.c.R = (double)((png_pixel[0] << 8) + png_pixel[1])/UINT16_MAX;
-          tcolor.c.G = (double)((png_pixel[2] << 8) + png_pixel[3])/UINT16_MAX;
-          tcolor.c.B = (double)((png_pixel[4] << 8) + png_pixel[5])/UINT16_MAX;
-        } else {
-          png_bytep png_pixel = image + 3*(y*width + x);
-          tcolor.c.R = (double)png_pixel[0]/UINT8_MAX;
-          tcolor.c.G = (double)png_pixel[1]/UINT8_MAX;
-          tcolor.c.B = (double)png_pixel[2]/UINT8_MAX;
-        }
-      }
-
-      tcolor.c = dmnsn_color_from_sRGB(tcolor.c);
-      dmnsn_canvas_set_pixel(*payload->canvas, x, height - y - 1, tcolor);
-    }
-
-    dmnsn_future_increment(payload->future);
-  }
-
-  dmnsn_free(row_pointers);
-  dmnsn_free(image);
-  png_read_end(png_ptr, NULL);
-  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-  dmnsn_free(payload);
-  return 0;
-}
diff --git a/libdimension/point_light.c b/libdimension/point_light.c
deleted file mode 100644
index b8c2cfb..0000000
--- a/libdimension/point_light.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Point lights.
- */
-
-#include "dimension.h"
-#include <stdlib.h>
-
-/// Point light type.
-typedef struct dmnsn_point_light {
-  dmnsn_light light;
-  dmnsn_vector origin;
-  dmnsn_color color;
-} dmnsn_point_light;
-
-/// Point light direction callback.
-static dmnsn_vector
-dmnsn_point_light_direction_fn(const dmnsn_light *light, dmnsn_vector v)
-{
-  const dmnsn_point_light *point_light = (const dmnsn_point_light *)light;
-  return dmnsn_vector_sub(point_light->origin, v);
-}
-
-/// Point light illumination callback.
-static dmnsn_color
-dmnsn_point_light_illumination_fn(const dmnsn_light *light, dmnsn_vector v)
-{
-  const dmnsn_point_light *point_light = (const dmnsn_point_light *)light;
-  return point_light->color;
-}
-
-/// Point light illumination callback.
-static bool
-dmnsn_point_light_shadow_fn(const dmnsn_light *light, double t)
-{
-  return t < 1.0;
-}
-
-dmnsn_light *
-dmnsn_new_point_light(dmnsn_pool *pool, dmnsn_vector x0, dmnsn_color color)
-{
-  dmnsn_point_light *point_light = DMNSN_PALLOC(pool, dmnsn_point_light);
-  point_light->origin = x0;
-  point_light->color = color;
-
-  dmnsn_light *light = &point_light->light;
-  dmnsn_init_light(light);
-  light->direction_fn = dmnsn_point_light_direction_fn;
-  light->illumination_fn = dmnsn_point_light_illumination_fn;
-  light->shadow_fn = dmnsn_point_light_shadow_fn;
-  return light;
-}
diff --git a/libdimension/polynomial.c b/libdimension/polynomial.c
deleted file mode 100644
index 609364a..0000000
--- a/libdimension/polynomial.c
+++ /dev/null
@@ -1,441 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Real root isolation algorithm based on work by Vincent, Uspensky, Collins and
- * Akritas, Johnson, Krandick, and Rouillier and Zimmerman.
- */
-
-#include "dimension-internal.h"
-#include <math.h>
-
-/// Get the real degree of a polynomial, ignoring leading zeros.
-static inline size_t
-dmnsn_real_degree(const double poly[], size_t degree)
-{
-  for (size_t i = degree + 1; i-- > 0;) {
-    if (dmnsn_likely(fabs(poly[i]) >= dmnsn_epsilon)) {
-      return i;
-    }
-  }
-
-  return 0;
-}
-
-/// Divide each coefficient by the leading coefficient.
-static inline void
-dmnsn_polynomial_normalize(double poly[], size_t degree)
-{
-  for (size_t i = 0; i < degree; ++i) {
-    poly[i] /= poly[degree];
-  }
-  poly[degree] = 1.0;
-}
-
-/// Eliminate trivial zero roots from \p poly[].
-static inline void
-dmnsn_eliminate_zero_roots(double **poly, size_t *degree)
-{
-  size_t i;
-  for (i = 0; i <= *degree; ++i) {
-    if (dmnsn_likely(fabs((*poly)[i]) >= dmnsn_epsilon)) {
-      break;
-    }
-  }
-
-  *poly += i;
-  *degree -= i;
-}
-
-/// Calculate a finite upper bound on the roots of a normalized polynomial.
-static inline double
-dmnsn_root_bound(const double poly[], size_t degree)
-{
-  double bound = fabs(poly[0]);
-  for (size_t i = 1; i < degree; ++i) {
-    bound = dmnsn_max(bound, fabs(poly[i]));
-  }
-  bound += 1.0;
-  return bound;
-}
-
-/// Copy a polynomial.
-static inline void
-dmnsn_polynomial_copy(double dest[], const double src[], size_t degree)
-{
-  for (size_t i = 0; i <= degree; ++i) {
-    dest[i] = src[i];
-  }
-}
-
-/// Transform a polynomial by P'(x) = P(x + 1).
-static inline void
-dmnsn_polynomial_translate(double poly[], size_t degree)
-{
-  for (size_t i = 0; i <= degree; ++i) {
-    for (size_t j = degree - i; j <= degree - 1; ++j) {
-      poly[j] += poly[j + 1];
-    }
-  }
-}
-
-/// Transform a polynomial by P'(x) = P(c*x).
-static inline void
-dmnsn_polynomial_scale(double poly[], size_t degree, double c)
-{
-  double factor = c;
-  for (size_t i = 1; i <= degree; ++i) {
-    poly[i] *= factor;
-    factor *= c;
-  }
-}
-
-/// Returns the result of Descartes' rule on x^degree * poly(1/(x + 1)).
-static size_t
-dmnsn_descartes_bound(const double poly[], size_t degree)
-{
-  // Copy the polynomial so we can be destructive
-  double p[degree + 1];
-  dmnsn_polynomial_copy(p, poly, degree);
-
-  // Calculate poly(1/(1/x + 1)) which avoids reversal
-  for (size_t i = 1; i <= degree; ++i) {
-    for (size_t j = i; j >= 1; --j) {
-      p[j] += p[j - 1];
-    }
-  }
-
-  // Find the number of sign changes in p[]
-  size_t changes = 0;
-  int lastsign = dmnsn_sgn(p[0]);
-  for (size_t i = 1; changes <= 1 && i <= degree; ++i) {
-    int sign = dmnsn_sgn(p[i]);
-    if (sign != 0 && sign != lastsign) {
-      ++changes;
-      lastsign = sign;
-    }
-  }
-
-  return changes;
-}
-
-/// Depth-first search of possible isolating intervals.
-static size_t
-dmnsn_root_bounds_recursive(double poly[], size_t degree, double *c, double *k,
-                            double bounds[][2], size_t nbounds)
-{
-  size_t s = dmnsn_descartes_bound(poly, degree);
-  if (s >= 2) {
-    // Get the left child
-    dmnsn_polynomial_scale(poly, degree, 1.0/2.0);
-    *c *= 2.0;
-    *k /= 2.0;
-    double currc = *c, currk = *k;
-
-    // Test the left child
-    size_t n = dmnsn_root_bounds_recursive(poly, degree, c, k, bounds, nbounds);
-    if (nbounds == n) {
-      return n;
-    }
-    bounds += n;
-    nbounds -= n;
-
-    // Get the right child from the last tested polynomial
-    dmnsn_polynomial_translate(poly, degree);
-    dmnsn_polynomial_scale(poly, degree, currk/(*k));
-    *c = currc + 1.0;
-    *k = currk;
-
-    // Test the right child
-    n += dmnsn_root_bounds_recursive(poly, degree, c, k, bounds, nbounds);
-    return n;
-  } else if (s == 1) {
-    bounds[0][0] = (*c)*(*k);
-    bounds[0][1] = (*c + 1.0)*(*k);
-    return 1;
-  } else {
-    return 0;
-  }
-}
-
-/// Find ranges that contain a single root.
-static size_t
-dmnsn_root_bounds(const double poly[], size_t degree, double bounds[][2],
-                  size_t nbounds)
-{
-  // Copy the polynomial so we can be destructive
-  double p[degree + 1];
-  dmnsn_polynomial_copy(p, poly, degree);
-
-  // Scale the roots to within (0, 1]
-  double bound = dmnsn_root_bound(p, degree);
-  dmnsn_polynomial_scale(p, degree, bound);
-
-  // Bounding intervals are of the form (c*k, (c + 1)*k)
-  double c = 0.0, k = 1.0;
-
-  // Isolate the roots
-  size_t n = dmnsn_root_bounds_recursive(p, degree, &c, &k, bounds, nbounds);
-
-  // Scale the roots back to within (0, bound]
-  for (size_t i = 0; i < n; ++i) {
-    bounds[i][0] *= bound;
-    bounds[i][1] *= bound;
-  }
-
-  return n;
-}
-
-/// Maximum number of iterations in dmnsn_bisect_root() before bailout.
-#define DMNSN_BISECT_ITERATIONS 64
-
-/// Use the false position method to find a root in a range that contains
-/// exactly one root.
-static inline double
-dmnsn_bisect_root(const double poly[], size_t degree, double min, double max)
-{
-  double evmin = dmnsn_polynomial_evaluate(poly, degree, min);
-  double evmax = dmnsn_polynomial_evaluate(poly, degree, max);
-
-  // Handle equal bounds, and equal values at the bounds.
-  if (dmnsn_unlikely(fabs(evmax - evmin) < dmnsn_epsilon)) {
-    return (min + max)/2.0;
-  }
-
-  double evinitial = dmnsn_min(fabs(evmin), fabs(evmax));
-  double mid, evmid;
-  int lastsign = 0;
-
-  for (size_t i = 0; i < DMNSN_BISECT_ITERATIONS; ++i) {
-    mid = (min*evmax - max*evmin)/(evmax - evmin);
-    evmid = dmnsn_polynomial_evaluate(poly, degree, mid);
-    int sign = dmnsn_sgn(evmid);
-
-    if ((fabs(evmid) < fabs(mid)*dmnsn_epsilon
-         // This condition improves stability when one of the bounds is close to
-         // a different root than we are trying to find
-         && fabs(evmid) <= evinitial)
-        || max - min < fabs(mid)*dmnsn_epsilon)
-    {
-      break;
-    }
-
-    if (mid < min) {
-      // This can happen due to numerical instability in the root bounding
-      // algorithm, so behave like the normal secant method
-      max = min;
-      evmax = evmin;
-      min = mid;
-      evmin = evmid;
-    } else if (mid > max) {
-      min = max;
-      evmin = evmax;
-      max = mid;
-      evmax = evmid;
-    } else if (sign == dmnsn_sgn(evmax)) {
-      max = mid;
-      evmax = evmid;
-      if (sign == lastsign) {
-        // Don't allow the algorithm to keep the same endpoint for three
-        // iterations in a row; this ensures superlinear convergence
-        evmin /= 2.0;
-      }
-    } else {
-      min = mid;
-      evmin = evmid;
-      if (sign == lastsign) {
-        evmax /= 2.0;
-      }
-    }
-
-    lastsign = sign;
-  }
-
-  return mid;
-}
-
-/// Use synthetic division to eliminate the root \p r from \p poly[].
-static inline size_t
-dmnsn_eliminate_root(double poly[], size_t degree, double r)
-{
-  double rem = poly[degree];
-  for (size_t i = degree; i-- > 0;) {
-    double temp = poly[i];
-    poly[i] = rem;
-    rem = temp + r*rem;
-  }
-  return degree - 1;
-}
-
-/// Solve a normalized linear polynomial algebraically.
-static inline size_t
-dmnsn_solve_linear(const double poly[2], double x[1])
-{
-  x[0] = -poly[0];
-  if (x[0] >= dmnsn_epsilon)
-    return 1;
-  else
-    return 0;
-}
-
-/// Solve a normalized quadratic polynomial algebraically.
-static inline size_t
-dmnsn_solve_quadratic(const double poly[3], double x[2])
-{
-  double disc = poly[1]*poly[1] - 4.0*poly[0];
-  if (disc >= 0.0) {
-    double s = sqrt(disc);
-    x[0] = (-poly[1] + s)/2.0;
-    x[1] = (-poly[1] - s)/2.0;
-
-    if (x[1] >= dmnsn_epsilon)
-      return 2;
-    else if (x[0] >= dmnsn_epsilon)
-      return 1;
-    else
-      return 0;
-  } else {
-    return 0;
-  }
-}
-
-/// Solve a normalized cubic polynomial algebraically.
-static inline size_t
-dmnsn_solve_cubic(double poly[4], double x[3])
-{
-  // Reduce to a monic trinomial (t^3 + p*t + q, t = x + b/3)
-  double b2 = poly[2]*poly[2];
-  double p = poly[1] - b2/3.0;
-  double q = poly[0] - poly[2]*(9.0*poly[1] - 2.0*b2)/27.0;
-
-  double disc = 4.0*p*p*p + 27.0*q*q;
-  double bdiv3 = poly[2]/3.0;
-
-  if (disc < 0.0) {
-    // Three real roots -- this implies p < 0
-    double msqrtp3 = -sqrt(-p/3.0);
-    double theta = acos(3*q/(2*p*msqrtp3))/3.0;
-
-    // Store the roots in order from largest to smallest
-    x[2] = 2.0*msqrtp3*cos(theta) - bdiv3;
-    x[0] = -2.0*msqrtp3*cos(4.0*atan(1.0)/3.0 - theta) - bdiv3;
-    x[1] = -(x[0] + x[2] + poly[2]);
-
-    if (x[2] >= dmnsn_epsilon)
-      return 3;
-    else if (x[1] >= dmnsn_epsilon)
-      return 2;
-  } else if (disc > 0.0) {
-    // One real root
-    double cbrtdiscq = cbrt(sqrt(disc/108.0) + fabs(q)/2.0);
-    double abst = cbrtdiscq - p/(3.0*cbrtdiscq);
-
-    if (q >= 0) {
-      x[0] = -abst - bdiv3;
-    } else {
-      x[0] = abst - bdiv3;
-    }
-  } else if (fabs(p) < dmnsn_epsilon) {
-    // Equation is a perfect cube
-    x[0] = -bdiv3;
-  } else {
-    // Two real roots; one duplicate
-    double t1 = -(3.0*q)/(2.0*p), t2 = -2.0*t1;
-    x[0] = dmnsn_max(t1, t2) - bdiv3;
-    x[1] = dmnsn_min(t1, t2) - bdiv3;
-    if (x[1] >= dmnsn_epsilon)
-      return 2;
-  }
-
-  if (x[0] >= dmnsn_epsilon)
-    return 1;
-  else
-    return 0;
-}
-
-// Solve a polynomial
-DMNSN_HOT size_t
-dmnsn_polynomial_solve(const double poly[], size_t degree, double x[])
-{
-  // Copy the polynomial so we can be destructive
-  double copy[degree + 1], *p = copy;
-  dmnsn_polynomial_copy(p, poly, degree);
-
-  // Index into x[]
-  size_t i = 0;
-
-  // Account for leading zero coefficients
-  degree = dmnsn_real_degree(p, degree);
-  // Normalize the leading coefficient to 1.0
-  dmnsn_polynomial_normalize(p, degree);
-  // Eliminate simple zero roots
-  dmnsn_eliminate_zero_roots(&p, &degree);
-
-  static const size_t max_algebraic = 3;
-  if (degree > max_algebraic) {
-    // Find isolating intervals for (degree - max_algebraic) roots of p[]
-    double ranges[degree - max_algebraic][2];
-    size_t n = dmnsn_root_bounds(p, degree, ranges, degree - max_algebraic);
-
-    for (size_t j = 0; j < n; ++j) {
-      // Bisect within the found range
-      double r = dmnsn_bisect_root(p, degree, ranges[j][0], ranges[j][1]);
-
-      // Use synthetic division to eliminate the root `r'
-      degree = dmnsn_eliminate_root(p, degree, r);
-
-      // Store the found root
-      x[i] = r;
-      ++i;
-    }
-  }
-
-  switch (degree) {
-  case 1:
-    i += dmnsn_solve_linear(p, x + i);
-    break;
-  case 2:
-    i += dmnsn_solve_quadratic(p, x + i);
-    break;
-  case 3:
-    i += dmnsn_solve_cubic(p, x + i);
-    break;
-  }
-
-  return i;
-}
-
-// Print a polynomial
-void
-dmnsn_polynomial_print(FILE *file, const double poly[], size_t degree)
-{
-  for (size_t i = degree + 1; i-- > 0;) {
-    if (i < degree) {
-      fprintf(file, (poly[i] >= 0.0) ? " + " : " - ");
-    }
-    fprintf(file, "%.17g", fabs(poly[i]));
-    if (i >= 2) {
-      fprintf(file, "*x^%zu", i);
-    } else if (i == 1) {
-      fprintf(file, "*x");
-    }
-  }
-}
diff --git a/libdimension/pool.c b/libdimension/pool.c
deleted file mode 100644
index a7d6b4f..0000000
--- a/libdimension/pool.c
+++ /dev/null
@@ -1,176 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>          *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Memory pool implementation.
- */
-
-#include "dimension-internal.h"
-#include <stdatomic.h>
-
-/// Number of pointers per block, we want a block to fit in a single page.
-#define DMNSN_POOL_BLOCK_SIZE (4096/sizeof(void *) - 4)
-/// Number of pointers per tidy block
-#define DMNSN_TIDY_BLOCK_SIZE ((4096 - 4*sizeof(void *))/(sizeof(void *) + sizeof(dmnsn_callback_fn *)))
-
-/// A single block in a thread-specific pool.
-typedef struct dmnsn_pool_block {
-  /// Current index into allocs[].
-  size_t i;
-  /// All allocations in the current block.
-  void *allocs[DMNSN_POOL_BLOCK_SIZE];
-  /// Tail pointer to the previous block in the global chain.
-  struct dmnsn_pool_block *prev;
-} dmnsn_pool_block;
-
-/// A single tidy block in a thread-specific pool.
-typedef struct dmnsn_tidy_block {
-  /// Current index into allocs[].
-  size_t i;
-  /// All allocations in the current block.
-  void *allocs[DMNSN_TIDY_BLOCK_SIZE];
-  /// All cleanup callbacks in the current block.
-  dmnsn_callback_fn *cleanup_fns[DMNSN_TIDY_BLOCK_SIZE];
-  /// Tail pointer to the previous tidy block in the global chain.
-  struct dmnsn_tidy_block *prev;
-} dmnsn_tidy_block;
-
-/// dmnsn_pool implementation.
-struct dmnsn_pool {
-  /// Thread-local regular block.
-  pthread_key_t thread_block;
-  /// Thread-local tidy block.
-  pthread_key_t thread_tidy_block;
-
-  /// Global chain of regular blocks.
-  atomic(dmnsn_pool_block *) chain;
-  /// Global chain of tidy blocks.
-  atomic(dmnsn_tidy_block *) tidy_chain;
-};
-
-dmnsn_pool *
-dmnsn_new_pool(void)
-{
-  dmnsn_pool *pool = DMNSN_MALLOC(dmnsn_pool);
-
-  dmnsn_key_create(&pool->thread_block, NULL);
-  dmnsn_key_create(&pool->thread_tidy_block, NULL);
-
-  atomic_store_explicit(&pool->chain, NULL, memory_order_relaxed);
-  atomic_store_explicit(&pool->tidy_chain, NULL, memory_order_relaxed);
-
-  return pool;
-}
-
-void *
-dmnsn_palloc(dmnsn_pool *pool, size_t size)
-{
-  dmnsn_pool_block *old_block = pthread_getspecific(pool->thread_block);
-
-  dmnsn_pool_block *new_block = old_block;
-  if (dmnsn_unlikely(!old_block || old_block->i == DMNSN_POOL_BLOCK_SIZE)) {
-    new_block = DMNSN_MALLOC(dmnsn_pool_block);
-    new_block->i = 0;
-  }
-
-  void *result = dmnsn_malloc(size);
-  new_block->allocs[new_block->i++] = result;
-
-  if (dmnsn_unlikely(new_block != old_block)) {
-    dmnsn_setspecific(pool->thread_block, new_block);
-
-    // Atomically update pool->chain
-    dmnsn_pool_block *old_chain = atomic_exchange(&pool->chain, new_block);
-    new_block->prev = old_chain;
-  }
-
-  return result;
-}
-
-void *
-dmnsn_palloc_tidy(dmnsn_pool *pool, size_t size, dmnsn_callback_fn *cleanup_fn)
-{
-  dmnsn_assert(cleanup_fn != NULL, "NULL cleanup_fn");
-
-  dmnsn_tidy_block *old_block = pthread_getspecific(pool->thread_tidy_block);
-
-  dmnsn_tidy_block *new_block = old_block;
-  if (dmnsn_unlikely(!old_block || old_block->i == DMNSN_TIDY_BLOCK_SIZE)) {
-    new_block = DMNSN_MALLOC(dmnsn_tidy_block);
-    new_block->i = 0;
-  }
-
-  void *result = dmnsn_malloc(size);
-
-  size_t i = new_block->i;
-  new_block->allocs[i] = result;
-  new_block->cleanup_fns[i] = cleanup_fn;
-  ++new_block->i;
-
-  if (dmnsn_unlikely(new_block != old_block)) {
-    dmnsn_setspecific(pool->thread_tidy_block, new_block);
-
-    // Atomically update pool->tidy_chain
-    dmnsn_tidy_block *old_chain = atomic_exchange(&pool->tidy_chain, new_block);
-    new_block->prev = old_chain;
-  }
-
-  return result;
-}
-
-void
-dmnsn_delete_pool(dmnsn_pool *pool)
-{
-  if (!pool) {
-    return;
-  }
-
-  dmnsn_pool_block *block = atomic_load_explicit(&pool->chain, memory_order_relaxed);
-  while (block) {
-    // Free all the allocations
-    for (size_t i = block->i; i-- > 0;) {
-      dmnsn_free(block->allocs[i]);
-    }
-
-    // Free the block itself and go to the previous one
-    dmnsn_pool_block *saved = block;
-    block = block->prev;
-    dmnsn_free(saved);
-  }
-
-  dmnsn_tidy_block *tidy_block = atomic_load_explicit(&pool->tidy_chain, memory_order_relaxed);
-  while (tidy_block) {
-    // Free all the allocations
-    for (size_t i = tidy_block->i; i-- > 0;) {
-      void *ptr = tidy_block->allocs[i];
-      tidy_block->cleanup_fns[i](ptr);
-      dmnsn_free(ptr);
-    }
-
-    // Free the block itself and go to the previous one
-    dmnsn_tidy_block *saved = tidy_block;
-    tidy_block = tidy_block->prev;
-    dmnsn_free(saved);
-  }
-
-  dmnsn_key_delete(pool->thread_tidy_block);
-  dmnsn_free(pool);
-}
diff --git a/libdimension/profile.c b/libdimension/profile.c
deleted file mode 100644
index 164e002..0000000
--- a/libdimension/profile.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Branch profile accounting.
- */
-
-#include "dimension-internal.h"
-#include <pthread.h>
-#include <stdio.h>
-#include <inttypes.h>
-
-/// Information on one predicted branch.
-typedef struct {
-  char *location;
-  uint64_t predicted, branches;
-} dmnsn_branch;
-
-/// Count of mispredicted branches.
-static dmnsn_dictionary *dmnsn_profile = NULL;
-/// Mutex which protects \c dmnsn_profile.
-static pthread_mutex_t dmnsn_profile_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-/// Thread-local count of mispredicted branches.
-static pthread_key_t dmnsn_thread_profile;
-/// Initialize the thread-specific pointer exactly once.
-static pthread_once_t dmnsn_thread_profile_once = PTHREAD_ONCE_INIT;
-
-/// Add thread-specific profile data to the global counts.
-static void
-dmnsn_profile_globalize(void *ptr)
-{
-  dmnsn_branch *branch = ptr;
-  dmnsn_branch *global = dmnsn_dictionary_at(dmnsn_profile, branch->location);
-  if (global) {
-    global->predicted += branch->predicted;
-    global->branches  += branch->branches;
-    dmnsn_free(branch->location);
-  } else {
-    dmnsn_dictionary_insert(dmnsn_profile, branch->location, branch);
-  }
-}
-
-/// Destructor function for thread-specific profile data.
-static void
-dmnsn_delete_thread_profile(void *ptr)
-{
-  dmnsn_dictionary *thread_profile = ptr;
-
-  dmnsn_lock_mutex(&dmnsn_profile_mutex);
-    dmnsn_dictionary_apply(thread_profile, dmnsn_profile_globalize);
-  dmnsn_unlock_mutex(&dmnsn_profile_mutex);
-
-  dmnsn_delete_dictionary(thread_profile);
-}
-
-/// Initialize the thread-specific pointer.
-static void
-dmnsn_initialize_thread_profile(void)
-{
-  dmnsn_key_create(&dmnsn_thread_profile, dmnsn_delete_thread_profile);
-
-  dmnsn_lock_mutex(&dmnsn_profile_mutex);
-    dmnsn_profile = dmnsn_new_dictionary(sizeof(dmnsn_branch));
-  dmnsn_unlock_mutex(&dmnsn_profile_mutex);
-}
-
-/// Get the thread-specific profile data.
-static dmnsn_dictionary *
-dmnsn_get_thread_profile(void)
-{
-  dmnsn_once(&dmnsn_thread_profile_once, dmnsn_initialize_thread_profile);
-  return pthread_getspecific(dmnsn_thread_profile);
-}
-
-/// Set the thread-specific profile data.
-static void
-dmnsn_set_thread_profile(dmnsn_dictionary *thread_profile)
-{
-  dmnsn_setspecific(dmnsn_thread_profile, thread_profile);
-}
-
-bool
-dmnsn_expect(bool result, bool expected, const char *func, const char *file,
-             unsigned int line)
-{
-  int size = snprintf(NULL, 0, "%s:%s:%u", file, func, line) + 1;
-  if (size < 1) {
-    dmnsn_error("sprintf() failed.");
-  }
-
-  char key[size];
-  if (snprintf(key, size, "%s:%s:%u", file, func, line) < 0) {
-    dmnsn_error("sprintf() failed.");
-  }
-
-  dmnsn_dictionary *thread_profile = dmnsn_get_thread_profile();
-  if (!thread_profile) {
-    thread_profile = dmnsn_new_dictionary(sizeof(dmnsn_branch));
-    dmnsn_set_thread_profile(thread_profile);
-  }
-
-  dmnsn_branch *branch = dmnsn_dictionary_at(thread_profile, key);
-  if (branch) {
-    ++branch->branches;
-    if (result == expected) {
-      ++branch->predicted;
-    }
-  } else {
-    dmnsn_branch new_branch = {
-      .location  = dmnsn_strdup(key),
-      .predicted = (result == expected) ? 1 : 0,
-      .branches  = 1
-    };
-    dmnsn_dictionary_insert(thread_profile, key, &new_branch);
-  }
-
-  return result;
-}
-
-static void
-dmnsn_print_bad_prediction(void *ptr)
-{
-  dmnsn_branch *branch = ptr;
-  double rate = ((double)branch->predicted)/branch->branches;
-  if (rate < 0.75 || branch->branches < 100000) {
-    fprintf(stderr,
-            "Bad branch prediction: %s: %" PRIu64 "/%" PRIu64 " (%g%%)\n",
-            branch->location, branch->predicted, branch->branches, 100.0*rate);
-  }
-
-  dmnsn_free(branch->location);
-}
-
-DMNSN_DESTRUCTOR static void
-dmnsn_print_bad_predictions(void)
-{
-  dmnsn_dictionary *thread_profile = dmnsn_get_thread_profile();
-  if (thread_profile) {
-    dmnsn_delete_thread_profile(thread_profile);
-    dmnsn_set_thread_profile(NULL);
-  }
-
-  dmnsn_lock_mutex(&dmnsn_profile_mutex);
-    dmnsn_dictionary_apply(dmnsn_profile, dmnsn_print_bad_prediction);
-    dmnsn_delete_dictionary(dmnsn_profile);
-    dmnsn_profile = NULL;
-  dmnsn_unlock_mutex(&dmnsn_profile_mutex);
-}
diff --git a/libdimension/profile.h b/libdimension/profile.h
deleted file mode 100644
index 4817482..0000000
--- a/libdimension/profile.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Built-in branch profiler.
- */
-
-#include <stdbool.h>
-
-/**
- * Record a test and its expected result.  Called by dmnsn_[un]likely();
- * don't call directly.
- * @param[in] result    The result of the test.
- * @param[in] expected  The expected result of the test.
- * @param[in] func      The name of the function the test occurs in.
- * @param[in] file      The name of the file the test occurs in.
- * @param[in] line      The line number on which the test occurs.
- * @return \p result.
- */
-DMNSN_INTERNAL bool dmnsn_expect(bool result, bool expected, const char *func,
-                                 const char *file, unsigned int line);
diff --git a/libdimension/prtree.c b/libdimension/prtree.c
deleted file mode 100644
index bb7beec..0000000
--- a/libdimension/prtree.c
+++ /dev/null
@@ -1,370 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Priority R-tree implementation.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-/// Number of children per PR-node.
-#define DMNSN_PRTREE_B 8
-/// Number of priority leaves per pseudo-PR-node (must be 2*ndimensions).
-#define DMNSN_PSEUDO_B 6
-
-/// The side of the split that a node ended up on.
-typedef enum dmnsn_prnode_color {
-  DMNSN_PRTREE_LEAF, ///< Priority leaf.
-  DMNSN_PRTREE_LEFT, ///< Left child.
-  DMNSN_PRTREE_RIGHT ///< Right child.
-} dmnsn_prnode_color;
-
-/**
- * A BVH node with associated color.  Compared to storing the color in the
- * \p dmnsn_bvh_node itself, this method has decreased cache performance during
- * sorting (due to an extra pointer chase), but increased performance during
- * tree building (because it's much smaller than a full \p dmnsn_bvh_node).
- * Overall it gives about a 25% improvement.
- */
-typedef struct dmnsn_colored_prnode {
-  dmnsn_prnode_color color;
-  dmnsn_bvh_node *node;
-} dmnsn_colored_prnode;
-
-/// Construct an empty PR-node.
-static inline dmnsn_bvh_node *
-dmnsn_new_prnode(void)
-{
-  return dmnsn_new_bvh_node(DMNSN_PRTREE_B);
-}
-
-/// Comparator types.
-enum {
-  DMNSN_XMIN,
-  DMNSN_YMIN,
-  DMNSN_ZMIN,
-  DMNSN_XMAX,
-  DMNSN_YMAX,
-  DMNSN_ZMAX
-};
-
-// List sorting comparators
-
-static int
-dmnsn_xmin_comp(const void *l, const void *r)
-{
-  double lval = (*(const dmnsn_colored_prnode **)l)->node->bounding_box.min.x;
-  double rval = (*(const dmnsn_colored_prnode **)r)->node->bounding_box.min.x;
-  return (lval > rval) - (lval < rval);
-}
-
-static int
-dmnsn_ymin_comp(const void *l, const void *r)
-{
-  double lval = (*(const dmnsn_colored_prnode **)l)->node->bounding_box.min.y;
-  double rval = (*(const dmnsn_colored_prnode **)r)->node->bounding_box.min.y;
-  return (lval > rval) - (lval < rval);
-}
-
-static int
-dmnsn_zmin_comp(const void *l, const void *r)
-{
-  double lval = (*(const dmnsn_colored_prnode **)l)->node->bounding_box.min.z;
-  double rval = (*(const dmnsn_colored_prnode **)r)->node->bounding_box.min.z;
-  return (lval > rval) - (lval < rval);
-}
-
-static int
-dmnsn_xmax_comp(const void *l, const void *r)
-{
-  double lval = (*(const dmnsn_colored_prnode **)l)->node->bounding_box.max.x;
-  double rval = (*(const dmnsn_colored_prnode **)r)->node->bounding_box.max.x;
-  return (lval < rval) - (lval > rval);
-}
-
-static int
-dmnsn_ymax_comp(const void *l, const void *r)
-{
-  double lval = (*(const dmnsn_colored_prnode **)l)->node->bounding_box.max.y;
-  double rval = (*(const dmnsn_colored_prnode **)r)->node->bounding_box.max.y;
-  return (lval < rval) - (lval > rval);
-}
-
-static int
-dmnsn_zmax_comp(const void *l, const void *r)
-{
-  double lval = (*(const dmnsn_colored_prnode **)l)->node->bounding_box.max.z;
-  double rval = (*(const dmnsn_colored_prnode **)r)->node->bounding_box.max.z;
-  return (lval < rval) - (lval > rval);
-}
-
-/// All comparators.
-static dmnsn_array_comparator_fn *const dmnsn_comparators[DMNSN_PSEUDO_B] = {
-  [DMNSN_XMIN] = dmnsn_xmin_comp,
-  [DMNSN_YMIN] = dmnsn_ymin_comp,
-  [DMNSN_ZMIN] = dmnsn_zmin_comp,
-  [DMNSN_XMAX] = dmnsn_xmax_comp,
-  [DMNSN_YMAX] = dmnsn_ymax_comp,
-  [DMNSN_ZMAX] = dmnsn_zmax_comp,
-};
-
-/// Add the priority leaves for this level.
-static void
-dmnsn_add_priority_leaves(dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B],
-                          size_t start, size_t end,
-                          dmnsn_array *new_leaves)
-{
-  for (size_t i = 0; i < DMNSN_PSEUDO_B; ++i) {
-    dmnsn_bvh_node *leaf = NULL;
-    dmnsn_colored_prnode **leaves = sorted_leaves[i];
-
-    for (size_t j = start;
-         j < end && (!leaf || leaf->nchildren < DMNSN_PRTREE_B);
-         ++j) {
-      // Skip all the previously found extreme nodes
-      if (leaves[j]->color == DMNSN_PRTREE_LEAF) {
-        continue;
-      }
-
-      if (!leaf) {
-        leaf = dmnsn_new_prnode();
-      }
-      leaves[j]->color = DMNSN_PRTREE_LEAF;
-      dmnsn_bvh_node_add(leaf, leaves[j]->node);
-    }
-
-    if (leaf) {
-      dmnsn_array_push(new_leaves, &leaf);
-    } else {
-      return;
-    }
-  }
-}
-
-/// Get rid of the extreme nodes.
-static void
-dmnsn_filter_priority_leaves(dmnsn_colored_prnode **leaves, size_t start, size_t *endp)
-{
-  size_t i, skip, end;
-  for (i = start, skip = 0, end = *endp; i < end; ++i) {
-    if (leaves[i]->color == DMNSN_PRTREE_LEAF) {
-      ++skip;
-    } else {
-      leaves[i - skip] = leaves[i];
-    }
-  }
-
-  *endp -= skip;
-}
-
-/// Split the leaves and mark the left and right child nodes.
-static void
-dmnsn_split_sorted_leaves_easy(dmnsn_colored_prnode **leaves, size_t start, size_t *midp, size_t end)
-{
-  size_t i, mid = start + (end - start + 1)/2;
-  for (i = start; i < mid; ++i) {
-    leaves[i]->color = DMNSN_PRTREE_LEFT;
-  }
-  for (; i < end; ++i) {
-    leaves[i]->color = DMNSN_PRTREE_RIGHT;
-  }
-
-  *midp = mid;
-}
-
-/// Split the leaves using the coloring from dmnsn_split_sorted_leaves_easy().
-static void
-dmnsn_split_sorted_leaves_hard(dmnsn_colored_prnode **leaves, dmnsn_colored_prnode **buffer, size_t start, size_t end)
-{
-  size_t i, j, skip;
-  for (i = start, j = 0, skip = 0; i < end; ++i) {
-    if (leaves[i]->color == DMNSN_PRTREE_LEFT) {
-      leaves[i - skip] = leaves[i];
-    } else {
-      if (leaves[i]->color == DMNSN_PRTREE_RIGHT) {
-        buffer[j] = leaves[i];
-        ++j;
-      }
-      ++skip;
-    }
-  }
-
-  size_t mid = i - skip;
-  for (i = 0; i < j; ++i) {
-    leaves[mid + i] = buffer[i];
-  }
-}
-
-/// Split the sorted lists into the left and right subtrees.
-static void
-dmnsn_split_sorted_leaves(dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B],
-                          size_t start, size_t *midp, size_t *endp,
-                          dmnsn_colored_prnode **buffer, int i)
-{
-  size_t orig_end = *endp;
-
-  // Filter the extreme nodes in the ith list
-  dmnsn_filter_priority_leaves(sorted_leaves[i], start, endp);
-
-  // Split the ith list
-  dmnsn_split_sorted_leaves_easy(sorted_leaves[i], start, midp, *endp);
-
-  // Split the rest of the lists
-  for (size_t j = 0; j < DMNSN_PSEUDO_B; ++j) {
-    if (j == i) {
-      continue;
-    }
-
-    dmnsn_split_sorted_leaves_hard(sorted_leaves[j], buffer, start, orig_end);
-  }
-}
-
-/// Recursively constructs an implicit pseudo-PR-tree and collects the priority
-/// leaves.
-static void
-dmnsn_priority_leaves_recursive(dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B],
-                                size_t start, size_t end,
-                                dmnsn_colored_prnode **buffer,
-                                dmnsn_array *new_leaves,
-                                int comparator)
-{
-  dmnsn_add_priority_leaves(sorted_leaves, start, end, new_leaves);
-
-  size_t mid;
-  dmnsn_split_sorted_leaves(sorted_leaves, start, &mid, &end, buffer, comparator);
-
-  int next = (comparator + 1)%DMNSN_PSEUDO_B;
-
-  if (start < mid) {
-    dmnsn_priority_leaves_recursive(sorted_leaves, start, mid, buffer, new_leaves, next);
-  }
-
-  if (mid < end) {
-    dmnsn_priority_leaves_recursive(sorted_leaves, mid, end, buffer, new_leaves, next);
-  }
-}
-
-/// Sort each dimension in parallel with more than this many leaves.
-#define DMNSN_PARALLEL_SORT_THRESHOLD 1024
-
-typedef struct {
-  dmnsn_colored_prnode *colored_leaves;
-  dmnsn_colored_prnode ***sorted_leaves;
-  size_t nleaves;
-} dmnsn_sort_leaves_payload;
-
-static dmnsn_colored_prnode **
-dmnsn_sort_leaf_array(dmnsn_colored_prnode *colored_leaves, size_t nleaves, int comparator)
-{
-  dmnsn_colored_prnode **sorted_leaves = dmnsn_malloc(nleaves*sizeof(dmnsn_colored_prnode *));
-
-  for (size_t i = 0; i < nleaves; ++i) {
-    sorted_leaves[i] = colored_leaves + i;
-  }
-
-  qsort(sorted_leaves, nleaves, sizeof(dmnsn_colored_prnode *), dmnsn_comparators[comparator]);
-
-  return sorted_leaves;
-}
-
-static int
-dmnsn_sort_leaves(void *ptr, unsigned int thread, unsigned int nthreads)
-{
-  dmnsn_sort_leaves_payload *payload = ptr;
-
-  for (unsigned int i = thread; i < DMNSN_PSEUDO_B; i += nthreads) {
-    payload->sorted_leaves[i] = dmnsn_sort_leaf_array(payload->colored_leaves, payload->nleaves, i);
-  }
-
-  return 0;
-}
-
-/// Constructs an implicit pseudo-PR-tree and returns the priority leaves.
-static dmnsn_array *
-dmnsn_priority_leaves(const dmnsn_array *leaves, unsigned int nthreads)
-{
-  dmnsn_bvh_node **leaves_arr = dmnsn_array_first(leaves);
-  size_t nleaves = dmnsn_array_size(leaves);
-
-  dmnsn_colored_prnode *colored_leaves = dmnsn_malloc(nleaves*sizeof(dmnsn_colored_prnode));
-  for (size_t i = 0; i < nleaves; ++i) {
-    colored_leaves[i].color = DMNSN_PRTREE_LEFT; // Mustn't be _LEAF
-    colored_leaves[i].node = leaves_arr[i];
-  }
-
-  dmnsn_colored_prnode **sorted_leaves[DMNSN_PSEUDO_B];
-
-  if (nleaves >= DMNSN_PARALLEL_SORT_THRESHOLD && nthreads > 1) {
-    dmnsn_sort_leaves_payload payload = {
-      .colored_leaves = colored_leaves,
-      .sorted_leaves = sorted_leaves,
-      .nleaves = nleaves,
-    };
-    dmnsn_execute_concurrently(NULL, dmnsn_sort_leaves, &payload, nthreads);
-  } else {
-    for (size_t i = 0; i < DMNSN_PSEUDO_B; ++i) {
-      sorted_leaves[i] = dmnsn_sort_leaf_array(colored_leaves, nleaves, i);
-    }
-  }
-
-  size_t buffer_size = nleaves/2;
-  dmnsn_colored_prnode **buffer = dmnsn_malloc(buffer_size*sizeof(dmnsn_colored_prnode *));
-
-  dmnsn_array *new_leaves = DMNSN_NEW_ARRAY(dmnsn_bvh_node *);
-
-  dmnsn_priority_leaves_recursive(sorted_leaves, 0, nleaves, buffer, new_leaves, 0);
-
-  dmnsn_free(buffer);
-  for (size_t i = 0; i < DMNSN_PSEUDO_B; ++i) {
-    dmnsn_free(sorted_leaves[i]);
-  }
-  dmnsn_free(colored_leaves);
-
-  return new_leaves;
-}
-
-dmnsn_bvh_node *
-dmnsn_new_prtree(const dmnsn_array *objects)
-{
-  if (dmnsn_array_size(objects) == 0) {
-    return NULL;
-  }
-
-  // Make the initial array of leaves
-  dmnsn_array *leaves = DMNSN_NEW_ARRAY(dmnsn_bvh_node *);
-  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, objects) {
-    dmnsn_bvh_node *node = dmnsn_new_bvh_leaf_node(*object);
-    dmnsn_array_push(leaves, &node);
-  }
-
-  unsigned int ncpus = dmnsn_ncpus();
-  unsigned int nthreads = ncpus < DMNSN_PSEUDO_B ? ncpus : DMNSN_PSEUDO_B;
-  while (dmnsn_array_size(leaves) > 1) {
-    dmnsn_array *new_leaves = dmnsn_priority_leaves(leaves, nthreads);
-    dmnsn_delete_array(leaves);
-    leaves = new_leaves;
-  }
-
-  dmnsn_bvh_node *root = *(dmnsn_bvh_node **)dmnsn_array_first(leaves);
-  dmnsn_delete_array(leaves);
-  return root;
-}
diff --git a/libdimension/prtree.h b/libdimension/prtree.h
deleted file mode 100644
index 76a4051..0000000
--- a/libdimension/prtree.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2012 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file.
- * Priority R-trees.  PR-trees are a data structure introduced by Arge, de Berg,
- * Haverkort, and Yi, which provides asymptotically optimal worst-case lookup,
- * while remaining efficient with real-world data.  Their structure is derived
- * from B-trees.
- */
-
-/// Create a PR-tree.
-DMNSN_INTERNAL dmnsn_bvh_node *dmnsn_new_prtree(const dmnsn_array *objects);
diff --git a/libdimension/ray_trace.c b/libdimension/ray_trace.c
deleted file mode 100644
index 6f4ce33..0000000
--- a/libdimension/ray_trace.c
+++ /dev/null
@@ -1,547 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * The ray-tracing algorithm.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-////////////////////////////////////
-// Boilerplate for multithreading //
-////////////////////////////////////
-
-/// Payload type for passing arguments to worker threads.
-typedef struct {
-  dmnsn_future *future;
-  dmnsn_scene *scene;
-  dmnsn_bvh *bvh;
-} dmnsn_ray_trace_payload;
-
-// Ray-trace a scene
-void
-dmnsn_ray_trace(dmnsn_scene *scene)
-{
-  dmnsn_future *future = dmnsn_ray_trace_async(scene);
-  if (dmnsn_future_join(future) != 0) {
-    dmnsn_error("Error occured while ray-tracing.");
-  }
-}
-
-/// Background thread callback.
-static int dmnsn_ray_trace_scene_thread(void *ptr);
-
-// Ray-trace a scene in the background
-dmnsn_future *
-dmnsn_ray_trace_async(dmnsn_scene *scene)
-{
-  dmnsn_future *future = dmnsn_new_future();
-
-  dmnsn_ray_trace_payload *payload = DMNSN_MALLOC(dmnsn_ray_trace_payload);
-  payload->future = future;
-  payload->scene  = scene;
-
-  dmnsn_new_thread(future, dmnsn_ray_trace_scene_thread, payload);
-
-  return future;
-}
-
-/// Worker thread callback.
-static int dmnsn_ray_trace_scene_concurrent(void *ptr, unsigned int thread,
-                                            unsigned int nthreads);
-
-// Thread callback -- set up the multithreaded engine
-static int
-dmnsn_ray_trace_scene_thread(void *ptr)
-{
-  dmnsn_ray_trace_payload *payload = ptr;
-
-  // Pre-calculate bounding box transformations, etc.
-  dmnsn_scene_initialize(payload->scene);
-
-  // Time the bounding tree construction
-  dmnsn_timer_start(&payload->scene->bounding_timer);
-    payload->bvh = dmnsn_new_bvh(payload->scene->objects, DMNSN_BVH_PRTREE);
-  dmnsn_timer_stop(&payload->scene->bounding_timer);
-
-  // Set up the future object
-  dmnsn_future_set_total(payload->future, payload->scene->canvas->height);
-
-  // Time the render itself
-  dmnsn_timer_start(&payload->scene->render_timer);
-    int ret = dmnsn_execute_concurrently(payload->future,
-                                         dmnsn_ray_trace_scene_concurrent,
-                                         payload, payload->scene->nthreads);
-  dmnsn_timer_stop(&payload->scene->render_timer);
-
-  dmnsn_delete_bvh(payload->bvh);
-  dmnsn_free(payload);
-
-  return ret;
-}
-
-///////////////////////////
-// Ray-tracing algorithm //
-///////////////////////////
-
-/// The current state of the ray-tracing engine.
-typedef struct dmnsn_rtstate {
-  const struct dmnsn_rtstate *parent;
-
-  const dmnsn_scene *scene;
-  const dmnsn_intersection *intersection;
-  const dmnsn_texture *texture;
-  const dmnsn_interior *interior;
-  const dmnsn_bvh *bvh;
-  unsigned int reclevel;
-
-  dmnsn_vector r;
-  dmnsn_vector pigment_r;
-  dmnsn_vector viewer;
-  dmnsn_vector reflected;
-
-  bool is_shadow_ray;
-  dmnsn_vector light_ray;
-  dmnsn_color light_color;
-
-  dmnsn_tcolor pigment;
-  dmnsn_tcolor color;
-
-  double ior;
-
-  dmnsn_color adc_value;
-} dmnsn_rtstate;
-
-/// Compute a ray-tracing state from an intersection.
-static inline void
-dmnsn_rtstate_initialize(dmnsn_rtstate *state,
-                         const dmnsn_intersection *intersection);
-/// Main helper for dmnsn_ray_trace_scene_concurrent - shoot a ray.
-static dmnsn_tcolor dmnsn_ray_shoot(dmnsn_rtstate *state, dmnsn_line ray);
-
-// Actually ray-trace a scene
-static int
-dmnsn_ray_trace_scene_concurrent(void *ptr, unsigned int thread,
-                                 unsigned int nthreads)
-{
-  const dmnsn_ray_trace_payload *payload = ptr;
-  dmnsn_future *future = payload->future;
-  dmnsn_scene *scene = payload->scene;
-  dmnsn_bvh *bvh = payload->bvh;
-
-  dmnsn_rtstate state = {
-    .parent = NULL,
-    .scene  = scene,
-    .bvh = bvh,
-  };
-
-  // Iterate through each pixel
-  for (size_t y = thread; y < scene->canvas->height; y += nthreads) {
-    for (size_t x = 0; x < scene->canvas->width; ++x) {
-      // Get the ray corresponding to the (x,y)'th pixel
-      dmnsn_line ray = dmnsn_camera_ray(
-        scene->camera,
-        ((double)(x + scene->region_x))/(scene->outer_width - 1),
-        ((double)(y + scene->region_y))/(scene->outer_height - 1)
-      );
-
-      // Shoot a ray
-      state.reclevel = scene->reclimit;
-      state.ior = 1.0;
-      state.adc_value = dmnsn_white;
-      dmnsn_tcolor tcolor = dmnsn_ray_shoot(&state, ray);
-      dmnsn_canvas_set_pixel(scene->canvas, x, y, tcolor);
-    }
-
-    dmnsn_future_increment(future);
-  }
-
-  return 0;
-}
-
-// Compute rtstate fields
-static inline void
-dmnsn_rtstate_initialize(dmnsn_rtstate *state,
-                         const dmnsn_intersection *intersection)
-{
-  state->intersection = intersection;
-  state->texture      = intersection->object->texture;
-  state->interior     = intersection->object->interior;
-
-  state->r = dmnsn_line_point(intersection->ray, intersection->t);
-  state->pigment_r = dmnsn_transform_point(
-    intersection->object->pigment_trans,
-    state->r
-  );
-  state->viewer = dmnsn_vector_normalized(
-    dmnsn_vector_negate(intersection->ray.n)
-  );
-  state->reflected = dmnsn_vector_sub(
-    dmnsn_vector_mul(
-      2*dmnsn_vector_dot(state->viewer, intersection->normal),
-      intersection->normal),
-    state->viewer
-  );
-
-  state->is_shadow_ray = false;
-}
-
-/// Calculate the background color.
-static void dmnsn_trace_background(dmnsn_rtstate *state, dmnsn_line ray);
-/// Calculate the base pigment at the intersection.
-static void dmnsn_trace_pigment(dmnsn_rtstate *state);
-/// Handle light, shadow, and shading.
-static void dmnsn_trace_lighting(dmnsn_rtstate *state);
-/// Trace a reflected ray.
-static void dmnsn_trace_reflection(dmnsn_rtstate *state);
-/// Trace a transmitted ray.
-static void dmnsn_trace_transparency(dmnsn_rtstate *state);
-
-// Shoot a ray, and calculate the color
-static dmnsn_tcolor
-dmnsn_ray_shoot(dmnsn_rtstate *state, dmnsn_line ray)
-{
-  if (state->reclevel == 0
-      || dmnsn_color_intensity(state->adc_value) < state->scene->adc_bailout)
-  {
-    return DMNSN_TCOLOR(dmnsn_black);
-  }
-
-  --state->reclevel;
-
-  dmnsn_intersection intersection;
-  bool reset = state->reclevel == state->scene->reclimit - 1;
-  dmnsn_bvh_intersection(state->bvh, ray, &intersection, reset);
-  if (dmnsn_bvh_intersection(state->bvh, ray, &intersection, reset)) {
-    // Found an intersection
-    dmnsn_rtstate_initialize(state, &intersection);
-
-    dmnsn_trace_pigment(state);
-    if (state->scene->quality & DMNSN_RENDER_LIGHTS) {
-      dmnsn_trace_lighting(state);
-    }
-    if (state->scene->quality & DMNSN_RENDER_REFLECTION) {
-      dmnsn_trace_reflection(state);
-    }
-    if (state->scene->quality & DMNSN_RENDER_TRANSPARENCY) {
-      dmnsn_trace_transparency(state);
-    }
-  } else {
-    // No intersection, return the background color
-    dmnsn_trace_background(state, ray);
-  }
-
-  return state->color;
-}
-
-static void
-dmnsn_trace_background(dmnsn_rtstate *state, dmnsn_line ray)
-{
-  dmnsn_pigment *background = state->scene->background;
-  if (state->scene->quality & DMNSN_RENDER_PIGMENT) {
-    dmnsn_vector r = dmnsn_vector_normalized(ray.n);
-    state->color = dmnsn_pigment_evaluate(background, r);
-  } else {
-    state->color = background->quick_color;
-  }
-}
-
-static void
-dmnsn_trace_pigment(dmnsn_rtstate *state)
-{
-  dmnsn_pigment *pigment = state->texture->pigment;
-  if (state->scene->quality & DMNSN_RENDER_PIGMENT) {
-    state->pigment = dmnsn_pigment_evaluate(pigment, state->pigment_r);
-  } else {
-    state->pigment = pigment->quick_color;
-  }
-  state->color = state->pigment;
-}
-
-/// Determine the amount of specular highlight.
-static inline dmnsn_color
-dmnsn_evaluate_specular(const dmnsn_rtstate *state)
-{
-  const dmnsn_finish *finish = &state->texture->finish;
-  if (finish->specular) {
-    return finish->specular->specular_fn(
-      finish->specular, state->light_color, state->pigment.c,
-      state->light_ray, state->intersection->normal, state->viewer
-    );
-  } else {
-    return dmnsn_black;
-  }
-}
-
-/// Determine the amount of reflected light.
-static inline dmnsn_color
-dmnsn_evaluate_reflection(const dmnsn_rtstate *state,
-                          dmnsn_color light, dmnsn_vector direction)
-{
-  const dmnsn_reflection *reflection = state->texture->finish.reflection;
-  if (reflection && (state->scene->quality & DMNSN_RENDER_REFLECTION)) {
-    return reflection->reflection_fn(
-      reflection, light, state->pigment.c, direction,
-      state->intersection->normal
-    );
-  } else {
-    return dmnsn_black;
-  }
-}
-
-/// Determine the amount of transmitted light.
-static inline dmnsn_color
-dmnsn_evaluate_transparency(const dmnsn_rtstate *state, dmnsn_color light)
-{
-  if (state->pigment.T >= dmnsn_epsilon
-      && (state->scene->quality & DMNSN_RENDER_TRANSPARENCY))
-  {
-    return dmnsn_tcolor_filter(light, state->pigment);
-  } else {
-    return dmnsn_black;
-  }
-}
-
-/// Get a light's diffuse contribution to the object
-static inline dmnsn_color
-dmnsn_evaluate_diffuse(const dmnsn_rtstate *state)
-{
-  const dmnsn_finish *finish = &state->texture->finish;
-  if (finish->diffuse) {
-    return finish->diffuse->diffuse_fn(
-      finish->diffuse, state->light_color, state->pigment.c,
-      state->light_ray, state->intersection->normal
-    );
-  } else {
-    return dmnsn_black;
-  }
-}
-
-/// Get the color of a light ray at an intersection point.
-static bool
-dmnsn_trace_light_ray(dmnsn_rtstate *state, const dmnsn_light *light)
-{
-  dmnsn_line shadow_ray = dmnsn_new_line(
-    state->r,
-    light->direction_fn(light, state->r)
-  );
-  // Add epsilon to avoid hitting ourselves with the shadow ray
-  shadow_ray = dmnsn_line_add_epsilon(shadow_ray);
-
-  // Check if we're casting a shadow on ourself
-  if ((dmnsn_vector_dot(shadow_ray.n, state->intersection->normal)
-       * dmnsn_vector_dot(state->viewer, state->intersection->normal) < 0.0)
-      && (!state->is_shadow_ray || state->pigment.T < dmnsn_epsilon))
-  {
-    return false;
-  }
-
-  state->light_ray = dmnsn_vector_normalized(shadow_ray.n);
-  state->light_color = light->illumination_fn(light, state->r);
-
-  // Test for shadow ray intersections
-  dmnsn_intersection shadow_caster;
-  bool in_shadow = dmnsn_bvh_intersection(state->bvh, shadow_ray,
-                                          &shadow_caster, false);
-  if (!in_shadow || !light->shadow_fn(light, shadow_caster.t)) {
-    return true;
-  }
-
-  if (state->reclevel > 0
-      && dmnsn_color_intensity(state->adc_value) >= state->scene->adc_bailout
-      && (state->scene->quality & DMNSN_RENDER_TRANSPARENCY)) {
-    dmnsn_rtstate shadow_state = *state;
-    dmnsn_rtstate_initialize(&shadow_state, &shadow_caster);
-    dmnsn_trace_pigment(&shadow_state);
-
-    if (shadow_state.pigment.T >= dmnsn_epsilon) {
-      --shadow_state.reclevel;
-      shadow_state.adc_value = dmnsn_evaluate_transparency(
-        &shadow_state, shadow_state.adc_value
-      );
-      shadow_state.is_shadow_ray = true;
-      if (dmnsn_trace_light_ray(&shadow_state, light)) {
-        state->light_color = shadow_state.light_color;
-
-        // Handle reflection
-        dmnsn_color reflected = dmnsn_evaluate_reflection(
-          &shadow_state, state->light_color, state->light_ray
-        );
-        state->light_color = dmnsn_color_sub(state->light_color, reflected);
-
-        // Handle transparency
-        state->light_color = dmnsn_evaluate_transparency(
-          &shadow_state, state->light_color
-        );
-
-        return true;
-      }
-    }
-  }
-
-  return false;
-}
-
-static void
-dmnsn_trace_lighting(dmnsn_rtstate *state)
-{
-  // Calculate the ambient color
-  state->color = DMNSN_TCOLOR(dmnsn_black);
-  const dmnsn_finish *finish = &state->texture->finish;
-  if (finish->ambient) {
-    dmnsn_color ambient = finish->ambient->ambient;
-
-    // Handle reflection and transmittance of the ambient light
-    dmnsn_color reflected = dmnsn_evaluate_reflection(
-      state, ambient, state->intersection->normal
-    );
-    ambient = dmnsn_color_sub(ambient, reflected);
-    dmnsn_color transmitted = dmnsn_evaluate_transparency(state, ambient);
-    ambient = dmnsn_color_sub(ambient, transmitted);
-
-    state->color.c = dmnsn_color_illuminate(ambient, state->pigment.c);
-  }
-
-  // Iterate over each light
-  DMNSN_ARRAY_FOREACH (dmnsn_light **, light, state->scene->lights) {
-    if (dmnsn_trace_light_ray(state, *light)) {
-      if (state->scene->quality & DMNSN_RENDER_FINISH) {
-        dmnsn_color specular = dmnsn_evaluate_specular(state);
-        state->light_color = dmnsn_color_sub(state->light_color, specular);
-
-        dmnsn_color reflected = dmnsn_evaluate_reflection(
-          state, state->light_color, state->reflected
-        );
-        state->light_color = dmnsn_color_sub(state->light_color, reflected);
-
-        dmnsn_color transmitted = dmnsn_evaluate_transparency(
-          state, state->light_color
-        );
-        state->light_color = dmnsn_color_sub(state->light_color, transmitted);
-
-        dmnsn_color diffuse = dmnsn_evaluate_diffuse(state);
-
-        state->color.c = dmnsn_color_add(state->color.c, specular);
-        state->color.c = dmnsn_color_add(state->color.c, diffuse);
-      } else {
-        state->color.c = state->pigment.c;
-        break;
-      }
-    }
-  }
-}
-
-static void
-dmnsn_trace_reflection(dmnsn_rtstate *state)
-{
-  const dmnsn_reflection *reflection = state->texture->finish.reflection;
-  if (reflection) {
-    dmnsn_line refl_ray = dmnsn_new_line(state->r, state->reflected);
-    refl_ray = dmnsn_line_add_epsilon(refl_ray);
-
-    dmnsn_rtstate recursive_state = *state;
-
-    // Calculate ADC value
-    recursive_state.adc_value = dmnsn_evaluate_reflection(
-      state, state->adc_value, state->reflected
-    );
-
-    // Shoot the reflected ray
-    dmnsn_color rec = dmnsn_ray_shoot(&recursive_state, refl_ray).c;
-    dmnsn_color reflected = dmnsn_evaluate_reflection(
-      state, rec, state->reflected
-    );
-
-    state->color.c = dmnsn_color_add(state->color.c, reflected);
-  }
-}
-
-static void
-dmnsn_trace_transparency(dmnsn_rtstate *state)
-{
-  if (state->pigment.T >= dmnsn_epsilon) {
-    const dmnsn_interior *interior = state->interior;
-
-    dmnsn_line trans_ray = dmnsn_new_line(state->r, state->intersection->ray.n);
-    trans_ray = dmnsn_line_add_epsilon(trans_ray);
-
-    dmnsn_vector r = dmnsn_vector_normalized(trans_ray.n);
-    dmnsn_vector n = state->intersection->normal;
-
-    dmnsn_rtstate recursive_state = *state;
-
-    // Calculate new refractive index
-    if (dmnsn_vector_dot(r, n) < 0.0) {
-      // We are entering an object
-      recursive_state.ior = interior->ior;
-      recursive_state.parent = state;
-    } else {
-      // We are leaving an object
-      recursive_state.ior = state->parent ? state->parent->ior : 1.0;
-      recursive_state.parent = state->parent ? state->parent->parent : NULL;
-    }
-
-    // Calculate transmitted ray direction
-    double iorr = state->ior/recursive_state.ior; // ior ratio
-    double c1 = -dmnsn_vector_dot(r, n);
-    double c2 = 1.0 - iorr*iorr*(1.0 - c1*c1);
-    if (c2 <= 0.0) {
-      // Total internal reflection
-      return;
-    }
-    c2 = sqrt(c2);
-    if (c1 >= 0.0) {
-      trans_ray.n = dmnsn_vector_add(
-        dmnsn_vector_mul(iorr, r),
-        dmnsn_vector_mul(iorr*c1 - c2, n)
-      );
-    } else {
-      trans_ray.n = dmnsn_vector_add(
-        dmnsn_vector_mul(iorr, r),
-        dmnsn_vector_mul(iorr*c1 + c2, n)
-      );
-    }
-
-    // Calculate ADC value
-    recursive_state.adc_value = dmnsn_evaluate_transparency(
-      state, state->adc_value
-    );
-    dmnsn_color adc_reflected = dmnsn_evaluate_reflection(
-      state, recursive_state.adc_value, state->reflected
-    );
-    recursive_state.adc_value = dmnsn_color_sub(
-      recursive_state.adc_value, adc_reflected
-    );
-
-    // Shoot the transmitted ray
-    dmnsn_color rec = dmnsn_ray_shoot(&recursive_state, trans_ray).c;
-    dmnsn_color filtered = dmnsn_evaluate_transparency(state, rec);
-
-    // Conserve energy
-    dmnsn_color reflected = dmnsn_evaluate_reflection(
-      state, filtered, state->reflected
-    );
-    filtered = dmnsn_color_sub(filtered, reflected);
-
-    state->color.c = dmnsn_color_add(state->color.c, filtered);
-  }
-}
diff --git a/libdimension/reflection.c b/libdimension/reflection.c
deleted file mode 100644
index 6765bef..0000000
--- a/libdimension/reflection.c
+++ /dev/null
@@ -1,64 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Reflective finish.
- */
-
-#include "dimension.h"
-#include <math.h>
-#include <stdlib.h>
-
-/// Basic reflective finish type.
-typedef struct dmnsn_basic_reflection {
-  dmnsn_reflection reflection;
-  dmnsn_color min, max;
-  double falloff;
-} dmnsn_basic_reflection;
-
-/// Reflective finish callback.
-static dmnsn_color
-dmnsn_basic_reflection_fn(const dmnsn_reflection *reflection,
-                          dmnsn_color reflect, dmnsn_color color,
-                          dmnsn_vector ray, dmnsn_vector normal)
-{
-  const dmnsn_basic_reflection *basic = (const dmnsn_basic_reflection *)reflection;
-  double coeff = pow(fabs(dmnsn_vector_dot(ray, normal)), basic->falloff);
-
-  return dmnsn_color_illuminate(
-    dmnsn_color_gradient(basic->min, basic->max, coeff),
-    reflect
-  );
-}
-
-dmnsn_reflection *
-dmnsn_new_basic_reflection(dmnsn_pool *pool, dmnsn_color min, dmnsn_color max, double falloff)
-{
-  dmnsn_basic_reflection *basic = DMNSN_PALLOC(pool, dmnsn_basic_reflection);
-  basic->min = min;
-  basic->max = max;
-  basic->falloff = falloff;
-
-  dmnsn_reflection *reflection = &basic->reflection;
-  dmnsn_init_reflection(reflection);
-  reflection->reflection_fn = dmnsn_basic_reflection_fn;
-  return reflection;
-}
diff --git a/libdimension/render/render.c b/libdimension/render/render.c
new file mode 100644
index 0000000..842b41e
--- /dev/null
+++ b/libdimension/render/render.c
@@ -0,0 +1,548 @@
+/*************************************************************************
+ * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
+ *                                                                       *
+ * This file is part of The Dimension Library.                           *
+ *                                                                       *
+ * The Dimension Library is free software; you can redistribute it and/  *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version.                       *
+ *                                                                       *
+ * The Dimension Library is distributed in the hope that it will be      *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
+ * Lesser General Public License for more details.                       *
+ *                                                                       *
+ * You should have received a copy of the GNU Lesser General Public      *
+ * License along with this program.  If not, see                         *
+ * <http://www.gnu.org/licenses/>.                                       *
+ *************************************************************************/
+
+/**
+ * @file
+ * The ray-tracing algorithm.
+ */
+
+#include "internal/bvh.h"
+#include "internal/concurrency.h"
+#include "dimension/render.h"
+#include <stdlib.h>
+
+////////////////////////////////////
+// Boilerplate for multithreading //
+////////////////////////////////////
+
+/// Payload type for passing arguments to worker threads.
+typedef struct {
+  dmnsn_future *future;
+  dmnsn_scene *scene;
+  dmnsn_bvh *bvh;
+} dmnsn_render_payload;
+
+// Ray-trace a scene
+void
+dmnsn_render(dmnsn_scene *scene)
+{
+  dmnsn_future *future = dmnsn_render_async(scene);
+  if (dmnsn_future_join(future) != 0) {
+    dmnsn_error("Error occured while ray-tracing.");
+  }
+}
+
+/// Background thread callback.
+static int dmnsn_render_scene_thread(void *ptr);
+
+// Ray-trace a scene in the background
+dmnsn_future *
+dmnsn_render_async(dmnsn_scene *scene)
+{
+  dmnsn_future *future = dmnsn_new_future();
+
+  dmnsn_render_payload *payload = DMNSN_MALLOC(dmnsn_render_payload);
+  payload->future = future;
+  payload->scene  = scene;
+
+  dmnsn_new_thread(future, dmnsn_render_scene_thread, payload);
+
+  return future;
+}
+
+/// Worker thread callback.
+static int dmnsn_render_scene_concurrent(void *ptr, unsigned int thread,
+                                            unsigned int nthreads);
+
+// Thread callback -- set up the multithreaded engine
+static int
+dmnsn_render_scene_thread(void *ptr)
+{
+  dmnsn_render_payload *payload = ptr;
+
+  // Pre-calculate bounding box transformations, etc.
+  dmnsn_scene_initialize(payload->scene);
+
+  // Time the bounding tree construction
+  dmnsn_timer_start(&payload->scene->bounding_timer);
+    payload->bvh = dmnsn_new_bvh(payload->scene->objects, DMNSN_BVH_PRTREE);
+  dmnsn_timer_stop(&payload->scene->bounding_timer);
+
+  // Set up the future object
+  dmnsn_future_set_total(payload->future, payload->scene->canvas->height);
+
+  // Time the render itself
+  dmnsn_timer_start(&payload->scene->render_timer);
+    int ret = dmnsn_execute_concurrently(payload->future,
+                                         dmnsn_render_scene_concurrent,
+                                         payload, payload->scene->nthreads);
+  dmnsn_timer_stop(&payload->scene->render_timer);
+
+  dmnsn_delete_bvh(payload->bvh);
+  dmnsn_free(payload);
+
+  return ret;
+}
+
+///////////////////////////
+// Ray-tracing algorithm //
+///////////////////////////
+
+/// The current state of the ray-tracing engine.
+typedef struct dmnsn_rtstate {
+  const struct dmnsn_rtstate *parent;
+
+  const dmnsn_scene *scene;
+  const dmnsn_intersection *intersection;
+  const dmnsn_texture *texture;
+  const dmnsn_interior *interior;
+  const dmnsn_bvh *bvh;
+  unsigned int reclevel;
+
+  dmnsn_vector r;
+  dmnsn_vector pigment_r;
+  dmnsn_vector viewer;
+  dmnsn_vector reflected;
+
+  bool is_shadow_ray;
+  dmnsn_vector light_ray;
+  dmnsn_color light_color;
+
+  dmnsn_tcolor pigment;
+  dmnsn_tcolor color;
+
+  double ior;
+
+  dmnsn_color adc_value;
+} dmnsn_rtstate;
+
+/// Compute a ray-tracing state from an intersection.
+static inline void
+dmnsn_rtstate_initialize(dmnsn_rtstate *state,
+                         const dmnsn_intersection *intersection);
+/// Main helper for dmnsn_render_scene_concurrent - shoot a ray.
+static dmnsn_tcolor dmnsn_ray_shoot(dmnsn_rtstate *state, dmnsn_ray ray);
+
+// Actually ray-trace a scene
+static int
+dmnsn_render_scene_concurrent(void *ptr, unsigned int thread, unsigned int nthreads)
+{
+  const dmnsn_render_payload *payload = ptr;
+  dmnsn_future *future = payload->future;
+  dmnsn_scene *scene = payload->scene;
+  dmnsn_bvh *bvh = payload->bvh;
+
+  dmnsn_rtstate state = {
+    .parent = NULL,
+    .scene  = scene,
+    .bvh = bvh,
+  };
+
+  // Iterate through each pixel
+  for (size_t y = thread; y < scene->canvas->height; y += nthreads) {
+    for (size_t x = 0; x < scene->canvas->width; ++x) {
+      // Get the ray corresponding to the (x,y)'th pixel
+      dmnsn_ray ray = dmnsn_camera_ray(
+        scene->camera,
+        ((double)(x + scene->region_x))/(scene->outer_width - 1),
+        ((double)(y + scene->region_y))/(scene->outer_height - 1)
+      );
+
+      // Shoot a ray
+      state.reclevel = scene->reclimit;
+      state.ior = 1.0;
+      state.adc_value = dmnsn_white;
+      dmnsn_tcolor tcolor = dmnsn_ray_shoot(&state, ray);
+      dmnsn_canvas_set_pixel(scene->canvas, x, y, tcolor);
+    }
+
+    dmnsn_future_increment(future);
+  }
+
+  return 0;
+}
+
+// Compute rtstate fields
+static inline void
+dmnsn_rtstate_initialize(dmnsn_rtstate *state,
+                         const dmnsn_intersection *intersection)
+{
+  state->intersection = intersection;
+  state->texture      = intersection->object->texture;
+  state->interior     = intersection->object->interior;
+
+  state->r = dmnsn_ray_point(intersection->ray, intersection->t);
+  state->pigment_r = dmnsn_transform_point(
+    intersection->object->pigment_trans,
+    state->r
+  );
+  state->viewer = dmnsn_vector_normalized(
+    dmnsn_vector_negate(intersection->ray.n)
+  );
+  state->reflected = dmnsn_vector_sub(
+    dmnsn_vector_mul(
+      2*dmnsn_vector_dot(state->viewer, intersection->normal),
+      intersection->normal),
+    state->viewer
+  );
+
+  state->is_shadow_ray = false;
+}
+
+/// Calculate the background color.
+static void dmnsn_trace_background(dmnsn_rtstate *state, dmnsn_ray ray);
+/// Calculate the base pigment at the intersection.
+static void dmnsn_trace_pigment(dmnsn_rtstate *state);
+/// Handle light, shadow, and shading.
+static void dmnsn_trace_lighting(dmnsn_rtstate *state);
+/// Trace a reflected ray.
+static void dmnsn_trace_reflection(dmnsn_rtstate *state);
+/// Trace a transmitted ray.
+static void dmnsn_trace_transparency(dmnsn_rtstate *state);
+
+// Shoot a ray, and calculate the color
+static dmnsn_tcolor
+dmnsn_ray_shoot(dmnsn_rtstate *state, dmnsn_ray ray)
+{
+  if (state->reclevel == 0
+      || dmnsn_color_intensity(state->adc_value) < state->scene->adc_bailout)
+  {
+    return DMNSN_TCOLOR(dmnsn_black);
+  }
+
+  --state->reclevel;
+
+  dmnsn_intersection intersection;
+  bool reset = state->reclevel == state->scene->reclimit - 1;
+  dmnsn_bvh_intersection(state->bvh, ray, &intersection, reset);
+  if (dmnsn_bvh_intersection(state->bvh, ray, &intersection, reset)) {
+    // Found an intersection
+    dmnsn_rtstate_initialize(state, &intersection);
+
+    dmnsn_trace_pigment(state);
+    if (state->scene->quality & DMNSN_RENDER_LIGHTS) {
+      dmnsn_trace_lighting(state);
+    }
+    if (state->scene->quality & DMNSN_RENDER_REFLECTION) {
+      dmnsn_trace_reflection(state);
+    }
+    if (state->scene->quality & DMNSN_RENDER_TRANSPARENCY) {
+      dmnsn_trace_transparency(state);
+    }
+  } else {
+    // No intersection, return the background color
+    dmnsn_trace_background(state, ray);
+  }
+
+  return state->color;
+}
+
+static void
+dmnsn_trace_background(dmnsn_rtstate *state, dmnsn_ray ray)
+{
+  dmnsn_pigment *background = state->scene->background;
+  if (state->scene->quality & DMNSN_RENDER_PIGMENT) {
+    dmnsn_vector r = dmnsn_vector_normalized(ray.n);
+    state->color = dmnsn_pigment_evaluate(background, r);
+  } else {
+    state->color = background->quick_color;
+  }
+}
+
+static void
+dmnsn_trace_pigment(dmnsn_rtstate *state)
+{
+  dmnsn_pigment *pigment = state->texture->pigment;
+  if (state->scene->quality & DMNSN_RENDER_PIGMENT) {
+    state->pigment = dmnsn_pigment_evaluate(pigment, state->pigment_r);
+  } else {
+    state->pigment = pigment->quick_color;
+  }
+  state->color = state->pigment;
+}
+
+/// Determine the amount of specular highlight.
+static inline dmnsn_color
+dmnsn_evaluate_specular(const dmnsn_rtstate *state)
+{
+  const dmnsn_finish *finish = &state->texture->finish;
+  if (finish->specular) {
+    return finish->specular->specular_fn(
+      finish->specular, state->light_color, state->pigment.c,
+      state->light_ray, state->intersection->normal, state->viewer
+    );
+  } else {
+    return dmnsn_black;
+  }
+}
+
+/// Determine the amount of reflected light.
+static inline dmnsn_color
+dmnsn_evaluate_reflection(const dmnsn_rtstate *state,
+                          dmnsn_color light, dmnsn_vector direction)
+{
+  const dmnsn_reflection *reflection = state->texture->finish.reflection;
+  if (reflection && (state->scene->quality & DMNSN_RENDER_REFLECTION)) {
+    return reflection->reflection_fn(
+      reflection, light, state->pigment.c, direction,
+      state->intersection->normal
+    );
+  } else {
+    return dmnsn_black;
+  }
+}
+
+/// Determine the amount of transmitted light.
+static inline dmnsn_color
+dmnsn_evaluate_transparency(const dmnsn_rtstate *state, dmnsn_color light)
+{
+  if (state->pigment.T >= dmnsn_epsilon
+      && (state->scene->quality & DMNSN_RENDER_TRANSPARENCY))
+  {
+    return dmnsn_tcolor_filter(light, state->pigment);
+  } else {
+    return dmnsn_black;
+  }
+}
+
+/// Get a light's diffuse contribution to the object
+static inline dmnsn_color
+dmnsn_evaluate_diffuse(const dmnsn_rtstate *state)
+{
+  const dmnsn_finish *finish = &state->texture->finish;
+  if (finish->diffuse) {
+    return finish->diffuse->diffuse_fn(
+      finish->diffuse, state->light_color, state->pigment.c,
+      state->light_ray, state->intersection->normal
+    );
+  } else {
+    return dmnsn_black;
+  }
+}
+
+/// Get the color of a light ray at an intersection point.
+static bool
+dmnsn_trace_light_ray(dmnsn_rtstate *state, const dmnsn_light *light)
+{
+  dmnsn_ray shadow_ray = dmnsn_new_ray(
+    state->r,
+    light->direction_fn(light, state->r)
+  );
+  // Add epsilon to avoid hitting ourselves with the shadow ray
+  shadow_ray = dmnsn_ray_add_epsilon(shadow_ray);
+
+  // Check if we're casting a shadow on ourself
+  if ((dmnsn_vector_dot(shadow_ray.n, state->intersection->normal)
+       * dmnsn_vector_dot(state->viewer, state->intersection->normal) < 0.0)
+      && (!state->is_shadow_ray || state->pigment.T < dmnsn_epsilon))
+  {
+    return false;
+  }
+
+  state->light_ray = dmnsn_vector_normalized(shadow_ray.n);
+  state->light_color = light->illumination_fn(light, state->r);
+
+  // Test for shadow ray intersections
+  dmnsn_intersection shadow_caster;
+  bool in_shadow = dmnsn_bvh_intersection(state->bvh, shadow_ray,
+                                          &shadow_caster, false);
+  if (!in_shadow || !light->shadow_fn(light, shadow_caster.t)) {
+    return true;
+  }
+
+  if (state->reclevel > 0
+      && dmnsn_color_intensity(state->adc_value) >= state->scene->adc_bailout
+      && (state->scene->quality & DMNSN_RENDER_TRANSPARENCY)) {
+    dmnsn_rtstate shadow_state = *state;
+    dmnsn_rtstate_initialize(&shadow_state, &shadow_caster);
+    dmnsn_trace_pigment(&shadow_state);
+
+    if (shadow_state.pigment.T >= dmnsn_epsilon) {
+      --shadow_state.reclevel;
+      shadow_state.adc_value = dmnsn_evaluate_transparency(
+        &shadow_state, shadow_state.adc_value
+      );
+      shadow_state.is_shadow_ray = true;
+      if (dmnsn_trace_light_ray(&shadow_state, light)) {
+        state->light_color = shadow_state.light_color;
+
+        // Handle reflection
+        dmnsn_color reflected = dmnsn_evaluate_reflection(
+          &shadow_state, state->light_color, state->light_ray
+        );
+        state->light_color = dmnsn_color_sub(state->light_color, reflected);
+
+        // Handle transparency
+        state->light_color = dmnsn_evaluate_transparency(
+          &shadow_state, state->light_color
+        );
+
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+static void
+dmnsn_trace_lighting(dmnsn_rtstate *state)
+{
+  // Calculate the ambient color
+  state->color = DMNSN_TCOLOR(dmnsn_black);
+  const dmnsn_finish *finish = &state->texture->finish;
+  if (finish->ambient) {
+    dmnsn_color ambient = finish->ambient->ambient;
+
+    // Handle reflection and transmittance of the ambient light
+    dmnsn_color reflected = dmnsn_evaluate_reflection(
+      state, ambient, state->intersection->normal
+    );
+    ambient = dmnsn_color_sub(ambient, reflected);
+    dmnsn_color transmitted = dmnsn_evaluate_transparency(state, ambient);
+    ambient = dmnsn_color_sub(ambient, transmitted);
+
+    state->color.c = dmnsn_color_illuminate(ambient, state->pigment.c);
+  }
+
+  // Iterate over each light
+  DMNSN_ARRAY_FOREACH (dmnsn_light **, light, state->scene->lights) {
+    if (dmnsn_trace_light_ray(state, *light)) {
+      if (state->scene->quality & DMNSN_RENDER_FINISH) {
+        dmnsn_color specular = dmnsn_evaluate_specular(state);
+        state->light_color = dmnsn_color_sub(state->light_color, specular);
+
+        dmnsn_color reflected = dmnsn_evaluate_reflection(
+          state, state->light_color, state->reflected
+        );
+        state->light_color = dmnsn_color_sub(state->light_color, reflected);
+
+        dmnsn_color transmitted = dmnsn_evaluate_transparency(
+          state, state->light_color
+        );
+        state->light_color = dmnsn_color_sub(state->light_color, transmitted);
+
+        dmnsn_color diffuse = dmnsn_evaluate_diffuse(state);
+
+        state->color.c = dmnsn_color_add(state->color.c, specular);
+        state->color.c = dmnsn_color_add(state->color.c, diffuse);
+      } else {
+        state->color.c = state->pigment.c;
+        break;
+      }
+    }
+  }
+}
+
+static void
+dmnsn_trace_reflection(dmnsn_rtstate *state)
+{
+  const dmnsn_reflection *reflection = state->texture->finish.reflection;
+  if (reflection) {
+    dmnsn_ray refl_ray = dmnsn_new_ray(state->r, state->reflected);
+    refl_ray = dmnsn_ray_add_epsilon(refl_ray);
+
+    dmnsn_rtstate recursive_state = *state;
+
+    // Calculate ADC value
+    recursive_state.adc_value = dmnsn_evaluate_reflection(
+      state, state->adc_value, state->reflected
+    );
+
+    // Shoot the reflected ray
+    dmnsn_color rec = dmnsn_ray_shoot(&recursive_state, refl_ray).c;
+    dmnsn_color reflected = dmnsn_evaluate_reflection(
+      state, rec, state->reflected
+    );
+
+    state->color.c = dmnsn_color_add(state->color.c, reflected);
+  }
+}
+
+static void
+dmnsn_trace_transparency(dmnsn_rtstate *state)
+{
+  if (state->pigment.T >= dmnsn_epsilon) {
+    const dmnsn_interior *interior = state->interior;
+
+    dmnsn_ray trans_ray = dmnsn_new_ray(state->r, state->intersection->ray.n);
+    trans_ray = dmnsn_ray_add_epsilon(trans_ray);
+
+    dmnsn_vector r = dmnsn_vector_normalized(trans_ray.n);
+    dmnsn_vector n = state->intersection->normal;
+
+    dmnsn_rtstate recursive_state = *state;
+
+    // Calculate new refractive index
+    if (dmnsn_vector_dot(r, n) < 0.0) {
+      // We are entering an object
+      recursive_state.ior = interior->ior;
+      recursive_state.parent = state;
+    } else {
+      // We are leaving an object
+      recursive_state.ior = state->parent ? state->parent->ior : 1.0;
+      recursive_state.parent = state->parent ? state->parent->parent : NULL;
+    }
+
+    // Calculate transmitted ray direction
+    double iorr = state->ior/recursive_state.ior; // ior ratio
+    double c1 = -dmnsn_vector_dot(r, n);
+    double c2 = 1.0 - iorr*iorr*(1.0 - c1*c1);
+    if (c2 <= 0.0) {
+      // Total internal reflection
+      return;
+    }
+    c2 = sqrt(c2);
+    if (c1 >= 0.0) {
+      trans_ray.n = dmnsn_vector_add(
+        dmnsn_vector_mul(iorr, r),
+        dmnsn_vector_mul(iorr*c1 - c2, n)
+      );
+    } else {
+      trans_ray.n = dmnsn_vector_add(
+        dmnsn_vector_mul(iorr, r),
+        dmnsn_vector_mul(iorr*c1 + c2, n)
+      );
+    }
+
+    // Calculate ADC value
+    recursive_state.adc_value = dmnsn_evaluate_transparency(
+      state, state->adc_value
+    );
+    dmnsn_color adc_reflected = dmnsn_evaluate_reflection(
+      state, recursive_state.adc_value, state->reflected
+    );
+    recursive_state.adc_value = dmnsn_color_sub(
+      recursive_state.adc_value, adc_reflected
+    );
+
+    // Shoot the transmitted ray
+    dmnsn_color rec = dmnsn_ray_shoot(&recursive_state, trans_ray).c;
+    dmnsn_color filtered = dmnsn_evaluate_transparency(state, rec);
+
+    // Conserve energy
+    dmnsn_color reflected = dmnsn_evaluate_reflection(
+      state, filtered, state->reflected
+    );
+    filtered = dmnsn_color_sub(filtered, reflected);
+
+    state->color.c = dmnsn_color_add(state->color.c, filtered);
+  }
+}
diff --git a/libdimension/rgba.c b/libdimension/rgba.c
deleted file mode 100644
index 9889189..0000000
--- a/libdimension/rgba.c
+++ /dev/null
@@ -1,95 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * 16-bit RGBA canvas optimizer.
- */
-
-#include "dimension-internal.h"
-#include <stdint.h>
-
-void
-dmnsn_rgba8_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
-{
-  if (dmnsn_canvas_find_optimizer(canvas, dmnsn_rgba8_optimizer_fn)) {
-    return;
-  }
-
-  size_t ndata = 4*canvas->width*canvas->height;
-  dmnsn_rgba8_optimizer *rgba8 = dmnsn_palloc(pool, sizeof(dmnsn_rgba8_optimizer) + ndata*sizeof(uint8_t));
-
-  dmnsn_canvas_optimizer *optimizer = &rgba8->optimizer;
-  dmnsn_init_canvas_optimizer(optimizer);
-  optimizer->optimizer_fn = dmnsn_rgba8_optimizer_fn;
-
-  dmnsn_canvas_optimize(canvas, optimizer);
-}
-
-void
-dmnsn_rgba16_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas)
-{
-  if (dmnsn_canvas_find_optimizer(canvas, dmnsn_rgba16_optimizer_fn)) {
-    return;
-  }
-
-  size_t ndata = 4*canvas->width*canvas->height;
-  dmnsn_rgba16_optimizer *rgba16 = dmnsn_palloc(pool, sizeof(dmnsn_rgba16_optimizer) + ndata*sizeof(uint16_t));
-
-  dmnsn_canvas_optimizer *optimizer = &rgba16->optimizer;
-  dmnsn_init_canvas_optimizer(optimizer);
-  optimizer->optimizer_fn = dmnsn_rgba16_optimizer_fn;
-
-  dmnsn_canvas_optimize(canvas, optimizer);
-}
-
-void
-dmnsn_rgba8_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y)
-{
-  dmnsn_rgba8_optimizer *rgba8 = (dmnsn_rgba8_optimizer *)optimizer;
-
-  uint8_t *pixel = rgba8->data + 4*(y*canvas->width + x);
-  dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(canvas, x, y);
-  tcolor = dmnsn_tcolor_remove_filter(tcolor);
-  tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
-  tcolor = dmnsn_tcolor_clamp(tcolor);
-
-  pixel[0] = lround(tcolor.c.R*UINT8_MAX);
-  pixel[1] = lround(tcolor.c.G*UINT8_MAX);
-  pixel[2] = lround(tcolor.c.B*UINT8_MAX);
-  pixel[3] = lround(tcolor.T*UINT8_MAX);
-}
-
-void
-dmnsn_rgba16_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y)
-{
-  dmnsn_rgba16_optimizer *rgba16 = (dmnsn_rgba16_optimizer *)optimizer;
-
-  uint16_t *pixel = rgba16->data + 4*(y*canvas->width + x);
-  dmnsn_tcolor tcolor = dmnsn_canvas_get_pixel(canvas, x, y);
-  tcolor = dmnsn_tcolor_remove_filter(tcolor);
-  tcolor.c = dmnsn_color_to_sRGB(tcolor.c);
-  tcolor = dmnsn_tcolor_clamp(tcolor);
-
-  pixel[0] = lround(tcolor.c.R*UINT16_MAX);
-  pixel[1] = lround(tcolor.c.G*UINT16_MAX);
-  pixel[2] = lround(tcolor.c.B*UINT16_MAX);
-  pixel[3] = lround(tcolor.T*UINT16_MAX);
-}
diff --git a/libdimension/rgba.h b/libdimension/rgba.h
deleted file mode 100644
index b43ca3f..0000000
--- a/libdimension/rgba.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * RGBA canvas optimizer interface, used by image optimizers.
- */
-
-#include <stdint.h>
-
-/// RGBA8 optimizer type.
-typedef struct dmnsn_rgba8_optimizer {
-  dmnsn_canvas_optimizer optimizer;
-  uint8_t data[];
-} dmnsn_rgba8_optimizer;
-
-/// RGBA16 optimizer type.
-typedef struct dmnsn_rgba16_optimizer {
-  dmnsn_canvas_optimizer optimizer;
-  uint16_t data[];
-} dmnsn_rgba16_optimizer;
-
-/// Apply the RGBA8 optimizer to a canvas.
-DMNSN_INTERNAL void dmnsn_rgba8_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
-/// Apply the RGBA16 optimizer to a canvas.
-DMNSN_INTERNAL void dmnsn_rgba16_optimize_canvas(dmnsn_pool *pool, dmnsn_canvas *canvas);
-
-/// RGBA8 optimizer callback.
-DMNSN_INTERNAL void dmnsn_rgba8_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y);
-/// RGBA16 optimizer callback.
-DMNSN_INTERNAL void dmnsn_rgba16_optimizer_fn(dmnsn_canvas_optimizer *optimizer, const dmnsn_canvas *canvas, size_t x, size_t y);
diff --git a/libdimension/scene.c b/libdimension/scene.c
deleted file mode 100644
index 6c9c495..0000000
--- a/libdimension/scene.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Scenes.
- */
-
-#include "dimension-internal.h"
-#include <stdlib.h>
-
-// Allocate an empty scene
-dmnsn_scene *
-dmnsn_new_scene(dmnsn_pool *pool)
-{
-  dmnsn_scene *scene = DMNSN_PALLOC(pool, dmnsn_scene);
-
-  scene->background       = NULL;
-  scene->default_texture  = dmnsn_new_texture(pool);
-  scene->default_interior = dmnsn_new_interior(pool);
-  scene->canvas           = NULL;
-  scene->region_x         = 0;
-  scene->region_y         = 0;
-  scene->outer_width      = 0;
-  scene->outer_height     = 0;
-  scene->objects          = DMNSN_PALLOC_ARRAY(pool, dmnsn_object *);
-  scene->lights           = DMNSN_PALLOC_ARRAY(pool, dmnsn_light *);
-  scene->camera           = NULL;
-  scene->quality          = DMNSN_RENDER_FULL;
-  scene->reclimit         = 5;
-  scene->adc_bailout      = 1.0/255.0;
-  scene->nthreads         = dmnsn_ncpus();
-  scene->initialized      = false;
-
-  return scene;
-}
-
-void
-dmnsn_scene_initialize(dmnsn_scene *scene)
-{
-  dmnsn_assert(!scene->initialized, "Scene double-initialized.");
-  scene->initialized = true;
-
-  if (scene->outer_width == 0) {
-    scene->outer_width = scene->canvas->width;
-  }
-  if (scene->outer_height == 0) {
-    scene->outer_height = scene->canvas->height;
-  }
-
-  dmnsn_pigment_initialize(scene->background);
-
-  dmnsn_texture_initialize(scene->default_texture);
-
-  DMNSN_ARRAY_FOREACH (dmnsn_object **, object, scene->objects) {
-    dmnsn_texture_cascade(scene->default_texture, &(*object)->texture);
-    dmnsn_interior_cascade(scene->default_interior, &(*object)->interior);
-    dmnsn_object_precompute(*object);
-  }
-}
diff --git a/libdimension/solid_pigment.c b/libdimension/solid_pigment.c
deleted file mode 100644
index 8c94e1c..0000000
--- a/libdimension/solid_pigment.c
+++ /dev/null
@@ -1,36 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Solid color pigments.
- */
-
-#include "dimension.h"
-#include <stdlib.h>
-
-// Create a solid color
-dmnsn_pigment *
-dmnsn_new_solid_pigment(dmnsn_pool *pool, dmnsn_tcolor color)
-{
-  dmnsn_pigment *pigment = dmnsn_new_pigment(pool);
-  pigment->quick_color = color;
-  return pigment;
-}
diff --git a/libdimension/sphere.c b/libdimension/sphere.c
deleted file mode 100644
index d8fae05..0000000
--- a/libdimension/sphere.c
+++ /dev/null
@@ -1,114 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Spheres.
- */
-
-#include "dimension-internal.h"
-
-/// Sphere intersection callback.
-static bool
-dmnsn_sphere_intersection_fn(const dmnsn_object *sphere, dmnsn_line l,
-                             dmnsn_intersection *intersection)
-{
-  // Solve (x0 + nx*t)^2 + (y0 + ny*t)^2 + (z0 + nz*t)^2 == 1
-  double poly[3], x[2];
-  poly[2] = dmnsn_vector_dot(l.n, l.n);
-  poly[1] = 2.0*dmnsn_vector_dot(l.n, l.x0);
-  poly[0] = dmnsn_vector_dot(l.x0, l.x0) - 1.0;
-
-  size_t n = dmnsn_polynomial_solve(poly, 2, x);
-  if (n == 0) {
-    return false;
-  }
-
-  double t = x[0];
-  // Optimize for the case where we're outside the sphere
-  if (dmnsn_likely(n == 2)) {
-    t = dmnsn_min(t, x[1]);
-  }
-
-  intersection->t = t;
-  intersection->normal = dmnsn_line_point(l, t);
-  return true;
-}
-
-/// Sphere inside callback.
-static bool
-dmnsn_sphere_inside_fn(const dmnsn_object *sphere, dmnsn_vector point)
-{
-  return point.x*point.x + point.y*point.y + point.z*point.z < 1.0;
-}
-
-/// Helper for sphere bounding box calculation.
-static inline double
-dmnsn_implicit_dot(double row[4])
-{
-  double ret = 0.0;
-  for (int i = 0; i < 3; ++i) {
-    ret += row[i]*row[i];
-  }
-  return ret;
-}
-
-/// Sphere bounding callback.
-static dmnsn_bounding_box
-dmnsn_sphere_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  // Get a tight bound using the quadric representation of a sphere.  For
-  // details, see
-  // http://tavianator.com/2014/06/exact-bounding-boxes-for-spheres-ellipsoids
-
-  dmnsn_bounding_box box;
-
-  double cx = trans.n[0][3];
-  double dx = sqrt(dmnsn_implicit_dot(trans.n[0]));
-  box.min.x = cx - dx;
-  box.max.x = cx + dx;
-
-  double cy = trans.n[1][3];
-  double dy = sqrt(dmnsn_implicit_dot(trans.n[1]));
-  box.min.y = cy - dy;
-  box.max.y = cy + dy;
-
-  double cz = trans.n[2][3];
-  double dz = sqrt(dmnsn_implicit_dot(trans.n[2]));
-  box.min.z = cz - dz;
-  box.max.z = cz + dz;
-
-  return box;
-}
-
-/// Sphere vtable.
-static const dmnsn_object_vtable dmnsn_sphere_vtable = {
-  .intersection_fn = dmnsn_sphere_intersection_fn,
-  .inside_fn = dmnsn_sphere_inside_fn,
-  .bounding_fn = dmnsn_sphere_bounding_fn,
-};
-
-dmnsn_object *
-dmnsn_new_sphere(dmnsn_pool *pool)
-{
-  dmnsn_object *sphere = dmnsn_new_object(pool);
-  sphere->vtable = &dmnsn_sphere_vtable;
-  return sphere;
-}
diff --git a/libdimension/tests/future.c b/libdimension/tests/future.c
index ee2511a..a223a44 100644
--- a/libdimension/tests/future.c
+++ b/libdimension/tests/future.c
@@ -22,9 +22,9 @@
  * Tests for dmnsn_future.
  */
 
-#include "../platform.c"
-#include "../future.c"
-#include "../threads.c"
+#include "../platform/platform.c"
+#include "../concurrency/future.c"
+#include "../concurrency/threads.c"
 #include "tests.h"
 #include <stdatomic.h>
 
diff --git a/libdimension/tests/polynomial.c b/libdimension/tests/polynomial.c
index 492c22c..a983bd4 100644
--- a/libdimension/tests/polynomial.c
+++ b/libdimension/tests/polynomial.c
@@ -22,8 +22,8 @@
  * Basic tests of the polynomial root-finder.
  */
 
+#include "../math/polynomial.c"
 #include "tests.h"
-#include "../polynomial.c"
 #include <stdarg.h>
 
 #define DMNSN_CLOSE_ENOUGH 1.0e-6
diff --git a/libdimension/tests/pool.c b/libdimension/tests/pool.c
index 1722d66..6090c36 100644
--- a/libdimension/tests/pool.c
+++ b/libdimension/tests/pool.c
@@ -22,10 +22,10 @@
  * Tests for memory pools.
  */
 
-#include "../dimension-internal.h"
+#include "../concurrency/future.c"
+#include "../concurrency/threads.c"
+#include "../internal.h"
 #include "tests.h"
-#include "../future.c"
-#include "../threads.c"
 
 static dmnsn_pool *pool;
 
diff --git a/libdimension/tests/prtree.c b/libdimension/tests/prtree.c
index acdd426..8824e93 100644
--- a/libdimension/tests/prtree.c
+++ b/libdimension/tests/prtree.c
@@ -22,28 +22,28 @@
  * Basic tests of PR-trees.
  */
 
-#include "../platform.c"
-#include "../threads.c"
-#include "../future.c"
-#include "../bvh.c"
-#include "../prtree.c"
+#include "../platform/platform.c"
+#include "../concurrency/threads.c"
+#include "../concurrency/future.c"
+#include "../bvh/bvh.c"
+#include "../bvh/prtree.c"
 #include <stdio.h>
 #include <stdlib.h>
 
 unsigned int calls = 0;
 
 static bool
-dmnsn_fake_intersection_fn(const dmnsn_object *object, dmnsn_line line,
+dmnsn_fake_intersection_fn(const dmnsn_object *object, dmnsn_ray ray,
                            dmnsn_intersection *intersection)
 {
-  intersection->t = (object->bounding_box.min.z - line.x0.z)/line.n.z;
+  intersection->t = (object->aabb.min.z - ray.x0.z)/ray.n.z;
   intersection->normal = dmnsn_x;
   ++calls;
   return true;
 }
 
 static void
-dmnsn_randomize_bounding_box(dmnsn_object *object)
+dmnsn_randomize_aabb(dmnsn_object *object)
 {
   dmnsn_vector a, b;
 
@@ -55,8 +55,8 @@ dmnsn_randomize_bounding_box(dmnsn_object *object)
   b.y = 2.0*((double)rand())/RAND_MAX - 1.0;
   b.z = 2.0*((double)rand())/RAND_MAX - 1.0;
 
-  object->bounding_box.min = dmnsn_vector_min(a, b);
-  object->bounding_box.max = dmnsn_vector_max(a, b);
+  object->aabb.min = dmnsn_vector_min(a, b);
+  object->aabb.max = dmnsn_vector_max(a, b);
 }
 
 static const dmnsn_object_vtable dmnsn_fake_vtable = {
@@ -67,7 +67,7 @@ static dmnsn_object *
 dmnsn_new_fake_object(dmnsn_pool *pool)
 {
   dmnsn_object *object = dmnsn_new_object(pool);
-  dmnsn_randomize_bounding_box(object);
+  dmnsn_randomize_aabb(object);
   object->vtable = &dmnsn_fake_vtable;
   object->trans_inv = dmnsn_identity_matrix();
   return object;
@@ -92,7 +92,7 @@ main(void)
   dmnsn_bvh *bvh = dmnsn_new_bvh(objects, DMNSN_BVH_PRTREE);
 
   dmnsn_intersection intersection;
-  dmnsn_line ray = dmnsn_new_line(
+  dmnsn_ray ray = dmnsn_new_ray(
     dmnsn_new_vector(0.0, 0.0, -2.0),
     dmnsn_new_vector(0.0, 0.0, 1.0)
   );
diff --git a/libdimension/tests/render.c b/libdimension/tests/render.c
index 56a80d3..fd1e96d 100644
--- a/libdimension/tests/render.c
+++ b/libdimension/tests/render.c
@@ -350,7 +350,7 @@ main(void)
   // Render the scene
 
   printf("Rendering scene\n");
-  dmnsn_future *future = dmnsn_ray_trace_async(scene);
+  dmnsn_future *future = dmnsn_render_async(scene);
 
   // Display the scene as it's rendered
   if (display) {
diff --git a/libdimension/texture.c b/libdimension/texture.c
deleted file mode 100644
index 515e260..0000000
--- a/libdimension/texture.c
+++ /dev/null
@@ -1,68 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Textures.
- */
-
-#include "dimension-internal.h"
-
-dmnsn_texture *
-dmnsn_new_texture(dmnsn_pool *pool)
-{
-  dmnsn_texture *texture = DMNSN_PALLOC(pool, dmnsn_texture);
-  texture->pigment = NULL;
-  texture->finish = dmnsn_new_finish();
-  texture->trans = dmnsn_identity_matrix();
-  texture->initialized = false;
-  return texture;
-}
-
-void
-dmnsn_texture_initialize(dmnsn_texture *texture)
-{
-  dmnsn_assert(!texture->initialized, "Texture double-initialized.");
-  texture->initialized = true;
-
-  texture->trans_inv = dmnsn_matrix_inverse(texture->trans);
-
-  if (!texture->pigment->initialized) {
-    texture->pigment->trans = dmnsn_matrix_mul(texture->trans,
-                                               texture->pigment->trans);
-    dmnsn_pigment_initialize(texture->pigment);
-  }
-}
-
-void
-dmnsn_texture_cascade(dmnsn_texture *default_texture, dmnsn_texture **texturep)
-{
-  if (!*texturep) {
-    *texturep = default_texture;
-  }
-
-  dmnsn_texture *texture = *texturep;
-
-  if (!texture->pigment) {
-    texture->pigment = default_texture->pigment;
-  }
-
-  dmnsn_finish_cascade(&default_texture->finish, &texture->finish);
-}
diff --git a/libdimension/threads.c b/libdimension/threads.c
deleted file mode 100644
index 1fee01d..0000000
--- a/libdimension/threads.c
+++ /dev/null
@@ -1,324 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Background threading.
- */
-
-#include "dimension-internal.h"
-#include <pthread.h>
-
-/// The payload to pass to the pthread callback.
-typedef struct dmnsn_thread_payload {
-  dmnsn_thread_fn *thread_fn;
-  void *arg;
-  dmnsn_future *future;
-} dmnsn_thread_payload;
-
-/// Clean up after a thread.
-static void
-dmnsn_thread_cleanup(void *arg)
-{
-  dmnsn_thread_payload *payload = arg;
-  dmnsn_future *future = payload->future;
-  dmnsn_free(payload);
-
-  dmnsn_future_finish(future);
-}
-
-/// pthread callback -- call the real thread callback.
-static void *
-dmnsn_thread(void *arg)
-{
-  dmnsn_thread_payload *payload = arg;
-  int *ret;
-
-  pthread_cleanup_push(dmnsn_thread_cleanup, payload);
-    ret  = DMNSN_MALLOC(int);
-    *ret = payload->thread_fn(payload->arg);
-  pthread_cleanup_pop(true);
-  return ret;
-}
-
-void
-dmnsn_new_thread(dmnsn_future *future, dmnsn_thread_fn *thread_fn, void *arg)
-{
-  dmnsn_thread_payload *payload = DMNSN_MALLOC(dmnsn_thread_payload);
-  payload->thread_fn = thread_fn;
-  payload->arg       = arg;
-  payload->future    = future;
-
-  if (pthread_create(&future->thread, NULL, dmnsn_thread, payload) != 0) {
-    dmnsn_error("Couldn't start thread.");
-  }
-}
-
-/// Payload for threads executed by dmnsn_execute_concurrently().
-typedef struct dmnsn_ccthread_payload {
-  dmnsn_future *future;
-  dmnsn_ccthread_fn *ccthread_fn;
-  void *arg;
-  unsigned int thread, nthreads;
-  int ret;
-  bool running;
-} dmnsn_ccthread_payload;
-
-static void *
-dmnsn_concurrent_thread(void *ptr)
-{
-  dmnsn_ccthread_payload *payload = ptr;
-  payload->ret = payload->ccthread_fn(payload->arg, payload->thread,
-                                      payload->nthreads);
-  if (payload->future) {
-    dmnsn_future_finish_thread(payload->future);
-  }
-  return NULL;
-}
-
-typedef struct dmnsn_ccthread_cleanup_payload {
-  dmnsn_future *future;
-  pthread_t *threads;
-  dmnsn_ccthread_payload *payloads;
-  unsigned int nthreads;
-} dmnsn_ccthread_cleanup_payload;
-
-static void
-dmnsn_ccthread_cleanup(void *ptr)
-{
-  dmnsn_ccthread_cleanup_payload *payload = ptr;
-
-  for (unsigned int i = 0; i < payload->nthreads; ++i) {
-    if (payload->payloads[i].running) {
-      pthread_cancel(payload->threads[i]);
-    }
-  }
-
-  for (unsigned int i = 0; i < payload->nthreads; ++i) {
-    if (payload->payloads[i].running) {
-      dmnsn_join_thread(payload->threads[i], NULL);
-    }
-  }
-
-  if (payload->future) {
-    dmnsn_future_set_nthreads(payload->future, 1);
-  }
-}
-
-int
-dmnsn_execute_concurrently(dmnsn_future *future, dmnsn_ccthread_fn *ccthread_fn,
-                           void *arg, unsigned int nthreads)
-{
-  dmnsn_assert(nthreads > 0, "Attempt to execute using 0 concurrent threads.");
-
-  if (future) {
-    dmnsn_future_set_nthreads(future, nthreads);
-  }
-
-  pthread_t threads[nthreads];
-  dmnsn_ccthread_payload payloads[nthreads];
-  for (unsigned int i = 0; i < nthreads; ++i) {
-    payloads[i].running = false;
-  }
-
-  int ret = 0;
-  dmnsn_ccthread_cleanup_payload cleanup_payload = {
-    .future = future,
-    .threads = threads,
-    .payloads = payloads,
-    .nthreads = nthreads,
-  };
-  pthread_cleanup_push(dmnsn_ccthread_cleanup, &cleanup_payload);
-    for (unsigned int i = 0; i < nthreads; ++i) {
-      payloads[i].future      = future;
-      payloads[i].ccthread_fn = ccthread_fn;
-      payloads[i].arg         = arg;
-      payloads[i].thread      = i;
-      payloads[i].nthreads    = nthreads;
-      payloads[i].ret         = -1;
-      if (pthread_create(&threads[i], NULL, dmnsn_concurrent_thread,
-                         &payloads[i]) != 0)
-      {
-        dmnsn_error("Couldn't start worker thread.");
-      }
-      payloads[i].running = true;
-    }
-
-    for (unsigned int i = 0; i < nthreads; ++i) {
-      dmnsn_join_thread(threads[i], NULL);
-      payloads[i].running = false;
-      if (payloads[i].ret != 0) {
-        ret = payloads[i].ret;
-      }
-    }
-  pthread_cleanup_pop(false);
-
-  if (future) {
-    dmnsn_future_set_nthreads(future, 1);
-  }
-
-  return ret;
-}
-
-// pthread wrappers
-
-void
-dmnsn_initialize_mutex(pthread_mutex_t *mutex)
-{
-  if (pthread_mutex_init(mutex, NULL) != 0) {
-    dmnsn_error("Couldn't initialize mutex.");
-  }
-}
-
-void
-dmnsn_lock_mutex_impl(pthread_mutex_t *mutex)
-{
-  if (pthread_mutex_lock(mutex) != 0) {
-    dmnsn_error("Couldn't lock mutex.");
-  }
-}
-
-void
-dmnsn_unlock_mutex_impl(void *mutex)
-{
-  if (pthread_mutex_unlock(mutex) != 0) {
-    dmnsn_error("Couldn't unlock mutex.");
-  }
-}
-
-void
-dmnsn_destroy_mutex(pthread_mutex_t *mutex)
-{
-  if (pthread_mutex_destroy(mutex) != 0) {
-    dmnsn_warning("Couldn't destroy mutex.");
-  }
-}
-
-void
-dmnsn_initialize_rwlock(pthread_rwlock_t *rwlock)
-{
-  if (pthread_rwlock_init(rwlock, NULL) != 0) {
-    dmnsn_error("Couldn't initialize read-write lock.");
-  }
-}
-
-void
-dmnsn_read_lock_impl(pthread_rwlock_t *rwlock)
-{
-  if (pthread_rwlock_rdlock(rwlock) != 0) {
-    dmnsn_error("Couldn't acquire read lock.");
-  }
-}
-
-void
-dmnsn_write_lock_impl(pthread_rwlock_t *rwlock)
-{
-  if (pthread_rwlock_wrlock(rwlock) != 0) {
-    dmnsn_error("Couldn't acquire write lock.");
-  }
-}
-
-void
-dmnsn_unlock_rwlock_impl(pthread_rwlock_t *rwlock)
-{
-  if (pthread_rwlock_unlock(rwlock) != 0) {
-    dmnsn_error("Couldn't unlock read-write lock.");
-  }
-}
-
-void
-dmnsn_destroy_rwlock(pthread_rwlock_t *rwlock)
-{
-  if (pthread_rwlock_destroy(rwlock) != 0) {
-    dmnsn_warning("Couldn't destroy read-write lock.");
-  }
-}
-
-void
-dmnsn_initialize_cond(pthread_cond_t *cond)
-{
-  if (pthread_cond_init(cond, NULL) != 0) {
-    dmnsn_error("Couldn't initialize condition variable.");
-  }
-}
-
-void
-dmnsn_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
-{
-  if (pthread_cond_wait(cond, mutex) != 0) {
-    dmnsn_error("Couldn't wait on condition variable.");
-  }
-}
-
-void
-dmnsn_cond_broadcast(pthread_cond_t *cond)
-{
-  if (pthread_cond_broadcast(cond) != 0) {
-    dmnsn_error("Couldn't signal condition variable.");
-  }
-}
-
-void
-dmnsn_destroy_cond(pthread_cond_t *cond)
-{
-  if (pthread_cond_destroy(cond) != 0) {
-    dmnsn_warning("Couldn't destroy condition variable.");
-  }
-}
-
-void
-dmnsn_once(pthread_once_t *once, dmnsn_once_fn *once_fn)
-{
-  if (pthread_once(once, once_fn) != 0) {
-    dmnsn_error("Couldn't call one-shot function.");
-  }
-}
-
-void
-dmnsn_key_create(pthread_key_t *key, dmnsn_callback_fn *destructor)
-{
-  if (pthread_key_create(key, destructor) != 0) {
-    dmnsn_error("Couldn't initialize thread-specific pointer.");
-  }
-}
-
-void
-dmnsn_setspecific(pthread_key_t key, const void *value)
-{
-  if (pthread_setspecific(key, value) != 0) {
-    dmnsn_error("Couldn't set thread-specific pointer.");
-  }
-}
-
-void
-dmnsn_key_delete(pthread_key_t key)
-{
-  if (pthread_key_delete(key) != 0) {
-    dmnsn_warning("Couldn't destroy thread-specific pointer.");
-  }
-}
-
-void
-dmnsn_join_thread(pthread_t thread, void **retval)
-{
-  if (pthread_join(thread, retval) != 0) {
-    dmnsn_error("Couldn't join thread.");
-  }
-}
diff --git a/libdimension/threads.h b/libdimension/threads.h
deleted file mode 100644
index 839299e..0000000
--- a/libdimension/threads.h
+++ /dev/null
@@ -1,216 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Background threading interface.
- */
-
-#include <pthread.h>
-
-/**
- * Thread callback type.
- * @param[in,out] ptr  An arbitrary pointer.
- * @return 0 on success, non-zero on failure.
- */
-typedef int dmnsn_thread_fn(void *ptr);
-
-/**
- * Create a thread that cleans up after itself on errors.
- * @param[in,out] future     The future object to associate with the thread.
- * @param[in]     thread_fn  The thread callback.
- * @param[in,out] arg        The pointer to pass to the thread callback.
- */
-DMNSN_INTERNAL void dmnsn_new_thread(dmnsn_future *future,
-                                     dmnsn_thread_fn *thread_fn, void *arg);
-
-/**
- * Thread callback type for parallel tasks.
- * @param[in,out] ptr       An arbitrary pointer.
- * @param[in]     thread    An ID for this thread, in [0, \p nthreads).
- * @param[in]     nthreads  The number of concurrent threads.
- * @return 0 on success, non-zero on failure.
- */
-typedef int dmnsn_ccthread_fn(void *ptr, unsigned int thread,
-                              unsigned int nthreads);
-
-/**
- * Run \p nthreads threads in parallel.
- * @param[in,out] future       The future object to associate with the threads,
- *                             possibly NULL.
- * @param[in]     ccthread_fn  The routine to run in each concurrent thread.
- * @param[in,out] arg          The pointer to pass to the thread callbacks.
- * @param[in]     nthreads     The number of concurrent threads to run.
- * @return 0 if all threads were successful, and an error code otherwise.
- */
-DMNSN_INTERNAL int dmnsn_execute_concurrently(dmnsn_future *future,
-                                              dmnsn_ccthread_fn *ccthread_fn,
-                                              void *arg, unsigned int nthreads);
-
-/**
- * Initialize a mutex, bailing out on failure.
- * @param[out] mutex  The mutex to initialize.
- */
-DMNSN_INTERNAL void dmnsn_initialize_mutex(pthread_mutex_t *mutex);
-
-/// dmnsn_lock_mutex() implementation.
-DMNSN_INTERNAL void dmnsn_lock_mutex_impl(pthread_mutex_t *mutex);
-/// dmnsn_unlock_mutex() implementation.
-DMNSN_INTERNAL void dmnsn_unlock_mutex_impl(void *mutex);
-
-/**
- * Lock a mutex, bailing out on failure.
- * Contains a {, so must be used in the same block as dmnsn_unlock_mutex().
- * @param[in,out] mutex  The mutex to lock.
- */
-#define dmnsn_lock_mutex(mutex) do { dmnsn_lock_mutex_impl((mutex))
-
-/**
- * Unlock a mutex, bailing out on failure.
- * Contains a }, so must be used in the same block as dmnsn_lock_mutex().
- * @param[in,out] mutex  The mutex to unlock.
- */
-#define dmnsn_unlock_mutex(mutex) dmnsn_unlock_mutex_impl((mutex)); } while (0)
-
-/**
- * Destroy a mutex, warning on failure.
- * @param[in,out] mutex  The mutex to destroy.
- */
-DMNSN_INTERNAL void dmnsn_destroy_mutex(pthread_mutex_t *mutex);
-
-/**
- * Initialize a read-write lock, bailing out on failure.
- * @param[out] rwlock  The read-write lock to initialize.
- */
-DMNSN_INTERNAL void dmnsn_initialize_rwlock(pthread_rwlock_t *rwlock);
-
-/// dmnsn_read_lock() implementation.
-DMNSN_INTERNAL void dmnsn_read_lock_impl(pthread_rwlock_t *rwlock);
-/// dmnsn_write_lock() implementation.
-DMNSN_INTERNAL void dmnsn_write_lock_impl(pthread_rwlock_t *rwlock);
-/// dmnsn_unlock_rwlock() implementation.
-DMNSN_INTERNAL void dmnsn_unlock_rwlock_impl(pthread_rwlock_t *rwlock);
-
-/**
- * Read-lock a read-write lock, bailing out on failure.
- * Contains a {, so must be used in the same block as dmnsn_unlock_rwlock().
- * @param[in,out] rwlock  The read-write lock to lock.
- */
-#define dmnsn_read_lock(rwlock) do { dmnsn_read_lock_impl((rwlock))
-
-/**
- * Write-lock a read-write lock, bailing out on failure.
- * Contains a {, so must be used in the same block as dmnsn_unlock_rwlock().
- * @param[in,out] rwlock  The read-write lock to lock.
- */
-#define dmnsn_write_lock(rwlock) do { dmnsn_write_lock_impl((rwlock))
-
-/**
- * Unlock a read-write lock, bailing out on failure.
- * Contains a }, so must be used in the same block as dmnsn_read_lock() or
- * dmnsn_write_lock().
- * @param[in,out] rwlock  The read-write lock to lock.
- */
-#define dmnsn_unlock_rwlock(rwlock)                     \
-  dmnsn_unlock_rwlock_impl((rwlock)); } while (0)
-
-/**
- * Destroy a read-write lock, warning on failure.
- * @param[in,out] rwlock  The read-write lock to destroy.
- */
-DMNSN_INTERNAL void dmnsn_destroy_rwlock(pthread_rwlock_t *rwlock);
-
-/**
- * Initialize a condition variable, bailing out on failure.
- * @param[out] cond  The condition variable to initialize.
- */
-DMNSN_INTERNAL void dmnsn_initialize_cond(pthread_cond_t *cond);
-
-/**
- * Wait on a condition variable, bailing out on error.
- * @param[in] cond   The condition variable to wait on.
- * @param[in] mutex  The associated mutex.
- */
-DMNSN_INTERNAL void dmnsn_cond_wait(pthread_cond_t *cond,
-                                    pthread_mutex_t *mutex);
-
-/**
- * Wait on a condition variable, bailing out on error, and unlock the mutex if
- * cancelled.
- * @param[in] cond   The condition variable to wait on.
- * @param[in] mutex  The associated mutex.
- */
-#define dmnsn_cond_wait_safely(cond, mutex)                     \
-  do {                                                          \
-    pthread_cleanup_push(dmnsn_unlock_mutex_impl, (mutex));     \
-    dmnsn_cond_wait((cond), (mutex));                           \
-    pthread_cleanup_pop(false);                                 \
-  } while (0)
-
-/**
- * Signal a condition variable, bailing out on error.
- * @param[in] cond   The condition variable to signal.
- */
-DMNSN_INTERNAL void dmnsn_cond_broadcast(pthread_cond_t *cond);
-
-/**
- * Destroy a condition variable, warning on failure.
- * @param[in,out] cond  The condition variable to destroy.
- */
-DMNSN_INTERNAL void dmnsn_destroy_cond(pthread_cond_t *cond);
-
-/**
- * Once-called callback type.
- */
-typedef void dmnsn_once_fn(void);
-
-/**
- * Call a function exactly once, bailing out on failure.
- * @param[in,out] once     The once control.
- * @param[in]     once_fn  The function to call.
- */
-DMNSN_INTERNAL void dmnsn_once(pthread_once_t *once, dmnsn_once_fn *once_fn);
-
-/**
- * Initialize a thread-local storage key, bailing out on failure.
- * @param[out] key         The key to initialize.
- * @param[in]  destructor  An optional destructor callback.
- */
-DMNSN_INTERNAL void dmnsn_key_create(pthread_key_t *key,
-                                     dmnsn_callback_fn *destructor);
-
-/**
- * Set a thread-specific pointer, bailing out on failure.
- * @param[in] key    The thread-local storage key.
- * @param[in] value  The value to set.
- */
-DMNSN_INTERNAL void dmnsn_setspecific(pthread_key_t key, const void *value);
-
-/**
- * Destroy a thread-local storage key, warning on failure.
- * @param[out] key         The key to destroy.
- */
-DMNSN_INTERNAL void dmnsn_key_delete(pthread_key_t key);
-
-/**
- * Join a thread, bailing out on failure.
- * @param[in,out] thread  The thread to join.
- */
-DMNSN_INTERNAL void dmnsn_join_thread(pthread_t thread, void **retval);
diff --git a/libdimension/timer.c b/libdimension/timer.c
deleted file mode 100644
index 35bf05d..0000000
--- a/libdimension/timer.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2011 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Performance counter.
- */
-
-#include "dimension-internal.h"
-
-void
-dmnsn_timer_start(dmnsn_timer *timer)
-{
-  dmnsn_get_times(timer);
-}
-
-void
-dmnsn_timer_stop(dmnsn_timer *timer)
-{
-  dmnsn_timer now;
-  dmnsn_get_times(&now);
-  timer->real   = now.real   - timer->real;
-  timer->user   = now.user   - timer->user;
-  timer->system = now.system - timer->system;
-}
diff --git a/libdimension/torus.c b/libdimension/torus.c
deleted file mode 100644
index 30acbd8..0000000
--- a/libdimension/torus.c
+++ /dev/null
@@ -1,172 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2010-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Torii.  A special case of a quartic.
- */
-
-#include "dimension.h"
-
-/// Torus type.
-typedef struct {
-  dmnsn_object object;
-  double major, minor;
-} dmnsn_torus;
-
-/// Bound the torus in a cylindrical shell.
-static inline bool
-dmnsn_torus_bound_intersection(const dmnsn_torus *torus, dmnsn_line l)
-{
-  double R = torus->major, r = torus->minor;
-  double rmax = R + r, rmin = R - r;
-  double rmax2 = rmax*rmax, rmin2 = rmin*rmin;
-
-  // Try the caps first
-  double tlower = (-r - l.x0.y)/l.n.y;
-  double tupper = (+r - l.x0.y)/l.n.y;
-  dmnsn_vector lower = dmnsn_line_point(l, tlower);
-  dmnsn_vector upper = dmnsn_line_point(l, tupper);
-  double ldist2 = lower.x*lower.x + lower.z*lower.z;
-  double udist2 = upper.x*upper.x + upper.z*upper.z;
-  if ((ldist2 < rmin2 || ldist2 > rmax2) && (udist2 < rmin2 || udist2 > rmax2)) {
-    // No valid intersection with the caps, try the cylinder walls
-    double dist2 = l.x0.x*l.x0.x + l.x0.z*l.x0.z;
-    double bigcyl[3], smallcyl[3];
-    bigcyl[2]   = smallcyl[2] = l.n.x*l.n.x + l.n.z*l.n.z;
-    bigcyl[1]   = smallcyl[1] = 2.0*(l.n.x*l.x0.x + l.n.z*l.x0.z);
-    bigcyl[0]   = dist2 - rmax2;
-    smallcyl[0] = dist2 - rmin2;
-
-    double x[4];
-    size_t n = dmnsn_polynomial_solve(bigcyl, 2, x);
-    n += dmnsn_polynomial_solve(smallcyl, 2, x + n);
-
-    size_t i;
-    for (i = 0; i < n; ++i) {
-      dmnsn_vector p = dmnsn_line_point(l, x[i]);
-      if (p.y >= -r && p.y <= r)
-        break;
-    }
-
-    if (i == n) {
-      // No valid intersection found
-      return false;
-    }
-  }
-
-  return true;
-}
-
-/// Torus intersection callback.
-static bool
-dmnsn_torus_intersection_fn(const dmnsn_object *object, dmnsn_line l,
-                            dmnsn_intersection *intersection)
-{
-  const dmnsn_torus *torus = (const dmnsn_torus *)object;
-  double R = torus->major, r = torus->minor;
-  double RR = R*R, rr = r*r;
-
-  if (!dmnsn_torus_bound_intersection(torus, l)) {
-    return false;
-  }
-
-  // This bit of algebra here is correct
-  dmnsn_vector x0mod = dmnsn_new_vector(l.x0.x, -l.x0.y, l.x0.z);
-  dmnsn_vector nmod  = dmnsn_new_vector(l.n.x,  -l.n.y,  l.n.z);
-  double nn      = dmnsn_vector_dot(l.n, l.n);
-  double nx0     = dmnsn_vector_dot(l.n, l.x0);
-  double x0x0    = dmnsn_vector_dot(l.x0, l.x0);
-  double x0x0mod = dmnsn_vector_dot(l.x0, x0mod);
-  double nx0mod  = dmnsn_vector_dot(l.n, x0mod);
-  double nnmod   = dmnsn_vector_dot(l.n, nmod);
-
-  double poly[5];
-  poly[4] = nn*nn;
-  poly[3] = 4*nn*nx0;
-  poly[2] = 2.0*(nn*(x0x0 - rr) + 2.0*nx0*nx0 - RR*nnmod);
-  poly[1] = 4.0*(nx0*(x0x0 - rr) - RR*nx0mod);
-  poly[0] = x0x0*x0x0 + RR*(RR - 2.0*x0x0mod) - rr*(2.0*(RR + x0x0) - rr);
-
-  double x[4];
-  size_t n = dmnsn_polynomial_solve(poly, 4, x);
-  if (n == 0)
-    return false;
-
-  double t = x[0];
-  for (size_t i = 1; i < n; ++i) {
-    t = dmnsn_min(t, x[i]);
-  }
-
-  if (t < 0.0) {
-    return false;
-  }
-
-  dmnsn_vector p = dmnsn_line_point(l, t);
-  dmnsn_vector center = dmnsn_vector_mul(
-    R,
-    dmnsn_vector_normalized(dmnsn_new_vector(p.x, 0.0, p.z))
-  );
-  dmnsn_vector normal = dmnsn_vector_sub(p, center);
-
-  intersection->t      = t;
-  intersection->normal = normal;
-  return true;
-}
-
-/// Torus inside callback.
-static bool
-dmnsn_torus_inside_fn(const dmnsn_object *object, dmnsn_vector point)
-{
-  const dmnsn_torus *torus = (const dmnsn_torus *)object;
-  double dmajor = torus->major - sqrt(point.x*point.x + point.z*point.z);
-  return dmajor*dmajor + point.y*point.y < torus->minor*torus->minor;
-}
-
-/// Torus bounding callback.
-static dmnsn_bounding_box
-dmnsn_torus_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  const dmnsn_torus *torus = (const dmnsn_torus *)object;
-
-  double extent = torus->major + torus->minor;
-  dmnsn_bounding_box box = dmnsn_symmetric_bounding_box(dmnsn_new_vector(extent, torus->minor, extent));
-  return dmnsn_transform_bounding_box(trans, box);
-}
-
-/// Torus vtable.
-static const dmnsn_object_vtable dmnsn_torus_vtable = {
-  .intersection_fn = dmnsn_torus_intersection_fn,
-  .inside_fn = dmnsn_torus_inside_fn,
-  .bounding_fn = dmnsn_torus_bounding_fn,
-};
-
-dmnsn_object *
-dmnsn_new_torus(dmnsn_pool *pool, double major, double minor)
-{
-  dmnsn_torus *torus = DMNSN_PALLOC(pool, dmnsn_torus);
-  torus->major = major;
-  torus->minor = minor;
-
-  dmnsn_object *object = &torus->object;
-  dmnsn_init_object(object);
-  object->vtable = &dmnsn_torus_vtable;
-  return object;
-}
diff --git a/libdimension/triangle.c b/libdimension/triangle.c
deleted file mode 100644
index fdd2b96..0000000
--- a/libdimension/triangle.c
+++ /dev/null
@@ -1,162 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2009-2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Triangles.  See
- * http://tavianator.com/2014/05/a-beautiful-raytriangle-intersection-method/
- * for a description of the intersection algorithm.
- */
-
-#include "dimension-internal.h"
-
-/// Optimized ray/triangle intersection test.
-static inline bool
-dmnsn_ray_triangle_intersection(dmnsn_line l, double *t, double *u, double *v)
-{
-  // See the change of basis in dmnsn_triangle_basis()
-  *t = -l.x0.z/l.n.z;
-  *u = l.x0.x + (*t)*l.n.x;
-  *v = l.x0.y + (*t)*l.n.y;
-  return *t >= 0.0 && *u >= 0.0 && *v >= 0.0 && *u + *v <= 1.0;
-}
-
-/// Triangle intersection callback.
-DMNSN_HOT static bool
-dmnsn_triangle_intersection_fn(const dmnsn_object *object, dmnsn_line l,
-                               dmnsn_intersection *intersection)
-{
-  double t, u, v;
-  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
-    intersection->t = t;
-    intersection->normal = dmnsn_z;
-    return true;
-  }
-
-  return false;
-}
-
-/// Triangle inside callback.
-static bool
-dmnsn_triangle_inside_fn(const dmnsn_object *object, dmnsn_vector point)
-{
-  return false;
-}
-
-/// Triangle bounding callback.
-static dmnsn_bounding_box
-dmnsn_triangle_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  dmnsn_vector a = dmnsn_transform_point(trans, dmnsn_zero);
-  dmnsn_vector b = dmnsn_transform_point(trans, dmnsn_x);
-  dmnsn_vector c = dmnsn_transform_point(trans, dmnsn_y);
-
-  dmnsn_bounding_box box = dmnsn_new_bounding_box(a, a);
-  box = dmnsn_bounding_box_swallow(box, b);
-  box = dmnsn_bounding_box_swallow(box, c);
-  return box;
-}
-
-/// Triangle vtable.
-static const dmnsn_object_vtable dmnsn_triangle_vtable = {
-  .intersection_fn = dmnsn_triangle_intersection_fn,
-  .inside_fn = dmnsn_triangle_inside_fn,
-  .bounding_fn = dmnsn_triangle_bounding_fn,
-};
-
-/// Smooth triangle type.
-typedef struct {
-  dmnsn_object object;
-  dmnsn_vector na, nab, nac;
-} dmnsn_smooth_triangle;
-
-/// Smooth triangle intersection callback.
-DMNSN_HOT static bool
-dmnsn_smooth_triangle_intersection_fn(const dmnsn_object *object, dmnsn_line l,
-                                      dmnsn_intersection *intersection)
-{
-  const dmnsn_smooth_triangle *triangle = (const dmnsn_smooth_triangle *)object;
-
-  double t, u, v;
-  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
-    intersection->t = t;
-    intersection->normal = dmnsn_vector_add(
-      triangle->na,
-      dmnsn_vector_add(
-        dmnsn_vector_mul(u, triangle->nab),
-        dmnsn_vector_mul(v, triangle->nac)
-      )
-    );
-    return true;
-  }
-
-  return false;
-}
-
-/// Smooth triangle vtable.
-static const dmnsn_object_vtable dmnsn_smooth_triangle_vtable = {
-  .intersection_fn = dmnsn_smooth_triangle_intersection_fn,
-  .inside_fn = dmnsn_triangle_inside_fn,
-  .bounding_fn = dmnsn_triangle_bounding_fn,
-};
-
-/// Make a change-of-basis matrix.
-static inline dmnsn_matrix
-dmnsn_triangle_basis(dmnsn_vector vertices[3])
-{
-  // The new vector space has corners at <1, 0, 0>, <0, 1, 0>, and 0,
-  // corresponding to the basis (ab, ac, ab X ac).
-  dmnsn_vector ab = dmnsn_vector_sub(vertices[1], vertices[0]);
-  dmnsn_vector ac = dmnsn_vector_sub(vertices[2], vertices[0]);
-  dmnsn_vector normal = dmnsn_vector_cross(ab, ac);
-  return dmnsn_new_matrix4(ab, ac, normal, vertices[0]);
-}
-
-dmnsn_object *
-dmnsn_new_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3])
-{
-  dmnsn_object *object = dmnsn_new_object(pool);
-  object->vtable = &dmnsn_triangle_vtable;
-  object->intrinsic_trans = dmnsn_triangle_basis(vertices);
-  return object;
-}
-
-dmnsn_object *
-dmnsn_new_smooth_triangle(dmnsn_pool *pool, dmnsn_vector vertices[3], dmnsn_vector normals[3])
-{
-  dmnsn_matrix P = dmnsn_triangle_basis(vertices);
-
-  // Transform the given normals.
-  dmnsn_vector na = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[0]));
-  dmnsn_vector nb = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[1]));
-  dmnsn_vector nc = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[2]));
-
-  dmnsn_smooth_triangle *triangle = DMNSN_PALLOC(pool, dmnsn_smooth_triangle);
-  triangle->na  = na;
-  triangle->nab = dmnsn_vector_sub(nb, na);
-  triangle->nac = dmnsn_vector_sub(nc, na);
-
-  dmnsn_object *object = &triangle->object;
-  dmnsn_init_object(object);
-  object->vtable = &dmnsn_smooth_triangle_vtable;
-  object->intrinsic_trans = P;
-
-  return object;
-}
diff --git a/libdimension/triangle_fan.c b/libdimension/triangle_fan.c
deleted file mode 100644
index 9940614..0000000
--- a/libdimension/triangle_fan.c
+++ /dev/null
@@ -1,346 +0,0 @@
-/*************************************************************************
- * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com>     *
- *                                                                       *
- * This file is part of The Dimension Library.                           *
- *                                                                       *
- * The Dimension Library is free software; you can redistribute it and/  *
- * or modify it under the terms of the GNU Lesser General Public License *
- * as published by the Free Software Foundation; either version 3 of the *
- * License, or (at your option) any later version.                       *
- *                                                                       *
- * The Dimension Library is distributed in the hope that it will be      *
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty   *
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  *
- * Lesser General Public License for more details.                       *
- *                                                                       *
- * You should have received a copy of the GNU Lesser General Public      *
- * License along with this program.  If not, see                         *
- * <http://www.gnu.org/licenses/>.                                       *
- *************************************************************************/
-
-/**
- * @file
- * Triangle fans.  See
- * http://tavianator.com/2014/05/a-beautiful-raymesh-intersection-algorithm/
- * for a description of the intersection algorithm.
- */
-
-#include "dimension-internal.h"
-
-/// Triangle fan type.
-typedef struct {
-  dmnsn_object object;
-  size_t ncoeffs;
-  double coeffs[][6];
-} dmnsn_triangle_fan;
-
-/// Change basis from one triangle to the next.
-static inline dmnsn_vector
-dmnsn_change_basis(const double coeffs[6], dmnsn_vector v)
-{
-  return dmnsn_new_vector(
-    coeffs[0]*v.x + coeffs[1]*v.z + v.y,
-    coeffs[2]*v.x + coeffs[3]*v.z,
-    coeffs[4]*v.x + coeffs[5]*v.z
-  );
-}
-
-/// Change basis from one triangle to the next for a normal vector.
-static inline dmnsn_vector
-dmnsn_change_normal_basis(const double coeffs[6], dmnsn_vector n)
-{
-  return dmnsn_new_vector(
-    coeffs[0]*n.x + coeffs[2]*n.y + coeffs[4]*n.z,
-    n.x,
-    coeffs[1]*n.x + coeffs[3]*n.y + coeffs[5]*n.z
-  );
-}
-
-/// Change basis from one triangle to the next for a line
-static inline dmnsn_line
-dmnsn_change_line_basis(const double coeffs[6], dmnsn_line l)
-{
-  return dmnsn_new_line(dmnsn_change_basis(coeffs, l.x0), dmnsn_change_basis(coeffs, l.n));
-}
-
-/// Store the compressed incremental matrix.
-static inline void
-dmnsn_compress_coeffs(double coeffs[6], dmnsn_matrix incremental)
-{
-  coeffs[0] = incremental.n[0][0];
-  coeffs[1] = incremental.n[0][2];
-  coeffs[2] = incremental.n[1][0];
-  coeffs[3] = incremental.n[1][2];
-  coeffs[4] = incremental.n[2][0];
-  coeffs[5] = incremental.n[2][2];
-}
-
-/// Decompress the incremental matrix.
-static inline dmnsn_matrix
-dmnsn_decompress_coeffs(const double coeffs[6])
-{
-  dmnsn_matrix incremental = dmnsn_new_matrix(
-    coeffs[0], 1.0, coeffs[1], 0.0,
-    coeffs[2], 0.0, coeffs[3], 0.0,
-    coeffs[4], 0.0, coeffs[5], 0.0
-  );
-  return incremental;
-}
-
-/// Make a change-of-basis matrix for a triangle.
-static inline dmnsn_matrix
-dmnsn_triangle_basis(dmnsn_vector a, dmnsn_vector ab, dmnsn_vector ac)
-{
-  dmnsn_vector normal = dmnsn_vector_cross(ab, ac);
-  return dmnsn_new_matrix4(ab, ac, normal, a);
-}
-
-/// Optimized ray/triangle intersection test.
-static inline bool
-dmnsn_ray_triangle_intersection(dmnsn_line l, double *t, double *u, double *v)
-{
-  *t = -l.x0.z/l.n.z;
-  *u = l.x0.x + (*t)*l.n.x;
-  *v = l.x0.y + (*t)*l.n.y;
-  return *t >= 0.0 && *u >= 0.0 && *v >= 0.0 && *u + *v <= 1.0;
-}
-
-/// Triangle fan intersection callback.
-DMNSN_HOT static bool
-dmnsn_triangle_fan_intersection_fn(const dmnsn_object *object, dmnsn_line l, dmnsn_intersection *intersection)
-{
-  const dmnsn_triangle_fan *fan = (const dmnsn_triangle_fan *)object;
-
-  double t, u, v;
-
-  double best_t = INFINITY;
-  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
-    best_t = t;
-  }
-
-  dmnsn_vector normal = dmnsn_z;
-  dmnsn_vector best_normal = normal;
-
-  for (size_t i = 0; i < fan->ncoeffs; ++i) {
-    const double *coeffs = fan->coeffs[i];
-    l = dmnsn_change_line_basis(coeffs, l);
-    normal = dmnsn_change_normal_basis(coeffs, normal);
-
-    if (dmnsn_ray_triangle_intersection(l, &t, &u, &v) && t < best_t) {
-      best_t = t;
-      best_normal = normal;
-    }
-  }
-
-  if (!isinf(best_t)) {
-    intersection->t = t;
-    intersection->normal = best_normal;
-    return true;
-  }
-
-  return false;
-}
-
-/// Triangle fan inside callback.
-static bool
-dmnsn_triangle_fan_inside_fn(const dmnsn_object *object, dmnsn_vector point)
-{
-  return false;
-}
-
-/// Computes the bounding box for the first triangle
-static inline dmnsn_bounding_box
-dmnsn_bound_first_triangle(dmnsn_matrix trans)
-{
-  dmnsn_vector a = dmnsn_transform_point(trans, dmnsn_zero);
-  dmnsn_vector b = dmnsn_transform_point(trans, dmnsn_x);
-  dmnsn_vector c = dmnsn_transform_point(trans, dmnsn_y);
-
-  dmnsn_bounding_box box = dmnsn_new_bounding_box(a, a);
-  box = dmnsn_bounding_box_swallow(box, b);
-  box = dmnsn_bounding_box_swallow(box, c);
-
-  return box;
-}
-
-/// Triangle fan bounding callback.
-static dmnsn_bounding_box
-dmnsn_triangle_fan_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  const dmnsn_triangle_fan *fan = (const dmnsn_triangle_fan *)object;
-
-  dmnsn_bounding_box box = dmnsn_bound_first_triangle(trans);
-
-  for (size_t i = 0; i < fan->ncoeffs; ++i) {
-    dmnsn_matrix incremental = dmnsn_decompress_coeffs(fan->coeffs[i]);
-    trans = dmnsn_matrix_mul(trans, dmnsn_matrix_inverse(incremental));
-    dmnsn_vector vertex = dmnsn_transform_point(trans, dmnsn_y);
-    box = dmnsn_bounding_box_swallow(box, vertex);
-  }
-
-  return box;
-}
-
-/// Triangle fan vtable.
-static dmnsn_object_vtable dmnsn_triangle_fan_vtable = {
-  .intersection_fn = dmnsn_triangle_fan_intersection_fn,
-  .inside_fn = dmnsn_triangle_fan_inside_fn,
-  .bounding_fn = dmnsn_triangle_fan_bounding_fn,
-};
-
-/// Smooth triangle fan type.
-typedef struct dmnsn_smooth_triangle_fan {
-  dmnsn_object object;
-  dmnsn_vector na, nab, nac;
-  size_t ncoeffs;
-  double coeffs[][9]; ///< 0-6 is same as dmnsn_triangle_fan, 6-9 is the normal
-} dmnsn_smooth_triangle_fan;
-
-/// Smooth triangle fan intersection callback.
-DMNSN_HOT static bool
-dmnsn_smooth_triangle_fan_intersection_fn(const dmnsn_object *object, dmnsn_line l, dmnsn_intersection *intersection)
-{
-  const dmnsn_smooth_triangle_fan *fan = (const dmnsn_smooth_triangle_fan *)object;
-
-  dmnsn_vector nab = fan->nab;
-  dmnsn_vector nac = fan->nac;
-
-  double t, u, v;
-
-  double best_t = INFINITY;
-  dmnsn_vector best_normal;
-  if (dmnsn_ray_triangle_intersection(l, &t, &u, &v)) {
-    best_t = t;
-    best_normal = dmnsn_vector_add(dmnsn_vector_mul(u, nab), dmnsn_vector_mul(v, nac));
-  }
-
-  for (size_t i = 0; i < fan->ncoeffs; ++i) {
-    const double *coeffs = fan->coeffs[i];
-    l = dmnsn_change_line_basis(coeffs, l);
-    nab = nac;
-    nac = dmnsn_new_vector(coeffs[6], coeffs[7], coeffs[8]);
-
-    if (dmnsn_ray_triangle_intersection(l, &t, &u, &v) && t < best_t) {
-      best_t = t;
-      best_normal = dmnsn_vector_add(dmnsn_vector_mul(u, nab), dmnsn_vector_mul(v, nac));
-    }
-  }
-
-  if (!isinf(best_t)) {
-    intersection->t = t;
-    intersection->normal = dmnsn_vector_add(fan->na, best_normal);
-    return true;
-  }
-
-  return false;
-}
-
-/// Smooth triangle fan bounding callback.
-static dmnsn_bounding_box
-dmnsn_smooth_triangle_fan_bounding_fn(const dmnsn_object *object, dmnsn_matrix trans)
-{
-  const dmnsn_smooth_triangle_fan *fan = (const dmnsn_smooth_triangle_fan *)object;
-
-  dmnsn_bounding_box box = dmnsn_bound_first_triangle(trans);
-
-  for (size_t i = 0; i < fan->ncoeffs; ++i) {
-    dmnsn_matrix incremental = dmnsn_decompress_coeffs(fan->coeffs[i]);
-    trans = dmnsn_matrix_mul(trans, dmnsn_matrix_inverse(incremental));
-    dmnsn_vector vertex = dmnsn_transform_point(trans, dmnsn_y);
-    box = dmnsn_bounding_box_swallow(box, vertex);
-  }
-
-  return box;
-}
-
-/// Smooth triangle fan vtable.
-static dmnsn_object_vtable dmnsn_smooth_triangle_fan_vtable = {
-  .intersection_fn = dmnsn_smooth_triangle_fan_intersection_fn,
-  .inside_fn = dmnsn_triangle_fan_inside_fn,
-  .bounding_fn = dmnsn_smooth_triangle_fan_bounding_fn,
-};
-
-dmnsn_object *
-dmnsn_new_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], size_t nvertices)
-{
-  dmnsn_assert(nvertices >= 3, "Not enough vertices for one triangle");
-
-  size_t ncoeffs = nvertices - 3;
-  dmnsn_triangle_fan *fan = dmnsn_palloc(pool, sizeof(dmnsn_triangle_fan) + ncoeffs*sizeof(double[6]));
-  fan->ncoeffs = ncoeffs;
-
-  dmnsn_object *object = &fan->object;
-  dmnsn_init_object(object);
-  object->vtable = &dmnsn_triangle_fan_vtable;
-
-  // Compute the initial matrix and the coefficients
-  dmnsn_vector a = vertices[0];
-  dmnsn_vector ab = dmnsn_vector_sub(vertices[1], a);
-  dmnsn_vector ac = dmnsn_vector_sub(vertices[2], a);
-  dmnsn_matrix P = dmnsn_triangle_basis(a, ab, ac);
-  object->intrinsic_trans = P;
-
-  for (size_t i = 0; i < ncoeffs; ++i) {
-    ab = ac;
-    ac = dmnsn_vector_sub(vertices[i + 3], a);
-
-    dmnsn_matrix newP = dmnsn_triangle_basis(a, ab, ac);
-    dmnsn_matrix incremental = dmnsn_matrix_mul(dmnsn_matrix_inverse(newP), P);
-    dmnsn_compress_coeffs(fan->coeffs[i], incremental);
-
-    P = newP;
-  }
-
-  return object;
-}
-
-dmnsn_object *
-dmnsn_new_smooth_triangle_fan(dmnsn_pool *pool, dmnsn_vector vertices[], dmnsn_vector normals[], size_t nvertices)
-{
-  dmnsn_assert(nvertices >= 3, "Not enough vertices for one triangle");
-
-  size_t ncoeffs = nvertices - 3;
-  dmnsn_smooth_triangle_fan *fan = dmnsn_palloc(pool, sizeof(dmnsn_smooth_triangle_fan) + ncoeffs*sizeof(double[9]));
-  fan->ncoeffs = ncoeffs;
-
-  dmnsn_object *object = &fan->object;
-  dmnsn_init_object(object);
-  object->vtable = &dmnsn_smooth_triangle_fan_vtable;
-
-  // Compute the initial matrix
-  dmnsn_vector a = vertices[0];
-  dmnsn_vector ab = dmnsn_vector_sub(vertices[1], a);
-  dmnsn_vector ac = dmnsn_vector_sub(vertices[2], a);
-  dmnsn_matrix P = dmnsn_triangle_basis(a, ab, ac);
-  dmnsn_matrix Pabc = P;
-  object->intrinsic_trans = P;
-
-  // Transform the first three normals
-  dmnsn_vector na = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[0]));
-  dmnsn_vector nb = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[1]));
-  dmnsn_vector nc = dmnsn_vector_normalized(dmnsn_transform_normal(P, normals[2]));
-  fan->na = na;
-  fan->nab = dmnsn_vector_sub(nb, na);
-  fan->nac = dmnsn_vector_sub(nc, na);
-
-  // Compute the coefficients
-  for (size_t i = 0; i < ncoeffs; ++i) {
-    ab = ac;
-    ac = dmnsn_vector_sub(vertices[i + 3], a);
-
-    dmnsn_matrix newP = dmnsn_triangle_basis(a, ab, ac);
-    dmnsn_matrix incremental = dmnsn_matrix_mul(dmnsn_matrix_inverse(newP), P);
-    double *coeffs = fan->coeffs[i];
-    dmnsn_compress_coeffs(coeffs, incremental);
-
-    nc = dmnsn_vector_normalized(dmnsn_transform_normal(Pabc, normals[i + 3]));
-    dmnsn_vector nac = dmnsn_vector_sub(nc, na);
-    coeffs[6] = nac.x;
-    coeffs[7] = nac.y;
-    coeffs[8] = nac.z;
-
-    P = newP;
-  }
-
-  return object;
-}
-- 
cgit v1.2.3