]> git.localhorst.tv Git - blank.git/blobdiff - src/model/shape.cpp
some annotations
[blank.git] / src / model / shape.cpp
index d69046e6c7c00523f93a2936c0c7e558eb88ff16..a5e728a398777d744935ee5a6c2f3e2863c12fff 100644 (file)
 #include "Shape.hpp"
-#include "shapes.hpp"
+#include "ShapeRegistry.hpp"
+
+#include "bounds.hpp"
+#include "../io/TokenStreamReader.hpp"
+
+#include <string>
+
+using namespace std;
 
 
 namespace blank {
 
-void Shape::Vertices(
-       EntityMesh::Buffer &out,
-       float tex_offset
-) const {
-       for (const auto &pos : vtx_pos) {
-               out.vertices.emplace_back(pos);
+Shape::Shape()
+: bounds()
+, vertices()
+, indices()
+, fill({ false, false, false, false, false, false }) {
+
+}
+
+void Shape::Read(TokenStreamReader &in) {
+       bounds.reset();
+       vertices.clear();
+       indices.clear();
+       fill = { false, false, false, false, false, false };
+
+       string name;
+       in.Skip(Token::ANGLE_BRACKET_OPEN);
+       while (in.HasMore() && in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
+               in.ReadIdentifier(name);
+               in.Skip(Token::EQUALS);
+               if (name == "bounds") {
+                       string bounds_class;
+                       in.ReadIdentifier(bounds_class);
+                       in.Skip(Token::PARENTHESIS_OPEN);
+                       if (bounds_class == "Cuboid") {
+                               glm::vec3 min;
+                               glm::vec3 max;
+                               in.ReadVec(min);
+                               in.Skip(Token::COMMA);
+                               in.ReadVec(max);
+                               bounds.reset(new CuboidBounds(AABB{min, max}));
+                       } else if (bounds_class == "Stair") {
+                               glm::vec3 min;
+                               glm::vec3 max;
+                               glm::vec2 split;
+                               in.ReadVec(min);
+                               in.Skip(Token::COMMA);
+                               in.ReadVec(max);
+                               in.Skip(Token::COMMA);
+                               in.ReadVec(split);
+                               bounds.reset(new StairBounds(AABB{min, max}, split));
+                       } else {
+                               while (in.Peek().type != Token::PARENTHESIS_CLOSE) {
+                                       in.Next();
+                               }
+                       }
+                       in.Skip(Token::PARENTHESIS_CLOSE);
+
+               } else if (name == "vertices") {
+                       in.Skip(Token::ANGLE_BRACKET_OPEN);
+                       while (in.HasMore() && in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
+                               in.Skip(Token::ANGLE_BRACKET_OPEN);
+                               Vertex vtx;
+                               in.ReadVec(vtx.position);
+                               in.Skip(Token::COMMA);
+                               in.ReadVec(vtx.normal);
+                               in.Skip(Token::COMMA);
+                               in.ReadVec(vtx.tex_st);
+                               in.Skip(Token::COMMA);
+                               in.ReadNumber(vtx.tex_id);
+                               if (in.Peek().type == Token::COMMA) {
+                                       in.Skip(Token::COMMA);
+                               }
+                               in.Skip(Token::ANGLE_BRACKET_CLOSE);
+                               if (in.Peek().type == Token::COMMA) {
+                                       in.Skip(Token::COMMA);
+                               }
+                               vertices.push_back(vtx);
+                       }
+                       in.Skip(Token::ANGLE_BRACKET_CLOSE);
+
+               } else if (name == "indices") {
+                       in.Skip(Token::ANGLE_BRACKET_OPEN);
+                       while (in.HasMore() && in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
+                               indices.push_back(in.GetULong());
+                               if (in.Peek().type == Token::COMMA) {
+                                       in.Skip(Token::COMMA);
+                               }
+                       }
+                       in.Skip(Token::ANGLE_BRACKET_CLOSE);
+
+               } else if (name == "fill") {
+                       in.Skip(Token::BRACKET_OPEN);
+                       fill.face[Block::FACE_UP] = in.GetBool();
+                       in.Skip(Token::COMMA);
+                       fill.face[Block::FACE_DOWN] = in.GetBool();
+                       in.Skip(Token::COMMA);
+                       fill.face[Block::FACE_RIGHT] = in.GetBool();
+                       in.Skip(Token::COMMA);
+                       fill.face[Block::FACE_LEFT] = in.GetBool();
+                       in.Skip(Token::COMMA);
+                       fill.face[Block::FACE_FRONT] = in.GetBool();
+                       in.Skip(Token::COMMA);
+                       fill.face[Block::FACE_BACK] = in.GetBool();
+                       in.Skip(Token::BRACKET_CLOSE);
+
+               } else {
+                       // try to skip, might fail though
+                       while (in.HasMore() && in.Peek().type != Token::SEMICOLON) {
+                               in.Next();
+                       }
+               }
+               in.Skip(Token::SEMICOLON);
        }
-       for (const auto &coord : vtx_tex_coords) {
-               out.tex_coords.emplace_back(coord.x, coord.y, coord.z + tex_offset);
+       in.Skip(Token::ANGLE_BRACKET_CLOSE);
+}
+
+float Shape::TexR(const vector<float> &tex_map, size_t off) noexcept {
+       if (off < tex_map.size()) {
+               return tex_map[off];
+       } else if (!tex_map.empty()) {
+               return tex_map.back();
+       } else {
+               return 0.0f;
        }
-       for (const auto &nrm : vtx_nrm) {
-               out.normals.emplace_back(nrm);
+}
+
+void Shape::Fill(
+       EntityMesh::Buffer &buf,
+       const vector<float> &tex_map
+) const {
+       for (const auto &vtx : vertices) {
+               buf.vertices.emplace_back(vtx.position);
+               buf.normals.emplace_back(vtx.normal);
+               buf.tex_coords.emplace_back(vtx.tex_st.s, vtx.tex_st.t, TexR(tex_map, vtx.tex_id));
        }
-       for (auto idx : vtx_idx) {
-               out.indices.emplace_back(idx);
+       for (auto idx : indices) {
+               buf.indices.emplace_back(idx);
        }
 }
 
-void Shape::Vertices(
-       EntityMesh::Buffer &out,
+void Shape::Fill(
+       EntityMesh::Buffer &buf,
        const glm::mat4 &transform,
-       float tex_offset,
-       EntityMesh::Index idx_offset
+       const vector<float> &tex_map
 ) const {
-       for (const auto &pos : vtx_pos) {
-               out.vertices.emplace_back(transform * glm::vec4(pos, 1.0f));
+       for (const auto &vtx : vertices) {
+               buf.vertices.emplace_back(transform * glm::vec4(vtx.position, 1.0f));
+               buf.normals.emplace_back(transform * glm::vec4(vtx.normal, 0.0f));
+               buf.tex_coords.emplace_back(vtx.tex_st.s, vtx.tex_st.t, TexR(tex_map, vtx.tex_id));
        }
-       for (const auto &coord : vtx_tex_coords) {
-               out.tex_coords.emplace_back(coord.x, coord.y, coord.z + tex_offset);
-       }
-       for (const auto &nrm : vtx_nrm) {
-               out.normals.emplace_back(transform * glm::vec4(nrm, 0.0f));
-       }
-       for (auto idx : vtx_idx) {
-               out.indices.emplace_back(idx_offset + idx);
+       for (auto idx : indices) {
+               buf.indices.emplace_back(idx);
        }
 }
 
-void Shape::Vertices(
-       BlockMesh::Buffer &out,
+void Shape::Fill(
+       BlockMesh::Buffer &buf,
        const glm::mat4 &transform,
-       float tex_offset,
-       BlockMesh::Index idx_offset
+       const vector<float> &tex_map,
+       size_t idx_offset
 ) const {
-       for (const auto &pos : vtx_pos) {
-               out.vertices.emplace_back(transform * glm::vec4(pos, 1.0f));
-       }
-       for (const auto &coord : vtx_tex_coords) {
-               out.tex_coords.emplace_back(coord.x, coord.y, coord.z + tex_offset);
+       for (const auto &vtx : vertices) {
+               buf.vertices.emplace_back(transform * glm::vec4(vtx.position, 1.0f));
+               buf.tex_coords.emplace_back(vtx.tex_st.s, vtx.tex_st.t, TexR(tex_map, vtx.tex_id));
        }
-       for (auto idx : vtx_idx) {
-               out.indices.emplace_back(idx_offset + idx);
+       for (auto idx : indices) {
+               buf.indices.emplace_back(idx_offset + idx);
        }
 }
 
-void Shape::Outline(OutlineMesh::Buffer &out) const {
-       out.vertices.insert(out.vertices.end(), out_pos.begin(), out_pos.end());
-       out.indices.insert(out.indices.end(), out_idx.begin(), out_idx.end());
-}
-
-void Shape::SetShape(
-       const EntityMesh::Positions &pos,
-       const EntityMesh::Normals &nrm,
-       const EntityMesh::Indices &idx
-) {
-       vtx_pos = pos;
-       vtx_nrm = nrm;
-       vtx_idx = idx;
-}
-
-void Shape::SetTexture(
-       const BlockMesh::TexCoords &tex_coords
-) {
-       vtx_tex_coords = tex_coords;
-}
-
-void Shape::SetOutline(
-       const OutlineMesh::Positions &pos,
-       const OutlineMesh::Indices &idx
-) {
-       out_pos = pos;
-       out_idx = idx;
-}
-
-
-NullShape::NullShape()
-: Shape() {
-
-}
-
-bool NullShape::Intersects(
-       const Ray &,
-       const glm::mat4 &,
-       float &, glm::vec3 &
-) const noexcept {
-       return false;
+size_t Shape::OutlineCount() const noexcept {
+       if (bounds) {
+               return bounds->OutlineCount();
+       } else {
+               return 0;
+       }
 }
 
-bool NullShape::Intersects(
-       const glm::mat4 &,
-       const AABB &,
-       const glm::mat4 &,
-       float &,
-       glm::vec3 &
-) const noexcept {
-       return false;
+size_t Shape::OutlineIndexCount() const noexcept {
+       if (bounds) {
+               return bounds->OutlineIndexCount();
+       } else {
+               return 0;
+       }
 }
 
-
-CuboidShape::CuboidShape(const AABB &b)
-: Shape()
-, bb(b) {
-       bb.Adjust();
-       SetShape({
-               { bb.min.x, bb.min.y, bb.max.z }, // front
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-               { bb.min.x, bb.min.y, bb.min.z }, // back
-               { bb.min.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.min.y, bb.min.z },
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.min.x, bb.max.y, bb.min.z }, // top
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-               { bb.min.x, bb.min.y, bb.min.z }, // bottom
-               { bb.max.x, bb.min.y, bb.min.z },
-               { bb.min.x, bb.min.y, bb.max.z },
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.min.y, bb.min.z }, // left
-               { bb.min.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.max.y, bb.min.z },
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.min.y, bb.min.z }, // right
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-       }, {
-               {  0.0f,  0.0f,  1.0f }, // front
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f, -1.0f }, // back
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  1.0f,  0.0f }, // top
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f }, // bottom
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f }, // left
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f }, // right
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-       }, {
-                 0,  1,  2,  2,  1,  3, // front
-                 4,  5,  6,  6,  5,  7, // back
-                 8,  9, 10, 10,  9, 11, // top
-                12, 13, 14, 14, 13, 15, // bottom
-                16, 17, 18, 18, 17, 19, // left
-                20, 21, 22, 22, 21, 23, // right
-       });
-       SetTexture({
-               { 0.0f, 1.0f, 0.0f }, // front
-               { 1.0f, 1.0f, 0.0f },
-               { 0.0f, 0.0f, 0.0f },
-               { 1.0f, 0.0f, 0.0f },
-               { 1.0f, 1.0f, 0.0f }, // back
-               { 1.0f, 0.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f },
-               { 0.0f, 0.0f, 0.0f },
-               { 0.0f, 0.0f, 0.0f }, // top
-               { 0.0f, 1.0f, 0.0f },
-               { 1.0f, 0.0f, 0.0f },
-               { 1.0f, 1.0f, 0.0f },
-               { 1.0f, 0.0f, 0.0f }, // bottom
-               { 0.0f, 0.0f, 0.0f },
-               { 1.0f, 1.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f }, // left
-               { 1.0f, 1.0f, 0.0f },
-               { 0.0f, 0.0f, 0.0f },
-               { 1.0f, 0.0f, 0.0f },
-               { 1.0f, 1.0f, 0.0f }, // right
-               { 1.0f, 0.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f },
-               { 0.0f, 0.0f, 0.0f },
-       });
-       SetOutline({
-               { bb.min.x, bb.min.y, bb.min.z }, // back
-               { bb.max.x, bb.min.y, bb.min.z },
-               { bb.min.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.min.x, bb.min.y, bb.max.z }, // front
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-       }, {
-               0, 1, 1, 3, 3, 2, 2, 0, // back
-               4, 5, 5, 7, 7, 6, 6, 4, // front
-               0, 4, 1, 5, 2, 6, 3, 7, // sides
-       });
+void Shape::Outline(PrimitiveMesh::Buffer &out) const {
+       if (bounds) {
+               bounds->Outline(out);
+       }
 }
 
-bool CuboidShape::Intersects(
+bool Shape::Intersects(
        const Ray &ray,
        const glm::mat4 &M,
-       float &dist, glm::vec3 &normal
+       float &dist,
+       glm::vec3 &normal
 ) const noexcept {
-       return Intersection(ray, bb, M, &dist, &normal);
+       if (bounds) {
+               return bounds->Intersects(ray, M, dist, normal);
+       } else {
+               return false;
+       }
 }
 
-bool CuboidShape::Intersects(
+bool Shape::Intersects(
        const glm::mat4 &M,
        const AABB &box,
        const glm::mat4 &box_M,
        float &depth,
        glm::vec3 &normal
 ) const noexcept {
-       return Intersection(bb, M, box, box_M, depth, normal);
+       if (bounds) {
+               return bounds->Intersects(M, box, box_M, depth, normal);
+       } else {
+               return false;
+       }
 }
 
 
-StairShape::StairShape(const AABB &bb, const glm::vec2 &clip)
-: Shape()
-, top({ { bb.min.x, clip.y, bb.min.z }, { bb.max.x, bb.max.y, clip.x } })
-, bot({ bb.min, { bb.max.x, clip.y, bb.max.z } }) {
-       SetShape({
-               { top.min.x, top.min.y, top.max.z }, // front, upper
-               { top.max.x, top.min.y, top.max.z },
-               { top.min.x, top.max.y, top.max.z },
-               { top.max.x, top.max.y, top.max.z },
-               { bot.min.x, bot.min.y, bot.max.z }, // front, lower
-               { bot.max.x, bot.min.y, bot.max.z },
-               { bot.min.x, bot.max.y, bot.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-               { bot.min.x, bot.min.y, bot.min.z }, // back
-               { bot.min.x, top.max.y, bot.min.z },
-               { top.max.x, bot.min.y, bot.min.z },
-               { top.max.x, top.max.y, bot.min.z },
-               { top.min.x, top.max.y, top.min.z }, // top, upper
-               { top.min.x, top.max.y, top.max.z },
-               { top.max.x, top.max.y, top.min.z },
-               { top.max.x, top.max.y, top.max.z },
-               { bot.min.x, bot.max.y, top.max.z }, // top, lower
-               { bot.min.x, bot.max.y, bot.max.z },
-               { bot.max.x, bot.max.y, top.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-               { bot.min.x, bot.min.y, bot.min.z }, // bottom
-               { bot.max.x, bot.min.y, bot.min.z },
-               { bot.min.x, bot.min.y, bot.max.z },
-               { bot.max.x, bot.min.y, bot.max.z },
-               { top.min.x, top.min.y, top.min.z }, // left, upper
-               { top.min.x, top.min.y, top.max.z },
-               { top.min.x, top.max.y, top.min.z },
-               { top.min.x, top.max.y, top.max.z },
-               { bot.min.x, bot.min.y, bot.min.z }, // left, lower
-               { bot.min.x, bot.min.y, bot.max.z },
-               { bot.min.x, bot.max.y, bot.min.z },
-               { bot.min.x, bot.max.y, bot.max.z },
-               { top.max.x, top.min.y, top.min.z }, // right, upper
-               { top.max.x, top.max.y, top.min.z },
-               { top.max.x, top.min.y, top.max.z },
-               { top.max.x, top.max.y, top.max.z },
-               { bot.max.x, bot.min.y, bot.min.z }, // right, lower
-               { bot.max.x, bot.max.y, bot.min.z },
-               { bot.max.x, bot.min.y, bot.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-       }, {
-               {  0.0f,  0.0f,  1.0f }, // front x2
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f, -1.0f }, // back
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  1.0f,  0.0f }, // top x2
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f }, // bottom
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f }, // left x2
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f }, // right x2
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-       }, {
-                0,  1,  2,  2,  1,  3, // front, upper
-                4,  5,  6,  6,  5,  7, // front, lower
-                8,  9, 10, 10,  9, 11, // back
-               12, 13, 14, 14, 13, 15, // top, upper
-               16, 17, 18, 18, 17, 19, // top, lower
-               20, 21, 22, 22, 21, 23, // bottom
-               24, 25, 26, 26, 25, 27, // left, upper
-               28, 29, 30, 30, 29, 31, // left, lower
-               32, 33, 34, 34, 33, 35, // right, upper
-               36, 37, 38, 38, 37, 39, // right, lower
-       });
-       SetTexture({
-               { 0.0f, 0.5f, 0.0f }, // front, upper
-               { 1.0f, 0.5f, 0.0f },
-               { 0.0f, 0.0f, 0.0f },
-               { 1.0f, 0.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f }, // front, lower
-               { 1.0f, 1.0f, 0.0f },
-               { 0.0f, 0.5f, 0.0f },
-               { 1.0f, 0.5f, 0.0f },
-               { 1.0f, 1.0f, 0.0f }, // back
-               { 1.0f, 0.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f },
-               { 0.0f, 0.0f, 0.0f },
-               { 0.0f, 0.0f, 0.0f }, // top, upper
-               { 0.0f, 0.5f, 0.0f },
-               { 1.0f, 0.0f, 0.0f },
-               { 1.0f, 0.5f, 0.0f },
-               { 0.0f, 0.5f, 0.0f }, // top, lower
-               { 0.0f, 1.0f, 0.0f },
-               { 1.0f, 0.5f, 0.0f },
-               { 1.0f, 1.0f, 0.0f },
-               { 1.0f, 0.0f, 0.0f }, // bottom
-               { 0.0f, 0.0f, 0.0f },
-               { 1.0f, 1.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f },
-               { 0.0f, 0.5f, 0.0f }, // left, upper
-               { 0.5f, 0.5f, 0.0f },
-               { 0.0f, 0.0f, 0.0f },
-               { 0.5f, 0.0f, 0.0f },
-               { 0.0f, 1.0f, 0.0f }, // left, lower
-               { 1.0f, 1.0f, 0.0f },
-               { 0.0f, 0.5f, 0.0f },
-               { 1.0f, 0.5f, 0.0f },
-               { 1.0f, 0.5f, 0.0f }, // right, upper
-               { 1.0f, 0.0f, 0.0f },
-               { 0.5f, 0.5f, 0.0f },
-               { 0.5f, 0.0f, 0.0f },
-               { 1.0f, 1.0f, 0.0f }, // right, lower
-               { 1.0f, 0.5f, 0.0f },
-               { 0.0f, 1.0f, 0.0f },
-               { 0.0f, 0.5f, 0.0f },
-       });
-       SetOutline({
-               { bot.min.x, bot.min.y, bot.min.z }, // bottom
-               { bot.max.x, bot.min.y, bot.min.z },
-               { bot.min.x, bot.min.y, bot.max.z },
-               { bot.max.x, bot.min.y, bot.max.z },
-               { bot.min.x, bot.max.y, top.max.z }, // middle
-               { bot.max.x, bot.max.y, top.max.z },
-               { bot.min.x, bot.max.y, bot.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-               { top.min.x, top.max.y, top.min.z }, // top
-               { top.max.x, top.max.y, top.min.z },
-               { top.min.x, top.max.y, top.max.z },
-               { top.max.x, top.max.y, top.max.z },
-       }, {
-                0,  1,  1,  3,  3,  2,  2,  0, // bottom
-                4,  5,  5,  7,  7,  6,  6,  4, // middle
-                8,  9,  9, 11, 11, 10, 10 , 8, // top
-                0,  8,  4, 10,  2,  6, // verticals, btf
-                1,  9,  5, 11,  3,  7,
-       });
-}
+ShapeRegistry::ShapeRegistry()
+: shapes() {
 
-bool StairShape::Intersects(
-       const Ray &ray,
-       const glm::mat4 &M,
-       float &dist,
-       glm::vec3 &norm
-) const noexcept {
-       float top_dist, bot_dist;
-       glm::vec3 top_norm, bot_norm;
-       bool top_hit = Intersection(ray, top, M, &top_dist, &top_norm);
-       bool bot_hit = Intersection(ray, bot, M, &bot_dist, &bot_norm);
+}
 
-       if (top_hit) {
-               if (bot_hit) {
-                       if (top_dist < bot_dist) {
-                               dist = top_dist;
-                               norm = top_norm;
-                               return true;
-                       } else {
-                               dist = bot_dist;
-                               norm = bot_norm;
-                               return true;
-                       }
-               } else {
-                       dist = top_dist;
-                       norm = top_norm;
-                       return true;
-               }
-       } else if (bot_hit) {
-               dist = bot_dist;
-               norm = bot_norm;
-               return true;
+Shape &ShapeRegistry::Add(const string &name) {
+       auto result = shapes.emplace(name, Shape());
+       if (result.second) {
+               return result.first->second;
        } else {
-               return false;
+               throw runtime_error("duplicate shape " + name);
        }
 }
 
-bool StairShape::Intersects(
-       const glm::mat4 &M,
-       const AABB &box,
-       const glm::mat4 &box_M,
-       float &dist,
-       glm::vec3 &normal
-) const noexcept {
-       bool top_hit, bot_hit;
-       float top_dist, bot_dist;
-       glm::vec3 top_normal, bot_normal;
-
-       top_hit = Intersection(bot, M, box, box_M, top_dist, top_normal);
-       bot_hit = Intersection(top, M, box, box_M, bot_dist, bot_normal);
+Shape &ShapeRegistry::Get(const string &name) {
+       auto entry = shapes.find(name);
+       if (entry != shapes.end()) {
+               return entry->second;
+       } else {
+               throw runtime_error("unknown shape " + name);
+       }
+}
 
-       if (top_hit) {
-               if (bot_hit && bot_dist < top_dist) {
-                       dist = bot_dist;
-                       normal = bot_normal;
-                       return true;
-               } else {
-                       dist = top_dist;
-                       normal = top_normal;
-                       return true;
-               }
-               return true;
-       } else if (bot_hit) {
-               dist = bot_dist;
-               normal = bot_normal;
-               return true;
+const Shape &ShapeRegistry::Get(const string &name) const {
+       auto entry = shapes.find(name);
+       if (entry != shapes.end()) {
+               return entry->second;
        } else {
-               return false;
+               throw runtime_error("unknown shape " + name);
        }
 }