From 85dc7a4197ffeca4f4602fbfe735cd83a04d9819 Mon Sep 17 00:00:00 2001
From: Philip Trettner <Philip.Trettner@rwth-aachen.de>
Date: Fri, 2 Oct 2020 08:45:31 +0200
Subject: [PATCH] some include renaming, added raw attributes

---
 src/polymesh/algorithms/cache-optimization.hh |   2 +-
 src/polymesh/algorithms/components.hh         |   2 +-
 src/polymesh/algorithms/interpolation.hh      |   2 +-
 src/polymesh/algorithms/normalize.hh          |   2 +-
 src/polymesh/algorithms/topology.hh           |   2 +-
 src/polymesh/algorithms/tracing.hh            |   2 +-
 src/polymesh/attributes.hh                    |  11 +-
 src/polymesh/attributes/flags.hh              |   2 +-
 src/polymesh/attributes/partitioning.hh       |   2 +-
 src/polymesh/attributes/raw_attribute.hh      | 275 ++++++++++++++++++
 src/polymesh/detail/topology_iterator.hh      |   2 +-
 src/polymesh/detail/unique_array.hh           |   2 +-
 src/polymesh/formats/obj.hh                   |   2 +-
 src/polymesh/formats/off.hh                   |   2 +-
 src/polymesh/formats/ply.hh                   |   2 +-
 src/polymesh/formats/stl.hh                   |   2 +-
 src/polymesh/impl/impl_attributes.hh          |   6 +-
 src/polymesh/impl/impl_cursors.hh             |   2 +-
 src/polymesh/impl/impl_low_level_api_base.hh  |   2 +-
 .../impl/impl_low_level_api_mutable.hh        |   2 +-
 src/polymesh/impl/impl_mesh.hh                |   2 +-
 src/polymesh/impl/impl_primitive.hh           |   2 +-
 src/polymesh/impl/impl_ranges.hh              |   2 +-
 src/polymesh/objects/cone.hh                  |   2 +-
 src/polymesh/objects/cube.hh                  |   2 +-
 src/polymesh/objects/cylinder.hh              |   2 +-
 src/polymesh/objects/quad.hh                  |   2 +-
 src/polymesh/objects/uv_sphere.hh             |   2 +-
 src/polymesh/span.hh                          |  80 +++++
 src/polymesh/tmp.hh                           |  45 +++
 30 files changed, 437 insertions(+), 30 deletions(-)
 create mode 100644 src/polymesh/attributes/raw_attribute.hh
 create mode 100644 src/polymesh/span.hh

diff --git a/src/polymesh/algorithms/cache-optimization.hh b/src/polymesh/algorithms/cache-optimization.hh
index 9e9f04c..fa6e599 100644
--- a/src/polymesh/algorithms/cache-optimization.hh
+++ b/src/polymesh/algorithms/cache-optimization.hh
@@ -2,7 +2,7 @@
 
 #include <vector>
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/algorithms/components.hh b/src/polymesh/algorithms/components.hh
index 03aa1c2..57e984c 100644
--- a/src/polymesh/algorithms/components.hh
+++ b/src/polymesh/algorithms/components.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 #include "../detail/primitive_set.hh"
 #include "../detail/topology_iterator.hh"
 
diff --git a/src/polymesh/algorithms/interpolation.hh b/src/polymesh/algorithms/interpolation.hh
index 6263237..e41d691 100644
--- a/src/polymesh/algorithms/interpolation.hh
+++ b/src/polymesh/algorithms/interpolation.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/algorithms/normalize.hh b/src/polymesh/algorithms/normalize.hh
index 970d524..da713a8 100644
--- a/src/polymesh/algorithms/normalize.hh
+++ b/src/polymesh/algorithms/normalize.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 #include "../fields.hh"
 
 namespace polymesh
diff --git a/src/polymesh/algorithms/topology.hh b/src/polymesh/algorithms/topology.hh
index 9be358b..6d3c53b 100644
--- a/src/polymesh/algorithms/topology.hh
+++ b/src/polymesh/algorithms/topology.hh
@@ -2,7 +2,7 @@
 
 #include <vector>
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/algorithms/tracing.hh b/src/polymesh/algorithms/tracing.hh
index d757a17..66c74c7 100644
--- a/src/polymesh/algorithms/tracing.hh
+++ b/src/polymesh/algorithms/tracing.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 #include "../fields.hh"
 
 namespace polymesh
diff --git a/src/polymesh/attributes.hh b/src/polymesh/attributes.hh
index cf3635e..d30c5b8 100644
--- a/src/polymesh/attributes.hh
+++ b/src/polymesh/attributes.hh
@@ -73,6 +73,8 @@ public:
 
     int size() const;
     int capacity() const;
+    size_t byte_size() const override { return size() * sizeof(AttrT); }
+    size_t allocated_byte_size() const override { return capacity() * sizeof(AttrT); }
 
     attribute_iterator<primitive_attribute> begin() { return {0, size(), *this}; }
     attribute_iterator<primitive_attribute const&> begin() const { return {0, size(), *this}; }
@@ -146,8 +148,6 @@ protected:
 protected:
     void resize_from(int old_size) override;
     void clear_with_default() override;
-    size_t byte_size() const override { return size() * sizeof(AttrT); }
-    size_t allocated_byte_size() const override { return capacity() * sizeof(AttrT); }
 
     void apply_remapping(std::vector<int> const& map) override;
     void apply_transpositions(std::vector<std::pair<int, int>> const& ts) override;
@@ -231,6 +231,13 @@ struct halfedge_attribute final : primitive_attribute<halfedge_tag, AttrT>
     friend struct smart_collection;
 };
 
+/**
+ * creates a new attribute from a primitive collection.
+ *
+ * Usage:
+ *
+ *   auto pos = attribute(m.vertices(), tg::pos3::zero);
+ */
 template <class AttrT, class Collection>
 auto attribute(Collection const& c, AttrT const& defaultValue = {}) -> decltype(c.make_attribute(defaultValue))
 {
diff --git a/src/polymesh/attributes/flags.hh b/src/polymesh/attributes/flags.hh
index ea87558..8f182ff 100644
--- a/src/polymesh/attributes/flags.hh
+++ b/src/polymesh/attributes/flags.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../attributes.hh"
+#include <polymesh/attributes.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/attributes/partitioning.hh b/src/polymesh/attributes/partitioning.hh
index e389472..0b67448 100644
--- a/src/polymesh/attributes/partitioning.hh
+++ b/src/polymesh/attributes/partitioning.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/attributes/raw_attribute.hh b/src/polymesh/attributes/raw_attribute.hh
new file mode 100644
index 0000000..ca5a124
--- /dev/null
+++ b/src/polymesh/attributes/raw_attribute.hh
@@ -0,0 +1,275 @@
+#pragma once
+
+#include <cstring>
+
+#include <polymesh/Mesh.hh>
+#include <polymesh/attributes.hh>
+#include <polymesh/span.hh>
+
+namespace polymesh
+{
+/**
+ * Raw attributes are attributes that store a fixed (but runtime determined) number of bytes per primitive
+ * The number of bytes per primitive is called stride
+ *
+ * NOTE: they are always zero-initialized
+ *
+ * TODO: iteration
+ * TODO: smart range
+ */
+template <class tag>
+struct raw_primitive_attribute : primitive_attribute_base<tag>
+{
+    template <class A>
+    using attribute = typename primitive<tag>::template attribute<A>;
+    using index_t = typename primitive<tag>::index;
+    using handle_t = typename primitive<tag>::handle;
+    using tag_t = tag;
+
+    // data access
+public:
+    span<std::byte> operator[](handle_t h)
+    {
+        POLYMESH_ASSERT(this->mMesh == h.mesh && "Handle belongs to a different mesh");
+        POLYMESH_ASSERT(0 <= h.idx.value && h.idx.value < this->size() && "out of bounds");
+        return {this->mData.get() + h.idx.value * mStride, mStride};
+    }
+    span<std::byte const> operator[](handle_t h) const
+    {
+        POLYMESH_ASSERT(this->mMesh == h.mesh && "Handle belongs to a different mesh");
+        POLYMESH_ASSERT(0 <= h.idx.value && h.idx.value < this->size() && "out of bounds");
+        return {this->mData.get() + h.idx.value * mStride, mStride};
+    }
+    span<std::byte> operator[](index_t h)
+    {
+        POLYMESH_ASSERT(h.is_valid());
+        return {this->mData.get() + h.value * mStride, mStride};
+    }
+    span<std::byte const> operator[](index_t h) const
+    {
+        POLYMESH_ASSERT(h.is_valid());
+        return {this->mData.get() + h.value * mStride, mStride};
+    }
+
+    span<std::byte> operator()(handle_t h)
+    {
+        POLYMESH_ASSERT(this->mMesh == h.mesh && "Handle belongs to a different mesh");
+        POLYMESH_ASSERT(0 <= h.idx.value && h.idx.value < this->size() && "out of bounds");
+        return {this->mData.get() + h.idx.value * mStride, mStride};
+    }
+    span<std::byte const> operator()(handle_t h) const
+    {
+        POLYMESH_ASSERT(this->mMesh == h.mesh && "Handle belongs to a different mesh");
+        POLYMESH_ASSERT(0 <= h.idx.value && h.idx.value < this->size() && "out of bounds");
+        return {this->mData.get() + h.idx.value * mStride, mStride};
+    }
+    span<std::byte> operator()(index_t h)
+    {
+        POLYMESH_ASSERT(h.is_valid());
+        return {this->mData.get() + h.value * mStride, mStride};
+    }
+
+    span<std::byte const> operator()(index_t h) const
+    {
+        POLYMESH_ASSERT(h.is_valid());
+        return {this->mData.get() + h.value * mStride, mStride};
+    }
+
+    std::byte* data() { return mData.get(); }
+    std::byte const* data() const { return mData.get(); }
+
+    int size() const { return primitive<tag>::all_size(*this->mMesh); }
+    int stride() const { return mStride; }
+    int capacity() const { return primitive<tag>::capacity(*this->mMesh); }
+    size_t byte_size() const override { return size() * mStride; }
+    size_t allocated_byte_size() const override { return capacity() * mStride; }
+
+    /// true iff this attribute is still attached to a mesh
+    /// do not use the attribute if not valid
+    bool is_valid() const { return this->mMesh != nullptr; }
+
+    // methods
+public:
+    /// sets all attribute values to zero
+    void clear()
+    {
+        if (byte_size() > 0)
+            std::memset(this->mData.get(), 0, byte_size());
+    }
+
+    /// Saves ALL data into a vector (includes possibly removed ones)
+    std::vector<std::byte> to_raw_vector() const
+    {
+        auto r = std::vector<std::byte>(byte_size());
+        if (r.size() > 0)
+            std::memcpy(r.data(), this->mData.get(), byte_size());
+        return r;
+    }
+
+    /// returns a new attribute where all elements were bitcast to the given type
+    /// NOTE: requires trivially copyable T with sizeof(T) == stride()
+    template <class T>
+    auto to() const -> attribute<T>
+    {
+        static_assert(std::is_trivially_copyable_v<T>, "only works for trivially copyable types");
+        POLYMESH_ASSERT(sizeof(T) == mStride && "stride must match exactly with type size");
+        auto a = attribute<T>(this->mesh());
+        if (byte_size() > 0)
+            std::memcpy(a.data(), this->mData.get(), byte_size());
+        return a;
+    }
+
+    // public ctor
+public:
+    raw_primitive_attribute() = default;
+    raw_primitive_attribute(Mesh const& mesh, int stride) : primitive_attribute_base<tag>(&mesh), mStride(stride)
+    {
+        POLYMESH_ASSERT(mStride > 0 && "only positive stride supported");
+
+        // register
+        this->register_attr();
+
+        // alloc data (zero-init)
+        this->mData.reset(new std::byte[this->capacity() * mStride]());
+    }
+
+    // members
+protected:
+    unique_array<std::byte> mData;
+    size_t mStride = 0; ///< number of bytes per element
+
+protected:
+    void resize_from(int old_size) override
+    {
+        // mesh is already resized, thus capacity() and size() return new values
+        // old_size is size before resize
+
+        auto new_capacity = this->capacity();
+        auto shared_size = std::min(this->size(), old_size);
+        POLYMESH_ASSERT(shared_size <= new_capacity && "size cannot exceed capacity");
+
+        // alloc new data
+        auto new_data = new_capacity > 0 ? new std::byte[new_capacity * mStride]() : nullptr;
+
+        // copy shared region to new data
+        if (shared_size > 0)
+            std::memcpy(new_data, this->mData.get(), shared_size * mStride);
+
+        // replace old data
+        this->mData.reset(new_data);
+    }
+    void clear_with_default() override { std::memset(this->mData.get(), 0, byte_size()); }
+
+    void apply_remapping(std::vector<int> const& map) override
+    {
+        // TODO: could be made faster by special casing a few stride sizes
+        for (auto i = 0u; i < map.size(); ++i)
+            std::memcpy(&this->mData[i], &this->mData[map[i]], mStride);
+    }
+    void apply_transpositions(std::vector<std::pair<int, int>> const& ts) override
+    {
+        for (auto t : ts)
+            for (auto i = 0u; i < mStride; ++i)
+                swap(this->mData[t.first * mStride + i], this->mData[t.second * mStride + i]);
+    }
+
+    template <class MeshT>
+    friend struct low_level_attribute_api;
+
+    // move & copy
+public:
+    raw_primitive_attribute(raw_primitive_attribute const& rhs) noexcept // copy
+      : primitive_attribute_base<tag>(rhs.mMesh), mStride(rhs.mStride)
+    {
+        // register attr
+        this->register_attr();
+
+        // alloc data
+        this->mData.reset(new std::byte[this->capacity() * mStride]());
+
+        // copy valid data
+        std::memcpy(this->mData.get(), rhs.mData.get(), rhs.byte_size());
+    }
+    raw_primitive_attribute(raw_primitive_attribute&& rhs) noexcept // move
+      : primitive_attribute_base<tag>(rhs.mMesh), mStride(rhs.mStride)
+    {
+        // take data from rhs
+        this->mData = std::move(rhs.mData);
+
+        // deregister rhs
+        rhs.deregister_attr();
+
+        // register lhs
+        this->register_attr();
+    }
+    raw_primitive_attribute& operator=(raw_primitive_attribute const& rhs) noexcept // copy assign
+    {
+        if (this == &rhs) // prevent self-copy
+            return *this;
+
+        // save old capacity for no-realloc path
+        auto old_capacity_bytes = is_valid() ? this->capacity() * mStride : 0;
+
+        // deregister from old mesh
+        this->deregister_attr();
+
+        // register into new mesh
+        this->mMesh = rhs.mMesh;
+        this->mStride = rhs.mStride;
+        this->register_attr();
+
+        // realloc if new capacity
+        auto new_capacity_bytes = this->capacity() * mStride;
+        if (old_capacity_bytes != new_capacity_bytes)
+        {
+            if (new_capacity_bytes == 0)
+                this->mData.reset();
+            else
+                this->mData.reset(new std::byte[new_capacity_bytes]());
+        }
+
+        // copy valid AND defaulted data
+        std::memcpy(this->mData.get(), rhs.mData.get(), new_capacity_bytes);
+
+        return *this;
+    }
+    raw_primitive_attribute& operator=(raw_primitive_attribute&& rhs) noexcept // move assign
+    {
+        if (this == &rhs) // prevent self-move
+            return *this;
+
+        // deregister from old mesh
+        this->deregister_attr();
+
+        // register into new mesh
+        this->mMesh = rhs.mMesh;
+        this->register_attr();
+
+        // take data of rhs
+        this->mStride = rhs.mStride;
+        this->mData = std::move(rhs.mData);
+
+        // deregister rhs
+        rhs.deregister_attr();
+
+        return *this;
+    }
+};
+
+struct raw_vertex_attribute final : raw_primitive_attribute<vertex_tag>
+{
+    using raw_primitive_attribute<vertex_tag>::raw_primitive_attribute;
+};
+struct raw_face_attribute final : raw_primitive_attribute<face_tag>
+{
+    using raw_primitive_attribute<face_tag>::raw_primitive_attribute;
+};
+struct raw_edge_attribute final : raw_primitive_attribute<edge_tag>
+{
+    using raw_primitive_attribute<edge_tag>::raw_primitive_attribute;
+};
+struct raw_halfedge_attribute final : raw_primitive_attribute<halfedge_tag>
+{
+    using raw_primitive_attribute<halfedge_tag>::raw_primitive_attribute;
+};
+}
diff --git a/src/polymesh/detail/topology_iterator.hh b/src/polymesh/detail/topology_iterator.hh
index 7dbeb6b..90bafd0 100644
--- a/src/polymesh/detail/topology_iterator.hh
+++ b/src/polymesh/detail/topology_iterator.hh
@@ -2,7 +2,7 @@
 
 #include <queue>
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 #include "primitive_set.hh"
 
 /// CAUTION: these iterators do NOT work like normal iterators where you can make copies!
diff --git a/src/polymesh/detail/unique_array.hh b/src/polymesh/detail/unique_array.hh
index ff82441..1d316b8 100644
--- a/src/polymesh/detail/unique_array.hh
+++ b/src/polymesh/detail/unique_array.hh
@@ -11,7 +11,7 @@ struct unique_array
     using element_type = T;
 
     unique_array() = default;
-    explicit unique_array(int size) { ptr = new T[size]; }
+    explicit unique_array(int size) { ptr = new T[size](); }
     ~unique_array()
     {
         delete[] ptr;
diff --git a/src/polymesh/formats/obj.hh b/src/polymesh/formats/obj.hh
index d61ecbe..eb56a42 100644
--- a/src/polymesh/formats/obj.hh
+++ b/src/polymesh/formats/obj.hh
@@ -3,7 +3,7 @@
 #include <iosfwd>
 #include <string>
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/formats/off.hh b/src/polymesh/formats/off.hh
index 7847a86..18ea1a4 100644
--- a/src/polymesh/formats/off.hh
+++ b/src/polymesh/formats/off.hh
@@ -4,7 +4,7 @@
 #include <iosfwd>
 #include <string>
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/formats/ply.hh b/src/polymesh/formats/ply.hh
index 9f890e2..76b0b1b 100644
--- a/src/polymesh/formats/ply.hh
+++ b/src/polymesh/formats/ply.hh
@@ -3,7 +3,7 @@
 #include <iosfwd>
 #include <string>
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/formats/stl.hh b/src/polymesh/formats/stl.hh
index 347318a..0c5a364 100644
--- a/src/polymesh/formats/stl.hh
+++ b/src/polymesh/formats/stl.hh
@@ -4,7 +4,7 @@
 #include <iosfwd>
 #include <string>
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/impl/impl_attributes.hh b/src/polymesh/impl/impl_attributes.hh
index e17c5d5..b8aa5a8 100644
--- a/src/polymesh/impl/impl_attributes.hh
+++ b/src/polymesh/impl/impl_attributes.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
@@ -189,7 +189,7 @@ primitive_attribute<tag, AttrT>& primitive_attribute<tag, AttrT>::operator=(prim
         if (new_capacity == 0)
             this->mData.reset();
         else
-            this->mData.reset(new AttrT[new_capacity]);
+            this->mData.reset(new AttrT[new_capacity]());
     }
 
     // copy ALL data (valid and defaulted)
@@ -234,7 +234,7 @@ void primitive_attribute<tag, AttrT>::resize_from(int old_size)
     POLYMESH_ASSERT(shared_size <= new_capacity && "size cannot exceed capacity");
 
     // alloc new data
-    auto new_data = new_capacity > 0 ? new AttrT[new_capacity] : nullptr;
+    auto new_data = new_capacity > 0 ? new AttrT[new_capacity]() : nullptr;
 
     // copy shared region to new data
     std::copy_n(this->mData.get(), shared_size, new_data);
diff --git a/src/polymesh/impl/impl_cursors.hh b/src/polymesh/impl/impl_cursors.hh
index 541efe5..78ee003 100644
--- a/src/polymesh/impl/impl_cursors.hh
+++ b/src/polymesh/impl/impl_cursors.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/impl/impl_low_level_api_base.hh b/src/polymesh/impl/impl_low_level_api_base.hh
index 7bdd5f9..a9665a0 100644
--- a/src/polymesh/impl/impl_low_level_api_base.hh
+++ b/src/polymesh/impl/impl_low_level_api_base.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/impl/impl_low_level_api_mutable.hh b/src/polymesh/impl/impl_low_level_api_mutable.hh
index 369c928..b0987e3 100644
--- a/src/polymesh/impl/impl_low_level_api_mutable.hh
+++ b/src/polymesh/impl/impl_low_level_api_mutable.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/impl/impl_mesh.hh b/src/polymesh/impl/impl_mesh.hh
index 892f15a..5af5703 100644
--- a/src/polymesh/impl/impl_mesh.hh
+++ b/src/polymesh/impl/impl_mesh.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 #include "../assert.hh"
 #include "../detail/split_vector.hh"
 
diff --git a/src/polymesh/impl/impl_primitive.hh b/src/polymesh/impl/impl_primitive.hh
index 17d3e04..6aec17e 100644
--- a/src/polymesh/impl/impl_primitive.hh
+++ b/src/polymesh/impl/impl_primitive.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/impl/impl_ranges.hh b/src/polymesh/impl/impl_ranges.hh
index e834348..5725bce 100644
--- a/src/polymesh/impl/impl_ranges.hh
+++ b/src/polymesh/impl/impl_ranges.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/objects/cone.hh b/src/polymesh/objects/cone.hh
index 4e17284..c2244ee 100644
--- a/src/polymesh/objects/cone.hh
+++ b/src/polymesh/objects/cone.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh::objects
 {
diff --git a/src/polymesh/objects/cube.hh b/src/polymesh/objects/cube.hh
index 5389783..5d0c1e0 100644
--- a/src/polymesh/objects/cube.hh
+++ b/src/polymesh/objects/cube.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 #include "../fields.hh"
 
 namespace polymesh
diff --git a/src/polymesh/objects/cylinder.hh b/src/polymesh/objects/cylinder.hh
index 7e576b6..86dfa33 100644
--- a/src/polymesh/objects/cylinder.hh
+++ b/src/polymesh/objects/cylinder.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh::objects
 {
diff --git a/src/polymesh/objects/quad.hh b/src/polymesh/objects/quad.hh
index 229eeab..85433cb 100644
--- a/src/polymesh/objects/quad.hh
+++ b/src/polymesh/objects/quad.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh
 {
diff --git a/src/polymesh/objects/uv_sphere.hh b/src/polymesh/objects/uv_sphere.hh
index a44b2d9..fbbea26 100644
--- a/src/polymesh/objects/uv_sphere.hh
+++ b/src/polymesh/objects/uv_sphere.hh
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../Mesh.hh"
+#include <polymesh/Mesh.hh>
 
 namespace polymesh::objects
 {
diff --git a/src/polymesh/span.hh b/src/polymesh/span.hh
new file mode 100644
index 0000000..db18e1a
--- /dev/null
+++ b/src/polymesh/span.hh
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+
+#include <polymesh/assert.hh>
+#include <polymesh/tmp.hh>
+
+namespace polymesh
+{
+// a non-owning view of a contiguous array of Ts
+// can be read and write (span<const T> vs span<T>)
+// is trivially copyable (and cheap)
+// NOTE: is range-checked via POLYMESH_ASSERT
+template <class T>
+struct span
+{
+    // ctors
+public:
+    constexpr span() = default;
+    constexpr span(T* data, size_t size) : _data(data), _size(size) {}
+    constexpr span(T* d_begin, T* d_end) : _data(d_begin), _size(d_end - d_begin) {}
+    template <size_t N>
+    constexpr span(T (&data)[N]) : _data(data), _size(N)
+    {
+    }
+    template <class Container, std::enable_if_t<tmp::is_contiguous_range<Container, T>, int> = 0>
+    constexpr span(Container&& c) : _data(c.data()), _size(c.size())
+    {
+    }
+
+    // container
+public:
+    constexpr T* begin() const { return _data; }
+    constexpr T* end() const { return _data + _size; }
+
+    constexpr T* data() const { return _data; }
+    constexpr size_t size() const { return _size; }
+    constexpr bool empty() const { return _size == 0; }
+
+    constexpr T& operator[](size_t i) const
+    {
+        POLYMESH_ASSERT(i < _size);
+        return _data[i];
+    }
+
+    constexpr T& front() const
+    {
+        POLYMESH_ASSERT(_size > 0);
+        return _data[0];
+    }
+    constexpr T& back() const
+    {
+        POLYMESH_ASSERT(_size > 0);
+        return _data[_size - 1];
+    }
+
+    // subviews
+public:
+    constexpr span first(size_t n) const
+    {
+        POLYMESH_ASSERT(n <= _size);
+        return {_data, n};
+    }
+    constexpr span last(size_t n) const
+    {
+        POLYMESH_ASSERT(n <= _size);
+        return {_data + (_size - n), n};
+    }
+    constexpr span subspan(size_t offset, size_t count) const
+    {
+        POLYMESH_ASSERT(offset + count <= _size);
+        return {_data + offset, count};
+    }
+
+private:
+    T* _data = nullptr;
+    size_t _size = 0;
+};
+}
diff --git a/src/polymesh/tmp.hh b/src/polymesh/tmp.hh
index 527247d..7801d2c 100644
--- a/src/polymesh/tmp.hh
+++ b/src/polymesh/tmp.hh
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cstddef>
 #include <utility>
 
 namespace polymesh
@@ -99,5 +100,49 @@ struct constant_rational
     }
 };
 
+namespace detail
+{
+template <class Container, class ElementT>
+auto contiguous_range_test(Container* c) -> decltype(static_cast<ElementT*>(c->data()), static_cast<size_t>(c->size()), 0);
+template <class Container, class ElementT>
+char contiguous_range_test(...);
+
+template <class Container, class ElementT, class = void>
+struct is_range_t : std::false_type
+{
+};
+template <class ElementT, size_t N>
+struct is_range_t<ElementT[N], ElementT> : std::true_type
+{
+};
+template <class ElementT, size_t N>
+struct is_range_t<ElementT[N], ElementT const> : std::true_type
+{
+};
+template <class ElementT, size_t N>
+struct is_range_t<ElementT (&)[N], ElementT> : std::true_type
+{
+};
+template <class ElementT, size_t N>
+struct is_range_t<ElementT (&)[N], ElementT const> : std::true_type
+{
+};
+template <class Container, class ElementT>
+struct is_range_t<Container,
+                  ElementT,
+                  std::void_t<                                                              //
+                      decltype(static_cast<ElementT&>(*std::declval<Container>().begin())), //
+                      decltype(std::declval<Container>().end())                             //
+                      >> : std::true_type
+{
+};
+}
+
+template <class Container, class ElementT>
+static constexpr bool is_contiguous_range = sizeof(detail::contiguous_range_test<Container, ElementT>(nullptr)) == sizeof(int);
+
+template <class Container, class ElementT>
+static constexpr bool is_range = detail::is_range_t<Container, ElementT>::value;
+
 } // namespace tmp
 } // namespace polymesh
-- 
GitLab