diff --git a/include/vsg/all.h b/include/vsg/all.h index d97edbcf90..c62036fac0 100644 --- a/include/vsg/all.h +++ b/include/vsg/all.h @@ -71,6 +71,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include #include #include +#include #include #include #include @@ -265,6 +266,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #include // Input/Output header files +#include #include #include #include diff --git a/include/vsg/core/ConstVisitor.h b/include/vsg/core/ConstVisitor.h index 3f7360bbcf..25f5cefab0 100644 --- a/include/vsg/core/ConstVisitor.h +++ b/include/vsg/core/ConstVisitor.h @@ -54,6 +54,7 @@ namespace vsg class InstanceNode; class InstanceDraw; class InstanceDrawIndexed; + class IntersectionProxy; // forward declare text classes class Text; @@ -360,6 +361,7 @@ namespace vsg virtual void apply(const InstanceNode&); virtual void apply(const InstanceDraw&); virtual void apply(const InstanceDrawIndexed&); + virtual void apply(const IntersectionProxy&); // text virtual void apply(const Text&); diff --git a/include/vsg/core/Visitor.h b/include/vsg/core/Visitor.h index 84abca3d54..c04041ea80 100644 --- a/include/vsg/core/Visitor.h +++ b/include/vsg/core/Visitor.h @@ -54,6 +54,7 @@ namespace vsg class InstanceNode; class InstanceDraw; class InstanceDrawIndexed; + class IntersectionProxy; // forward declare text classes class Text; @@ -360,6 +361,7 @@ namespace vsg virtual void apply(InstanceNode&); virtual void apply(InstanceDraw&); virtual void apply(InstanceDrawIndexed&); + virtual void apply(IntersectionProxy&); // text virtual void apply(Text&); diff --git a/include/vsg/io/ApplyVisitorReader.h b/include/vsg/io/ApplyVisitorReader.h new file mode 100644 index 0000000000..148d590cae --- /dev/null +++ b/include/vsg/io/ApplyVisitorReader.h @@ -0,0 +1,48 @@ +#pragma once + +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include + +namespace vsg +{ + /// Class to wrap a ReaderWriter and apply a Visitor to everything it loads + class VSG_DECLSPEC ApplyVisitorReader : public Inherit + { + public: + ApplyVisitorReader(vsg::ref_ptr in_child, vsg::ref_ptr in_visitor); + ApplyVisitorReader(const ApplyVisitorReader& rhs, const CopyOp& copyop = {}); + + vsg::ref_ptr child; + vsg::ref_ptr visitor; + + void read(Input& input) override; + void write(Output& output) const override; + + vsg::ref_ptr read(const vsg::Path& filename, vsg::ref_ptr options = {}) const override; + vsg::ref_ptr read(std::istream& fin, vsg::ref_ptr options = {}) const override; + vsg::ref_ptr read(const uint8_t* ptr, size_t size, vsg::ref_ptr options = {}) const override; + + bool write(const vsg::Object* object, const vsg::Path& filename, vsg::ref_ptr options = {}) const override; + bool write(const vsg::Object* object, std::ostream& fout, vsg::ref_ptr options = {}) const override; + + bool readOptions(vsg::Options& options, vsg::CommandLine& arguments) const override; + + bool getFeatures(Features& features) const override; + + protected: + mutable std::mutex _visitorMutex; + }; + VSG_type_name(vsg::ApplyVisitorReader); + +} // namespace vsg diff --git a/include/vsg/nodes/IntersectionProxy.h b/include/vsg/nodes/IntersectionProxy.h new file mode 100644 index 0000000000..6d196d1112 --- /dev/null +++ b/include/vsg/nodes/IntersectionProxy.h @@ -0,0 +1,120 @@ +#pragma once + +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include +#include + +namespace vsg +{ + class LineSegmentIntersector; + + /** IntersectionProxy wraps a node with a BVH to accelerate vsg::Intersector operations */ + class VSG_DECLSPEC IntersectionProxy : public Inherit + { + public: + IntersectionProxy(Node* in_original); + IntersectionProxy(const IntersectionProxy& rhs, const CopyOp& copyop = {}); + + void rebuild(ArrayState& arrayState); + + bool valid() const; + + void intersect(LineSegmentIntersector& lineSegmentIntersector) const; + + public: + ref_ptr clone(const CopyOp& copyop = {}) const override { return IntersectionProxy::create(*this, copyop); } + int compare(const Object& rhs) const override; + + template + static void t_traverse(N& node, V& visitor) + { + node.original->accept(visitor); + } + + void traverse(Visitor& visitor) override { t_traverse(*this, visitor); } + void traverse(ConstVisitor& visitor) const override { t_traverse(*this, visitor); } + void traverse(RecordTraversal& visitor) const override { t_traverse(*this, visitor); } + + void read(Input& input) override; + void write(Output& output) const override; + + ref_ptr original; + + protected: + virtual ~IntersectionProxy(); + + struct Triangle + { + vec3 vertex0; + vec3 vertex1; + vec3 vertex2; + }; + static constexpr size_t trisPerLeaf = 16; + struct Leaf + { + std::array tris; + }; + struct NodeRef + { + enum NodeType + { + LEAF, + INTERNAL, + INVALID + }; + NodeType type; + uint32_t index; + }; + struct InternalNode + { + std::array, 2> children; + }; + + std::vector> internalNodes; + std::vector> leaves; + box bounds; + NodeRef boundingVolumeHeirarchy; + + struct TriangleMetadata + { + uint32_t index0; + uint32_t index1; + uint32_t index2; + uint32_t instance; + }; + struct LeafMetadata + { + std::array tris; + }; + + std::vector> leafMetadata; + }; + VSG_type_name(vsg::IntersectionProxy) + + class VSG_DECLSPEC IntersectionOptimizeVisitor : public Inherit + { + public: + using ArrayStateStack = std::vector>; + + IntersectionOptimizeVisitor(ref_ptr initialArrayState = {}); + + void apply(Node& node) override; + + void apply(StateGroup& stateGroup) override; + + protected: + ArrayStateStack arrayStateStack; + }; + VSG_type_name(vsg::IntersectionOptimizeVisitor) +} // namespace vsg diff --git a/include/vsg/utils/Intersector.h b/include/vsg/utils/Intersector.h index 7950c6e456..c35c1cbe9e 100644 --- a/include/vsg/utils/Intersector.h +++ b/include/vsg/utils/Intersector.h @@ -28,6 +28,8 @@ namespace vsg Intersector(ref_ptr initialArrayState = {}); + virtual void reset(ref_ptr initialArrayState = {}); + // // handle traverse of the scene graph // diff --git a/include/vsg/utils/LineSegmentIntersector.h b/include/vsg/utils/LineSegmentIntersector.h index 48ecd70a9e..8f5b043c28 100644 --- a/include/vsg/utils/LineSegmentIntersector.h +++ b/include/vsg/utils/LineSegmentIntersector.h @@ -34,6 +34,10 @@ namespace vsg LineSegmentIntersector(const dvec3& s, const dvec3& e, ref_ptr initialArrayData = {}); LineSegmentIntersector(const Camera& camera, int32_t x, int32_t y, ref_ptr initialArrayData = {}); + void reset(ref_ptr initialArrayData = {}) override; + virtual void reset(const dvec3& s, const dvec3& e, ref_ptr initialArrayData = {}); + virtual void reset(const Camera& camera, int32_t x, int32_t y, ref_ptr initialArrayData = {}); + class VSG_DECLSPEC Intersection : public Inherit { public: @@ -59,6 +63,8 @@ namespace vsg ref_ptr add(const dvec3& coord, double ratio, const IndexRatios& indexRatios, uint32_t instanceIndex); + void apply(const IntersectionProxy& intersectionproxy) override; + void pushTransform(const Transform& transform) override; void popTransform() override; @@ -68,13 +74,16 @@ namespace vsg bool intersectDraw(uint32_t firstVertex, uint32_t vertexCount, uint32_t firstInstance, uint32_t instanceCount) override; bool intersectDrawIndexed(uint32_t firstIndex, uint32_t indexCount, uint32_t firstInstance, uint32_t instanceCount) override; - protected: struct LineSegment { dvec3 start; dvec3 end; }; + const LineSegment& lineSegment() { return _lineSegmentStack.back(); } + + protected: + std::vector _lineSegmentStack; }; VSG_type_name(vsg::LineSegmentIntersector); diff --git a/include/vsg/utils/PolytopeIntersector.h b/include/vsg/utils/PolytopeIntersector.h index f32bc23cc0..fdf8ece1a6 100644 --- a/include/vsg/utils/PolytopeIntersector.h +++ b/include/vsg/utils/PolytopeIntersector.h @@ -32,6 +32,10 @@ namespace vsg /// create intersector for a polytope with window space dimensions, projected into world coords using the Camera's projection and view matrices. PolytopeIntersector(const Camera& camera, double xMin, double yMin, double xMax, double yMax, ref_ptr initialArrayData = {}); + void reset(ref_ptr initialArrayData = {}) override; + virtual void reset(const Polytope& in_polytope, ref_ptr initialArrayData = {}); + virtual void reset(const Camera& camera, double xMin, double yMin, double xMax, double yMax, ref_ptr initialArrayData = {}); + class VSG_DECLSPEC Intersection : public Inherit { public: diff --git a/src/vsg/CMakeLists.txt b/src/vsg/CMakeLists.txt index 26e87199aa..4e1d0dde6a 100644 --- a/src/vsg/CMakeLists.txt +++ b/src/vsg/CMakeLists.txt @@ -50,6 +50,7 @@ set(SOURCES nodes/InstanceNode.cpp nodes/InstanceDraw.cpp nodes/InstanceDrawIndexed.cpp + nodes/IntersectionProxy.cpp lighting/Light.cpp lighting/AmbientLight.cpp @@ -153,6 +154,7 @@ set(SOURCES io/read.cpp io/write.cpp io/mem_stream.cpp + io/ApplyVisitorReader.cpp text/CpuLayoutTechnique.cpp text/GpuLayoutTechnique.cpp diff --git a/src/vsg/core/ConstVisitor.cpp b/src/vsg/core/ConstVisitor.cpp index 4b72f37c51..0b2fd1d1a0 100644 --- a/src/vsg/core/ConstVisitor.cpp +++ b/src/vsg/core/ConstVisitor.cpp @@ -689,6 +689,10 @@ void ConstVisitor::apply(const InstanceDrawIndexed& value) { apply(static_cast(value)); } +void ConstVisitor::apply(const IntersectionProxy& value) +{ + apply(static_cast(value)); +} //////////////////////////////////////////////////////////////////////////////// // diff --git a/src/vsg/core/Visitor.cpp b/src/vsg/core/Visitor.cpp index 377757159c..3fad730115 100644 --- a/src/vsg/core/Visitor.cpp +++ b/src/vsg/core/Visitor.cpp @@ -689,6 +689,10 @@ void Visitor::apply(InstanceDrawIndexed& value) { apply(static_cast(value)); } +void Visitor::apply(IntersectionProxy& value) +{ + apply(static_cast(value)); +} //////////////////////////////////////////////////////////////////////////////// // diff --git a/src/vsg/io/ApplyVisitorReader.cpp b/src/vsg/io/ApplyVisitorReader.cpp new file mode 100644 index 0000000000..7bbe8370d0 --- /dev/null +++ b/src/vsg/io/ApplyVisitorReader.cpp @@ -0,0 +1,93 @@ +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include + +using namespace vsg; + +vsg::ApplyVisitorReader::ApplyVisitorReader(vsg::ref_ptr in_child, vsg::ref_ptr in_visitor) : + child(in_child), + visitor(in_visitor) +{ +} + +vsg::ApplyVisitorReader::ApplyVisitorReader(const ApplyVisitorReader& rhs, const CopyOp& copyop) : + Inherit(rhs), + child(copyop(rhs.child)), + visitor(copyop(rhs.visitor)) +{ +} + +void ApplyVisitorReader::read(Input& input) +{ + input.readObject("child", child); + input.readObject("visitor", visitor); +} + +void ApplyVisitorReader::write(Output& output) const +{ + output.writeObject("child", child); + output.writeObject("visitor", visitor); +} + +vsg::ref_ptr ApplyVisitorReader::read(const vsg::Path& filename, ref_ptr options) const +{ + auto object = child->read(filename, options); + if (object) + { + std::scoped_lock lock(_visitorMutex); + object->accept(*visitor); + } + return object; +} + +vsg::ref_ptr ApplyVisitorReader::read(std::istream& fin, ref_ptr options) const +{ + auto object = child->read(fin, options); + if (object) + { + std::scoped_lock lock(_visitorMutex); + object->accept(*visitor); + } + return object; +} + +vsg::ref_ptr ApplyVisitorReader::read(const uint8_t* ptr, size_t size, vsg::ref_ptr options) const +{ + auto object = child->read(ptr, size, options); + if (object) + { + std::scoped_lock lock(_visitorMutex); + object->accept(*visitor); + } + return object; +} + +bool ApplyVisitorReader::write(const vsg::Object* object, const vsg::Path& filename, ref_ptr options) const +{ + return child->write(object, filename, options); +} + +bool ApplyVisitorReader::write(const vsg::Object* object, std::ostream& fout, vsg::ref_ptr options) const +{ + return child->write(object, fout, options); +} + +bool ApplyVisitorReader::readOptions(vsg::Options& options, vsg::CommandLine& arguments) const +{ + return child->readOptions(options, arguments); +} + +bool ApplyVisitorReader::getFeatures(Features& features) const +{ + return child->getFeatures(features); +} diff --git a/src/vsg/nodes/IntersectionProxy.cpp b/src/vsg/nodes/IntersectionProxy.cpp new file mode 100644 index 0000000000..234094500f --- /dev/null +++ b/src/vsg/nodes/IntersectionProxy.cpp @@ -0,0 +1,393 @@ +/* + +Copyright(c) 2025 Chris Djali + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vsg; + +namespace +{ + struct GetIndicesVisitor : public ConstVisitor + { + ref_ptr ubyte_indices; + ref_ptr ushort_indices; + ref_ptr uint_indices; + + void apply(const BufferInfo& bufferInfo) override + { + bufferInfo.data->accept(*this); + } + + void apply(const ubyteArray& array) override + { + ubyte_indices = &array; + ushort_indices = nullptr; + uint_indices = nullptr; + } + void apply(const ushortArray& array) override + { + ubyte_indices = nullptr; + ushort_indices = &array; + uint_indices = nullptr; + } + void apply(const uintArray& array) override + { + ubyte_indices = nullptr; + ushort_indices = nullptr; + uint_indices = &array; + } + + uint32_t operator[](size_t index) + { + if (ubyte_indices) return ubyte_indices->at(index); + if (ushort_indices) return ushort_indices->at(index); + return uint_indices->at(index); + } + }; +} + +IntersectionProxy::IntersectionProxy(Node* in_original) : + Inherit(), + original(in_original), + internalNodes(), + leaves(), + bounds(), + boundingVolumeHeirarchy({NodeRef::INVALID, 0u}) +{ +} + +IntersectionProxy::IntersectionProxy(const IntersectionProxy& rhs, const CopyOp& copyop) : + Inherit(rhs, copyop), + original(rhs.original), + internalNodes(rhs.internalNodes), + leaves(rhs.leaves), + bounds(rhs.bounds), + boundingVolumeHeirarchy(rhs.boundingVolumeHeirarchy) +{ +} + +void vsg::IntersectionProxy::rebuild(vsg::ArrayState& arrayState) +{ + leaves.clear(); + internalNodes.clear(); + + if (!original) + { + warn("Attempting to build IntersectionProxy for null node."); + return; + } + + // if instancing is used, accessing the nth triangle is a hassle, so grab them upfront + std::vector triangles; + std::vector metadata; + + if (auto* vertexDraw = ::cast(original)) + { + arrayState.apply(*vertexDraw); + + uint32_t lastIndex = vertexDraw->instanceCount > 1 ? (vertexDraw->firstInstance + vertexDraw->instanceCount) : vertexDraw->firstInstance + 1; + uint32_t endVertex = vertexDraw->firstVertex + vertexDraw->vertexCount; + + triangles.reserve(vertexDraw->instanceCount * vertexDraw->vertexCount / 3); + metadata.reserve(triangles.size()); + + for (uint32_t instanceIndex = vertexDraw->firstInstance; instanceIndex < lastIndex; ++instanceIndex) + { + if (auto vertices = arrayState.vertexArray(instanceIndex)) + { + for (uint32_t i = vertexDraw->firstVertex; (i + 2) < endVertex; i += 3) + { + triangles.emplace_back(Triangle{vertices->at(i), vertices->at(i + 1), vertices->at(i + 2)}); + metadata.emplace_back(TriangleMetadata{i, i + 1, i + 2, instanceIndex}); + } + } + } + } + else if (auto* vertexIndexDraw = ::cast(original)) + { + arrayState.apply(*vertexIndexDraw); + + uint32_t lastIndex = vertexIndexDraw->instanceCount > 1 ? (vertexIndexDraw->firstInstance + vertexIndexDraw->instanceCount) : vertexIndexDraw->firstInstance + 1; + uint32_t endIndex = vertexIndexDraw->firstIndex + ((vertexIndexDraw->indexCount + 2) / 3) * 3; + + triangles.reserve(vertexIndexDraw->instanceCount * vertexIndexDraw->indexCount / 3); + metadata.reserve(triangles.size()); + + if (!vertexIndexDraw->indices || !vertexIndexDraw->indices->data) + { + warn("Attempting to build IntersectionProxy for VertexIndexDraw with no indices."); + return; + } + + GetIndicesVisitor indices; + vertexIndexDraw->indices->accept(indices); + + for (uint32_t instanceIndex = vertexIndexDraw->firstInstance; instanceIndex < lastIndex; ++instanceIndex) + { + if (auto vertices = arrayState.vertexArray(instanceIndex)) + { + for (uint32_t i = vertexIndexDraw->firstIndex; i < endIndex; i += 3) + { + triangles.emplace_back(Triangle{vertices->at(indices[i]), vertices->at(indices[i + 1]), vertices->at(indices[i + 2])}); + metadata.emplace_back(TriangleMetadata{indices[i], indices[i + 1], indices[i + 2], instanceIndex}); + } + } + } + } + else + { + warn("Unsupported node type when building IntersectionProxy: ", original->className()); + return; + } + + std::vector barycenters; + barycenters.reserve(triangles.size()); + for (const auto& triangle : triangles) + { + barycenters.emplace_back((triangle.vertex0 + triangle.vertex1 + triangle.vertex2) / 3.f); + } + + std::vector indices; + indices.reserve(triangles.size()); + for (size_t i = 0; i < triangles.size(); ++i) + { + indices.emplace_back(i); + } + + leaves.reserve(triangles.size() / trisPerLeaf); + internalNodes.reserve(triangles.size() / (trisPerLeaf * 2)); + + using itr_t = decltype(indices)::iterator; + + auto computeKDTree = [&](itr_t first, itr_t last, auto&& computeKDTree) -> std::pair { + if (std::distance(first, last) < trisPerLeaf) + { + leaves.emplace_back(); + leafMetadata.emplace_back(); + box bound; + itr_t itr = first; + for (size_t i = 0; i < trisPerLeaf; ++i) + { + if (itr != last) + { + leaves.back().tris[i] = triangles[*itr]; + bound.add(triangles[*itr].vertex0); + bound.add(triangles[*itr].vertex1); + bound.add(triangles[*itr].vertex2); + leafMetadata.back().tris[i] = metadata[*itr]; + ++itr; + } + else + { + // add a degenerate triangle as we have to have trisPerLeaf + leaves.back().tris[i] = {vec3(), vec3(), vec3()}; + } + } + return std::make_pair(bound, NodeRef{NodeRef::LEAF, static_cast(leaves.size() - 1)}); + } + else + { + box baryBound; + for (itr_t itr = first; itr != last; ++itr) + { + baryBound.add(barycenters[*itr]); + } + vec3 range = baryBound.max - baryBound.min; + size_t axisIndex = range.x > range.y ? (range.x > range.z ? 0 : 2) : (range.y > range.z ? 1 : 2); + itr_t midpoint = first + std::distance(first, last) / 2; + std::nth_element(first, midpoint, last, [&, axisIndex](const size_t& lhs, const size_t& rhs) { return barycenters[lhs][axisIndex] < barycenters[rhs][axisIndex]; }); + internalNodes.emplace_back(InternalNode{{computeKDTree(first, midpoint, computeKDTree), computeKDTree(midpoint, last, computeKDTree)}}); + box overallBound; + for (const auto& [bound, ref] : internalNodes.back().children) + { + overallBound.add(bound); + } + return std::make_pair(overallBound, NodeRef{NodeRef::INTERNAL, static_cast(internalNodes.size() - 1)}); + } + }; + + std::tie(bounds, boundingVolumeHeirarchy) = computeKDTree(indices.begin(), indices.end(), computeKDTree); +} + +bool IntersectionProxy::valid() const +{ + return boundingVolumeHeirarchy.type != NodeRef::INVALID; +} + +void vsg::IntersectionProxy::intersect(LineSegmentIntersector& lineSegmentIntersector) const +{ + const auto& ls = lineSegmentIntersector.lineSegment(); + + using value_type = double; + using vec_type = t_vec3; + const value_type epsilon = 1e-10; + + vec_type start(ls.start); + vec_type end(ls.end); + + vec_type d = end - start; + + auto intersectBox = [&](const box& bound) { + value_type t1 = (bound.min.x - start.x) / d.x; + value_type t2 = (bound.max.x - start.x) / d.x; + value_type tmin = std::min(t1, t2); + value_type tmax = std::max(t1, t2); + t1 = (bound.min.y - start.y) / d.y; + t2 = (bound.max.y - start.y) / d.y; + tmin = std::max(tmin, std::min(t1, t2)); + tmax = std::min(tmax, std::max(t1, t2)); + t1 = (bound.min.z - start.z) / d.z; + t2 = (bound.max.z - start.z) / d.z; + tmin = std::max(tmin, std::min(t1, t2)); + tmax = std::min(tmax, std::max(t1, t2)); + return tmax >= tmin && tmin < 1.0 && tmax > 0.0; + }; + + if (!bounds.valid() || !intersectBox(bounds)) + return; + + value_type length = ::length(d); + value_type inverseLength = length != 0.0 ? 1.0 / length : 0.0; + + vec_type dInvX = d.x != 0.0 ? d / d.x : vec_type{0.0, 0.0, 0.0}; + vec_type dInvY = d.y != 0.0 ? d / d.y : vec_type{0.0, 0.0, 0.0}; + vec_type dInvZ = d.z != 0.0 ? d / d.z : vec_type{0.0, 0.0, 0.0}; + + auto intersectLeaf = [&](uint32_t index) { + for (size_t i = 0; i < trisPerLeaf; ++i) + { + const auto& triangle = leaves[index].tris[i]; + + vec_type E1 = vec_type(triangle.vertex1) - vec_type(triangle.vertex0); + vec_type E2 = vec_type(triangle.vertex2) - vec_type(triangle.vertex0); + + vec_type P = cross(d, E2); + value_type det = dot(P, E1); + if (det > -epsilon && det < epsilon) continue; + + value_type inv_det = 1.0 / det; + vec_type T = vec_type(start) - vec_type(triangle.vertex0); + value_type u = inv_det * dot(P, T); + if (u < 0.0 || u > 1) continue; + + vec_type Q = cross(T, E1); + value_type v = inv_det * dot(Q, d); + if (v < 0.0 || u + v > 1.0) continue; + + value_type t = inv_det * dot(Q, E2); + if (t < epsilon) continue; + + value_type r0 = 1.0 - u - v; + value_type r1 = u; + value_type r2 = v; + + dvec3 intersection = dvec3(triangle.vertex0) * double(r0) + dvec3(triangle.vertex1) * double(r1) + dvec3(triangle.vertex2) * double(r2); + const auto& metadata = leafMetadata[index].tris[i]; + lineSegmentIntersector.add(intersection, double(t * inverseLength), {{metadata.index0, r0}, {metadata.index1 + 1, r1}, {metadata.index2 + 2, r2}}, metadata.instance); + } + }; + + auto intersectNode = [&](const NodeRef& nodeRef, auto&& intersectNode) -> void { + if (nodeRef.type == NodeRef::LEAF) + { + intersectLeaf(nodeRef.index); + } + else + { + const auto& node = internalNodes[nodeRef.index]; + for (const auto& [bound, child] : node.children) + { + if (intersectBox(bound)) + { + intersectNode(child, intersectNode); + } + } + } + }; + + intersectNode(boundingVolumeHeirarchy, intersectNode); +} + +IntersectionProxy::~IntersectionProxy() = default; + +int IntersectionProxy::compare(const Object& rhs_object) const +{ + int result = Node::compare(rhs_object); + if (result != 0) return result; + + const auto& rhs = static_cast(rhs_object); + if ((result = compare_pointer(original, rhs.original)) != 0) return result; + // computation of BVH should be deterministic, so if the input is the same and it's actually been computed, we shouldn't need to scan all the nodes + if ((result = compare_value(boundingVolumeHeirarchy.type, rhs.boundingVolumeHeirarchy.type)) != 0) return result; + return compare_value(boundingVolumeHeirarchy.index, boundingVolumeHeirarchy.index); +} + +void IntersectionProxy::read(Input& input) +{ + Node::read(input); + + input.read("original", original); + // todo: deserialise BVH +} + +void IntersectionProxy::write(Output& output) const +{ + Node::write(output); + + output.write("original", original); + // todo: serialise BVH +} + +vsg::IntersectionOptimizeVisitor::IntersectionOptimizeVisitor(ref_ptr initialArrayState) +{ + arrayStateStack.reserve(4); + arrayStateStack.emplace_back(initialArrayState ? initialArrayState : ArrayState::create()); +} + +void vsg::IntersectionOptimizeVisitor::apply(Node& node) +{ + node.traverse(*this); +} + +void vsg::IntersectionOptimizeVisitor::apply(StateGroup& stategroup) +{ + auto arrayState = stategroup.prototypeArrayState ? stategroup.prototypeArrayState->cloneArrayState(arrayStateStack.back()) : arrayStateStack.back()->cloneArrayState(); + + for (auto& statecommand : stategroup.stateCommands) + { + statecommand->accept(*arrayState); + } + + arrayStateStack.emplace_back(arrayState); + + stategroup.traverse(*this); + + for (auto& child : stategroup.children) + { + if (child->className() == "vsg::VertexDraw") + { + auto optimized = vsg::IntersectionProxy::create(child); + optimized->rebuild(*arrayStateStack.back()); + child = optimized; + } + } + + arrayStateStack.pop_back(); +} diff --git a/src/vsg/utils/Intersector.cpp b/src/vsg/utils/Intersector.cpp index fc58b52163..fa9fa9d372 100644 --- a/src/vsg/utils/Intersector.cpp +++ b/src/vsg/utils/Intersector.cpp @@ -48,6 +48,23 @@ Intersector::Intersector(ref_ptr initialArrayState) arrayStateStack.emplace_back(initialArrayState ? initialArrayState : ArrayState::create()); } +void Intersector::reset(ref_ptr initialArrayState) +{ + // don't reset arrayStateStack if we don't have to. + // it should be back in its initial state after a traversal has finished. + if (initialArrayState) + { + arrayStateStack.resize(1); + arrayStateStack.front() = initialArrayState; + } + + ubyte_indices.reset(); + ushort_indices.reset(); + uint_indices.reset(); + + _nodePath.clear(); +} + void Intersector::apply(const Node& node) { PushPopNode ppn(_nodePath, &node); @@ -66,7 +83,7 @@ void Intersector::apply(const StateGroup& stategroup) statecommand->accept(*arrayState); } - arrayStateStack.emplace_back(arrayState); + arrayStateStack.emplace_back(std::move(arrayState)); stategroup.traverse(*this); diff --git a/src/vsg/utils/LineSegmentIntersector.cpp b/src/vsg/utils/LineSegmentIntersector.cpp index 2b385cdad9..aae95b9679 100644 --- a/src/vsg/utils/LineSegmentIntersector.cpp +++ b/src/vsg/utils/LineSegmentIntersector.cpp @@ -10,6 +10,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ +#include #include #include @@ -165,6 +166,54 @@ LineSegmentIntersector::LineSegmentIntersector(const Camera& camera, int32_t x, _lineSegmentStack.push_back(LineSegment{eyeToWorld * eye_near, eyeToWorld * eye_far}); } +void LineSegmentIntersector::reset(ref_ptr initialArrayData) +{ + Intersector::reset(initialArrayData); + + intersections.clear(); +} + +void LineSegmentIntersector::reset(const dvec3& s, const dvec3& e, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _lineSegmentStack.resize(1); + _lineSegmentStack.front() = {s, e}; +} + +void LineSegmentIntersector::reset(const Camera& camera, int32_t x, int32_t y, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _lineSegmentStack.resize(2); + + auto viewport = camera.getViewport(); + + vsg::vec2 ndc(0.0f, 0.0f); + if ((viewport.width > 0) && (viewport.height > 0)) + { + ndc.set((static_cast(x) - viewport.x) / viewport.width, (static_cast(y) - viewport.y) / viewport.height); + } + + auto projectionMatrix = camera.projectionMatrix->transform(); + auto viewMatrix = camera.viewMatrix->transform(); + + bool reverse_depth = (projectionMatrix(2, 2) > 0.0); + + vsg::dvec3 ndc_near(ndc.x * 2.0 - 1.0, ndc.y * 2.0 - 1.0, reverse_depth ? viewport.maxDepth : viewport.minDepth); + vsg::dvec3 ndc_far(ndc.x * 2.0 - 1.0, ndc.y * 2.0 - 1.0, reverse_depth ? viewport.minDepth : viewport.maxDepth); + + auto inv_projectionMatrix = vsg::inverse(projectionMatrix); + vsg::dvec3 eye_near = inv_projectionMatrix * ndc_near; + vsg::dvec3 eye_far = inv_projectionMatrix * ndc_far; + _lineSegmentStack.front() = LineSegment{eye_near, eye_far}; + + dmat4 eyeToWorld = inverse(viewMatrix); + localToWorldStack().push_back(viewMatrix); + worldToLocalStack().push_back(eyeToWorld); + _lineSegmentStack.back() = LineSegment{eyeToWorld * eye_near, eyeToWorld * eye_far}; +} + LineSegmentIntersector::Intersection::Intersection(const dvec3& in_localIntersection, const dvec3& in_worldIntersection, double in_ratio, const dmat4& in_localToWorld, const NodePath& in_nodePath, const DataList& in_arrays, const IndexRatios& in_indexRatios, uint32_t in_instanceIndex) : localIntersection(in_localIntersection), worldIntersection(in_worldIntersection), @@ -188,6 +237,18 @@ ref_ptr LineSegmentIntersector::add(const return intersection; } +void LineSegmentIntersector::apply(const IntersectionProxy& intersectionProxy) +{ + if (intersectionProxy.valid()) + { + intersectionProxy.intersect(*this); + } + else + { + Intersector::apply(intersectionProxy); + } +} + void LineSegmentIntersector::pushTransform(const Transform& transform) { auto& l2wStack = localToWorldStack(); diff --git a/src/vsg/utils/PolytopeIntersector.cpp b/src/vsg/utils/PolytopeIntersector.cpp index bc60959657..5210990fca 100644 --- a/src/vsg/utils/PolytopeIntersector.cpp +++ b/src/vsg/utils/PolytopeIntersector.cpp @@ -243,6 +243,71 @@ PolytopeIntersector::PolytopeIntersector(const Camera& camera, double xMin, doub worldToLocalStack().push_back(eyeToWorld); } +void vsg::PolytopeIntersector::reset(ref_ptr initialArrayData) +{ + Intersector::reset(initialArrayData); + + intersections.clear(); +} + +void vsg::PolytopeIntersector::reset(const Polytope& in_polytope, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _polytopeStack.resize(1); + _polytopeStack.front() = in_polytope; +} + +void vsg::PolytopeIntersector::reset(const Camera& camera, double xMin, double yMin, double xMax, double yMax, ref_ptr initialArrayData) +{ + reset(initialArrayData); + + _polytopeStack.resize(2); + + auto viewport = camera.getViewport(); + + auto projectionMatrix = camera.projectionMatrix->transform(); + auto viewMatrix = camera.viewMatrix->transform(); + bool reverse_depth = (projectionMatrix(2, 2) > 0.0); + + double ndc_xMin = (viewport.width > 0) ? (2.0 * (xMin - static_cast(viewport.x)) / static_cast(viewport.width) - 1.0) : xMin; + double ndc_xMax = (viewport.width > 0) ? (2.0 * (xMax - static_cast(viewport.x)) / static_cast(viewport.width) - 1.0) : xMax; + + double ndc_yMin = (viewport.height > 0) ? (2.0 * (yMin - static_cast(viewport.y)) / static_cast(viewport.height) - 1.0) : yMin; + double ndc_yMax = (viewport.height > 0) ? (2.0 * (yMax - static_cast(viewport.y)) / static_cast(viewport.height) - 1.0) : yMax; + + double ndc_near = reverse_depth ? viewport.maxDepth : viewport.minDepth; + double ndc_far = reverse_depth ? viewport.minDepth : viewport.maxDepth; + + vsg::Polytope clipspace; + clipspace.push_back(dplane(1.0, 0.0, 0.0, -ndc_xMin)); // left + clipspace.push_back(dplane(-1.0, 0.0, 0.0, ndc_xMax)); // right + clipspace.push_back(dplane(0.0, 1.0, 0.0, -ndc_yMin)); // bottom + clipspace.push_back(dplane(0.0, -1.0, 0.0, ndc_yMax)); // top + clipspace.push_back(dplane(0.0, 0.0, -1.0, ndc_near)); // near + clipspace.push_back(dplane(0.0, 0.0, 1.0, ndc_far)); // far + + vsg::Polytope eyespace; + for (auto& pl : clipspace) + { + eyespace.push_back(pl * projectionMatrix); + } + + _polytopeStack.front() = eyespace; + + vsg::Polytope worldspace; + for (auto& pl : eyespace) + { + worldspace.push_back(pl * viewMatrix); + } + + _polytopeStack.back() = worldspace; + + dmat4 eyeToWorld = inverse(viewMatrix); + localToWorldStack().push_back(viewMatrix); + worldToLocalStack().push_back(eyeToWorld); +} + PolytopeIntersector::Intersection::Intersection(const dvec3& in_localIntersection, const dvec3& in_worldIntersection, const dmat4& in_localToWorld, const NodePath& in_nodePath, const DataList& in_arrays, const std::vector& in_indices, uint32_t in_instanceIndex) : localIntersection(in_localIntersection), worldIntersection(in_worldIntersection),