// Copyright 2016 The Draco Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef DRACO_COMPRESSION_MESH_TRAVERSER_MAX_PREDICTION_DEGREE_TRAVERSER_H_ #define DRACO_COMPRESSION_MESH_TRAVERSER_MAX_PREDICTION_DEGREE_TRAVERSER_H_ #include #include "draco/compression/mesh/traverser/traverser_base.h" #include "draco/mesh/corner_table.h" namespace draco { // PredictionDegreeTraverser provides framework for traversal over a corner // table data structure following paper "Multi-way Geometry Encoding" by // Cohen-or at al.'02. The traversal is implicitly guided by prediction degree // of the destination vertices. A prediction degree is computed as the number of // possible faces that can be used as source points for traversal to the given // destination vertex (see image below, where faces F1 and F2 are already // traversed and face F0 is not traversed yet. The prediction degree of vertex // V is then equal to two). // // X-----V-----o // / \ / \ / \ // / F0\ / \ / F2\ // X-----o-----o-----B // \ F1/ // \ / // A // // The class implements the same interface as the DepthFirstTraverser // (depth_first_traverser.h) and it can be controlled via the same template // trait classes |CornerTableT| and |TraversalObserverT|, that are used // for controlling and monitoring of the traversal respectively. For details, // please see depth_first_traverser.h. template class MaxPredictionDegreeTraverser : public TraverserBase { public: typedef CornerTableT CornerTable; typedef TraversalObserverT TraversalObserver; typedef TraverserBase Base; MaxPredictionDegreeTraverser() {} // Called before any traversing starts. void OnTraversalStart() { prediction_degree_.resize(this->corner_table()->num_vertices(), 0); } // Called when all the traversing is done. void OnTraversalEnd() {} bool TraverseFromCorner(CornerIndex corner_id) { if (prediction_degree_.size() == 0) return true; // Traversal starts from the |corner_id|. It's going to follow either the // right or the left neighboring faces to |corner_id| based on their // prediction degree. traversal_stacks_[0].push_back(corner_id); best_priority_ = 0; // For the first face, check the remaining corners as they may not be // processed yet. const VertexIndex next_vert = this->corner_table()->Vertex(this->corner_table()->Next(corner_id)); const VertexIndex prev_vert = this->corner_table()->Vertex(this->corner_table()->Previous(corner_id)); if (!this->IsVertexVisited(next_vert)) { this->MarkVertexVisited(next_vert); this->traversal_observer().OnNewVertexVisited( next_vert, this->corner_table()->Next(corner_id)); } if (!this->IsVertexVisited(prev_vert)) { this->MarkVertexVisited(prev_vert); this->traversal_observer().OnNewVertexVisited( prev_vert, this->corner_table()->Previous(corner_id)); } const VertexIndex tip_vertex = this->corner_table()->Vertex(corner_id); if (!this->IsVertexVisited(tip_vertex)) { this->MarkVertexVisited(tip_vertex); this->traversal_observer().OnNewVertexVisited(tip_vertex, corner_id); } // Start the actual traversal. while ((corner_id = PopNextCornerToTraverse()) != kInvalidCornerIndex) { FaceIndex face_id(corner_id.value() / 3); // Make sure the face hasn't been visited yet. if (this->IsFaceVisited(face_id)) { // This face has been already traversed. continue; } while (true) { face_id = FaceIndex(corner_id.value() / 3); this->MarkFaceVisited(face_id); this->traversal_observer().OnNewFaceVisited(face_id); // If the newly reached vertex hasn't been visited, mark it and notify // the observer. const VertexIndex vert_id = this->corner_table()->Vertex(corner_id); if (!this->IsVertexVisited(vert_id)) { this->MarkVertexVisited(vert_id); this->traversal_observer().OnNewVertexVisited(vert_id, corner_id); } // Check whether we can traverse to the right and left neighboring // faces. const CornerIndex right_corner_id = this->corner_table()->GetRightCorner(corner_id); const CornerIndex left_corner_id = this->corner_table()->GetLeftCorner(corner_id); const FaceIndex right_face_id( (right_corner_id == kInvalidCornerIndex ? kInvalidFaceIndex : FaceIndex(right_corner_id.value() / 3))); const FaceIndex left_face_id( (left_corner_id == kInvalidCornerIndex ? kInvalidFaceIndex : FaceIndex(left_corner_id.value() / 3))); const bool is_right_face_visited = this->IsFaceVisited(right_face_id); const bool is_left_face_visited = this->IsFaceVisited(left_face_id); if (!is_left_face_visited) { // We can go to the left face. const int priority = ComputePriority(left_corner_id); if (is_right_face_visited && priority <= best_priority_) { // Right face has been already visited and the priority is equal or // better than the best priority. We are sure that the left face // would be traversed next so there is no need to put it onto the // stack. corner_id = left_corner_id; continue; } else { AddCornerToTraversalStack(left_corner_id, priority); } } if (!is_right_face_visited) { // Go to the right face. const int priority = ComputePriority(right_corner_id); if (priority <= best_priority_) { // We are sure that the right face would be traversed next so there // is no need to put it onto the stack. corner_id = right_corner_id; continue; } else { AddCornerToTraversalStack(right_corner_id, priority); } } // Couldn't proceed directly to the next corner break; } } return true; } private: // Retrieves the next available corner (edge) to traverse. Edges are processed // based on their priorities. // Returns kInvalidCornerIndex when there is no edge available. CornerIndex PopNextCornerToTraverse() { for (int i = best_priority_; i < kMaxPriority; ++i) { if (!traversal_stacks_[i].empty()) { const CornerIndex ret = traversal_stacks_[i].back(); traversal_stacks_[i].pop_back(); best_priority_ = i; return ret; } } return kInvalidCornerIndex; } inline void AddCornerToTraversalStack(CornerIndex ci, int priority) { traversal_stacks_[priority].push_back(ci); // Make sure that the best available priority is up to date. if (priority < best_priority_) best_priority_ = priority; } // Returns the priority of traversing edge leading to |corner_id|. inline int ComputePriority(CornerIndex corner_id) { const VertexIndex v_tip = this->corner_table()->Vertex(corner_id); // Priority 0 when traversing to already visited vertices. int priority = 0; if (!this->IsVertexVisited(v_tip)) { const int degree = ++prediction_degree_[v_tip]; // Priority 1 when prediction degree > 1, otherwise 2. priority = (degree > 1 ? 1 : 2); } // Clamp the priority to the maximum number of buckets. if (priority >= kMaxPriority) priority = kMaxPriority - 1; return priority; } // For efficiency reasons, the priority traversal is implemented using buckets // where each buckets represent a stack of available corners for a given // priority. Corners with the highest priority are always processed first. static constexpr int kMaxPriority = 3; std::vector traversal_stacks_[kMaxPriority]; // Keep the track of the best available priority to improve the performance // of PopNextCornerToTraverse() method. int best_priority_; // Prediction degree available for each vertex. IndexTypeVector prediction_degree_; }; } // namespace draco #endif // DRACO_COMPRESSION_MESH_TRAVERSER_MAX_PREDICTION_DEGREE_TRAVERSER_H_