// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 #pragma once #include "../common/geometry.h" #include "../common/buffer.h" #include "half_edge.h" #include "catmullclark_coefficients.h" namespace embree { struct __aligned(64) FinalQuad { Vec3fa vtx[4]; }; template struct __aligned(64) CatmullClark1RingT { ALIGNED_STRUCT_(64); int border_index; //!< edge index where border starts unsigned int face_valence; //!< number of adjacent quad faces unsigned int edge_valence; //!< number of adjacent edges (2*face_valence) float vertex_crease_weight; //!< weight of vertex crease (0 if no vertex crease) DynamicStackArray crease_weight; //!< edge crease weights for each adjacent edge float vertex_level; //!< maximum level of all adjacent edges float edge_level; //!< level of first edge unsigned int eval_start_index; //!< topology dependent index to start evaluation unsigned int eval_unique_identifier; //!< topology dependent unique identifier for this ring Vertex vtx; //!< center vertex DynamicStackArray ring; //!< ring of neighboring vertices public: CatmullClark1RingT () : eval_start_index(0), eval_unique_identifier(0) {} // FIXME: default constructor should be empty /*! calculates number of bytes required to serialize this structure */ __forceinline size_t bytes() const { size_t ofs = 0; ofs += sizeof(border_index); ofs += sizeof(face_valence); assert(2*face_valence == edge_valence); ofs += sizeof(vertex_crease_weight); ofs += face_valence*sizeof(float); ofs += sizeof(vertex_level); ofs += sizeof(edge_level); ofs += sizeof(eval_start_index); ofs += sizeof(eval_unique_identifier); ofs += sizeof(vtx); ofs += edge_valence*sizeof(Vertex); return ofs; } template static __forceinline void store(char* ptr, size_t& ofs, const Ty& v) { *(Ty*)&ptr[ofs] = v; ofs += sizeof(Ty); } template static __forceinline void load(char* ptr, size_t& ofs, Ty& v) { v = *(Ty*)&ptr[ofs]; ofs += sizeof(Ty); } /*! serializes the ring to some memory location */ __forceinline void serialize(char* ptr, size_t& ofs) const { store(ptr,ofs,border_index); store(ptr,ofs,face_valence); store(ptr,ofs,vertex_crease_weight); for (size_t i=0; ii); return ring[i]; } __forceinline const Vertex& back(size_t i) const { assert(edge_valence>=i); return ring[edge_valence-i]; } __forceinline bool has_last_face() const { return (size_t)border_index != (size_t)edge_valence-2; } __forceinline bool has_opposite_front(size_t i) const { return (size_t)border_index != 2*i; } __forceinline bool has_opposite_back(size_t i) const { return (size_t)border_index != ((size_t)edge_valence-2-2*i); } __forceinline BBox3fa bounds() const { BBox3fa bounds ( vtx ); for (size_t i = 0; igetStartVertexIndex()*stride); vertex_crease_weight = h->vertex_crease_weight; HalfEdge* p = (HalfEdge*) h; unsigned i=0; unsigned min_vertex_index = (unsigned)-1; unsigned min_vertex_index_face = (unsigned)-1; edge_level = p->edge_level; vertex_level = 0.0f; do { vertex_level = max(vertex_level,p->edge_level); crease_weight[i/2] = p->edge_crease_weight; assert(p->hasOpposite() || p->edge_crease_weight == float(inf)); /* store first two vertices of face */ p = p->next(); const unsigned index0 = p->getStartVertexIndex(); ring[i++] = Vertex_t::loadu(vertices+index0*stride); if (index0 < min_vertex_index) { min_vertex_index = index0; min_vertex_index_face = i>>1; } p = p->next(); const unsigned index1 = p->getStartVertexIndex(); ring[i++] = Vertex_t::loadu(vertices+index1*stride); p = p->next(); /* continue with next face */ if (likely(p->hasOpposite())) p = p->opposite(); /* if there is no opposite go the long way to the other side of the border */ else { /* find minimum start vertex */ const unsigned index0 = p->getStartVertexIndex(); if (index0 < min_vertex_index) { min_vertex_index = index0; min_vertex_index_face = i>>1; } /*! mark first border edge and store dummy vertex for face between the two border edges */ border_index = i; crease_weight[i/2] = inf; ring[i++] = Vertex_t::loadu(vertices+index0*stride); ring[i++] = vtx; // dummy vertex /*! goto other side of border */ p = (HalfEdge*) h; while (p->hasOpposite()) p = p->opposite()->next(); } } while (p != h); edge_valence = i; face_valence = i >> 1; eval_unique_identifier = min_vertex_index; eval_start_index = min_vertex_index_face; assert( hasValidPositions() ); } __forceinline void subdivide(CatmullClark1RingT& dest) const { dest.edge_level = 0.5f*edge_level; dest.vertex_level = 0.5f*vertex_level; dest.face_valence = face_valence; dest.edge_valence = edge_valence; dest.border_index = border_index; dest.vertex_crease_weight = max(0.0f,vertex_crease_weight-1.0f); dest.eval_start_index = eval_start_index; dest.eval_unique_identifier = eval_unique_identifier; /* calculate face points */ Vertex_t S = Vertex_t(0.0f); for (size_t i=0; i= face_valence) face_index -= face_valence; assert(face_index < face_valence); size_t index0 = 2*face_index+0; if (index0 >= edge_valence) index0 -= edge_valence; assert(index0 < edge_valence); size_t index1 = 2*face_index+1; if (index1 >= edge_valence) index1 -= edge_valence; assert(index1 < edge_valence); size_t index2 = 2*face_index+2; if (index2 >= edge_valence) index2 -= edge_valence; assert(index2 < edge_valence); S += dest.ring[index1] = ((vtx + ring[index1]) + (ring[index0] + ring[index2])) * 0.25f; } /* calculate new edge points */ size_t num_creases = 0; array_t crease_id; for (size_t i=0; i= face_valence) face_index -= face_valence; const float edge_crease = crease_weight[face_index]; dest.crease_weight[face_index] = max(edge_crease-1.0f,0.0f); size_t index = 2*face_index; size_t prev_index = face_index == 0 ? edge_valence-1 : 2*face_index-1; size_t next_index = 2*face_index+1; const Vertex_t v = vtx + ring[index]; const Vertex_t f = dest.ring[prev_index] + dest.ring[next_index]; S += ring[index]; /* fast path for regular edge points */ if (likely(edge_crease <= 0.0f)) { dest.ring[index] = (v+f) * 0.25f; } /* slower path for hard edge rule */ else { crease_id[num_creases++] = face_index; dest.ring[index] = v*0.5f; /* even slower path for blended edge rule */ if (unlikely(edge_crease < 1.0f)) { dest.ring[index] = lerp((v+f)*0.25f,v*0.5f,edge_crease); } } } /* compute new vertex using smooth rule */ const float inv_face_valence = 1.0f / (float)face_valence; const Vertex_t v_smooth = (Vertex_t) madd(inv_face_valence,S,(float(face_valence)-2.0f)*vtx)*inv_face_valence; dest.vtx = v_smooth; /* compute new vertex using vertex_crease_weight rule */ if (unlikely(vertex_crease_weight > 0.0f)) { if (vertex_crease_weight >= 1.0f) { dest.vtx = vtx; } else { dest.vtx = lerp(v_smooth,vtx,vertex_crease_weight); } return; } /* no edge crease rule and dart rule */ if (likely(num_creases <= 1)) return; /* compute new vertex using crease rule */ if (likely(num_creases == 2)) { /* update vertex using crease rule */ const size_t crease0 = crease_id[0], crease1 = crease_id[1]; const Vertex_t v_sharp = (Vertex_t)(ring[2*crease0] + 6.0f*vtx + ring[2*crease1]) * (1.0f / 8.0f); dest.vtx = v_sharp; /* update crease_weights using chaikin rule */ const float crease_weight0 = crease_weight[crease0], crease_weight1 = crease_weight[crease1]; dest.crease_weight[crease0] = max(0.25f*(3.0f*crease_weight0 + crease_weight1)-1.0f,0.0f); dest.crease_weight[crease1] = max(0.25f*(3.0f*crease_weight1 + crease_weight0)-1.0f,0.0f); /* interpolate between sharp and smooth rule */ const float v_blend = 0.5f*(crease_weight0+crease_weight1); if (unlikely(v_blend < 1.0f)) { dest.vtx = lerp(v_smooth,v_sharp,v_blend); } } /* compute new vertex using corner rule */ else { dest.vtx = vtx; } } __forceinline bool isRegular1() const { if (border_index == -1) { if (face_valence == 4) return true; } else { if (face_valence < 4) return true; } return false; } __forceinline size_t numEdgeCreases() const { ssize_t numCreases = 0; for (size_t i=0; i 0.0f; } return numCreases; } enum Type { TYPE_NONE = 0, //!< invalid type TYPE_REGULAR = 1, //!< regular patch when ignoring creases TYPE_REGULAR_CREASES = 2, //!< regular patch when considering creases TYPE_GREGORY = 4, //!< gregory patch when ignoring creases TYPE_GREGORY_CREASES = 8, //!< gregory patch when considering creases TYPE_CREASES = 16 //!< patch has crease features }; __forceinline Type type() const { /* check if there is an edge crease anywhere */ const size_t numCreases = numEdgeCreases(); const bool noInnerCreases = hasBorder() ? numCreases == 2 : numCreases == 0; Type crease_mask = (Type) (TYPE_REGULAR | TYPE_GREGORY); if (noInnerCreases ) crease_mask = (Type) (crease_mask | TYPE_REGULAR_CREASES | TYPE_GREGORY_CREASES); if (numCreases != 0) crease_mask = (Type) (crease_mask | TYPE_CREASES); /* calculate if this vertex is regular */ bool hasBorder = border_index != -1; if (face_valence == 2 && hasBorder) { if (vertex_crease_weight == 0.0f ) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); else if (vertex_crease_weight == float(inf)) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); else return TYPE_CREASES; } else if (vertex_crease_weight != 0.0f) return TYPE_CREASES; else if (face_valence == 3 && hasBorder) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); else if (face_valence == 4 && !hasBorder) return (Type) (crease_mask & (TYPE_REGULAR | TYPE_REGULAR_CREASES | TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); else return (Type) (crease_mask & (TYPE_GREGORY | TYPE_GREGORY_CREASES | TYPE_CREASES)); } __forceinline bool isFinalResolution(float res) const { return vertex_level <= res; } /* computes the limit vertex */ __forceinline Vertex getLimitVertex() const { /* return hard corner */ if (unlikely(std::isinf(vertex_crease_weight))) return vtx; /* border vertex rule */ if (unlikely(border_index != -1)) { const unsigned int second_border_index = border_index+2 >= int(edge_valence) ? 0 : border_index+2; return (4.0f * vtx + (ring[border_index] + ring[second_border_index])) * 1.0f/6.0f; } Vertex_t F( 0.0f ); Vertex_t E( 0.0f ); assert(eval_start_index < face_valence); for (size_t i=0; i= face_valence) index -= face_valence; F += ring[2*index+1]; E += ring[2*index]; } const float n = (float)face_valence; return (Vertex_t)(n*n*vtx+4.0f*E+F) / ((n+5.0f)*n); } /* gets limit tangent in the direction of edge vtx -> ring[0] */ __forceinline Vertex getLimitTangent() const { if (unlikely(std::isinf(vertex_crease_weight))) return ring[0] - vtx; /* border vertex rule */ if (unlikely(border_index != -1)) { if (border_index != (int)edge_valence-2 ) { return ring[0] - vtx; } else { const unsigned int second_border_index = border_index+2 >= int(edge_valence) ? 0 : border_index+2; return (ring[second_border_index] - ring[border_index]) * 0.5f; } } Vertex_t alpha( 0.0f ); Vertex_t beta ( 0.0f ); const size_t n = face_valence; assert(eval_start_index < face_valence); Vertex_t q( 0.0f ); for (size_t i=0; i= face_valence) index -= face_valence; const float a = CatmullClarkPrecomputedCoefficients::table.limittangent_a(index,n); const float b = CatmullClarkPrecomputedCoefficients::table.limittangent_b(index,n); alpha += a * ring[2*index]; beta += b * ring[2*index+1]; } const float sigma = CatmullClarkPrecomputedCoefficients::table.limittangent_c(n); return sigma * (alpha + beta); } /* gets limit tangent in the direction of edge vtx -> ring[edge_valence-2] */ __forceinline Vertex getSecondLimitTangent() const { if (unlikely(std::isinf(vertex_crease_weight))) return ring[2] - vtx; /* border vertex rule */ if (unlikely(border_index != -1)) { if (border_index != 2) { return ring[2] - vtx; } else { const unsigned int second_border_index = border_index+2 >= int(edge_valence) ? 0 : border_index+2; return (ring[border_index] - ring[second_border_index]) * 0.5f; } } Vertex_t alpha( 0.0f ); Vertex_t beta ( 0.0f ); const size_t n = face_valence; assert(eval_start_index < face_valence); for (size_t i=0; i= face_valence) index -= face_valence; size_t prev_index = index == 0 ? face_valence-1 : index-1; // need to be bit-wise exact in cosf eval const float a = CatmullClarkPrecomputedCoefficients::table.limittangent_a(prev_index,n); const float b = CatmullClarkPrecomputedCoefficients::table.limittangent_b(prev_index,n); alpha += a * ring[2*index]; beta += b * ring[2*index+1]; } const float sigma = CatmullClarkPrecomputedCoefficients::table.limittangent_c(n); return sigma* (alpha + beta); } /* gets surface normal */ const Vertex getNormal() const { return cross(getLimitTangent(),getSecondLimitTangent()); } /* returns center of the n-th quad in the 1-ring */ __forceinline Vertex getQuadCenter(const size_t index) const { const Vertex_t &p0 = vtx; const Vertex_t &p1 = ring[2*index+0]; const Vertex_t &p2 = ring[2*index+1]; const Vertex_t &p3 = index == face_valence-1 ? ring[0] : ring[2*index+2]; const Vertex p = (p0+p1+p2+p3) * 0.25f; return p; } /* returns center of the n-th edge in the 1-ring */ __forceinline Vertex getEdgeCenter(const size_t index) const { return (vtx + ring[index*2]) * 0.5f; } bool hasValidPositions() const { for (size_t i=0; i " << c.ring[i]; if (i % 2 == 0) o << " crease = " << c.crease_weight[i/2]; o << embree_endl; } return o; } }; typedef CatmullClark1RingT CatmullClark1Ring3fa; template struct __aligned(64) GeneralCatmullClark1RingT { ALIGNED_STRUCT_(64); typedef CatmullClark1RingT CatmullClark1Ring; struct Face { __forceinline Face() {} __forceinline Face (int size, float crease_weight) : size(size), crease_weight(crease_weight) {} // FIXME: add member that returns total number of vertices int size; // number of vertices-2 of nth face in ring float crease_weight; }; Vertex vtx; DynamicStackArray ring; DynamicStackArray faces; unsigned int face_valence; unsigned int edge_valence; int border_face; float vertex_crease_weight; float vertex_level; //!< maximum level of adjacent edges float edge_level; // level of first edge bool only_quads; // true if all faces are quads unsigned int eval_start_face_index; unsigned int eval_start_vertex_index; unsigned int eval_unique_identifier; public: GeneralCatmullClark1RingT() : eval_start_face_index(0), eval_start_vertex_index(0), eval_unique_identifier(0) {} __forceinline bool isRegular() const { if (border_face == -1 && face_valence == 4) return true; return false; } __forceinline bool has_last_face() const { return border_face != (int)face_valence-1; } __forceinline bool has_second_face() const { return (border_face == -1) || (border_face >= 2); } bool hasValidPositions() const { for (size_t i=0; igetStartVertexIndex()*stride); vertex_crease_weight = h->vertex_crease_weight; HalfEdge* p = (HalfEdge*) h; unsigned int e=0, f=0; unsigned min_vertex_index = (unsigned)-1; unsigned min_vertex_index_face = (unsigned)-1; unsigned min_vertex_index_vertex = (unsigned)-1; edge_level = p->edge_level; vertex_level = 0.0f; do { HalfEdge* p_prev = p->prev(); HalfEdge* p_next = p->next(); const float crease_weight = p->edge_crease_weight; assert(p->hasOpposite() || p->edge_crease_weight == float(inf)); vertex_level = max(vertex_level,p->edge_level); /* find minimum start vertex */ unsigned vertex_index = p_next->getStartVertexIndex(); if (vertex_index < min_vertex_index) { min_vertex_index = vertex_index; min_vertex_index_face = f; min_vertex_index_vertex = e; } /* store first N-2 vertices of face */ unsigned int vn = 0; for (p = p_next; p!=p_prev; p=p->next()) { ring[e++] = Vertex_t::loadu(vertices+p->getStartVertexIndex()*stride); vn++; } faces[f++] = Face(vn,crease_weight); only_quads &= (vn == 2); /* continue with next face */ if (likely(p->hasOpposite())) p = p->opposite(); /* if there is no opposite go the long way to the other side of the border */ else { /* find minimum start vertex */ unsigned vertex_index = p->getStartVertexIndex(); if (vertex_index < min_vertex_index) { min_vertex_index = vertex_index; min_vertex_index_face = f; min_vertex_index_vertex = e; } /*! mark first border edge and store dummy vertex for face between the two border edges */ border_face = f; faces[f++] = Face(2,inf); ring[e++] = Vertex_t::loadu(vertices+p->getStartVertexIndex()*stride); ring[e++] = vtx; // dummy vertex /*! goto other side of border */ p = (HalfEdge*) h; while (p->hasOpposite()) p = p->opposite()->next(); } } while (p != h); edge_valence = e; face_valence = f; eval_unique_identifier = min_vertex_index; eval_start_face_index = min_vertex_index_face; eval_start_vertex_index = min_vertex_index_vertex; assert( hasValidPositions() ); } __forceinline void subdivide(CatmullClark1Ring& dest) const { dest.edge_level = 0.5f*edge_level; dest.vertex_level = 0.5f*vertex_level; dest.face_valence = face_valence; dest.edge_valence = 2*face_valence; dest.border_index = border_face == -1 ? -1 : 2*border_face; // FIXME: dest.vertex_crease_weight = max(0.0f,vertex_crease_weight-1.0f); dest.eval_start_index = eval_start_face_index; dest.eval_unique_identifier = eval_unique_identifier; assert(dest.face_valence <= MAX_RING_FACE_VALENCE); /* calculate face points */ Vertex_t S = Vertex_t(0.0f); for (size_t face=0, v=eval_start_vertex_index; face crease_id; Vertex_t C = Vertex_t(0.0f); for (size_t face=0, j=eval_start_vertex_index; face 0.0f)) { if (vertex_crease_weight >= 1.0f) { dest.vtx = vtx; } else { dest.vtx = lerp(vtx,v_smooth,vertex_crease_weight); } return; } if (likely(num_creases <= 1)) return; /* compute new vertex using crease rule */ if (likely(num_creases == 2)) { const Vertex_t v_sharp = (Vertex_t)(C + 6.0f * vtx) * (1.0f / 8.0f); const float crease_weight0 = faces[crease_id[0]].crease_weight; const float crease_weight1 = faces[crease_id[1]].crease_weight; dest.vtx = v_sharp; dest.crease_weight[crease_id[0]] = max(0.25f*(3.0f*crease_weight0 + crease_weight1)-1.0f,0.0f); dest.crease_weight[crease_id[1]] = max(0.25f*(3.0f*crease_weight1 + crease_weight0)-1.0f,0.0f); const float v_blend = 0.5f*(crease_weight0+crease_weight1); if (unlikely(v_blend < 1.0f)) { dest.vtx = lerp(v_sharp,v_smooth,v_blend); } } /* compute new vertex using corner rule */ else { dest.vtx = vtx; } } void convert(CatmullClark1Ring& dst) const { dst.edge_level = edge_level; dst.vertex_level = vertex_level; dst.vtx = vtx; dst.face_valence = face_valence; dst.edge_valence = 2*face_valence; dst.border_index = border_face == -1 ? -1 : 2*border_face; for (size_t i=0; i ring[0] */ __forceinline Vertex getLimitTangent() const { CatmullClark1Ring cc_vtx; /* fast path for quad only rings */ if (only_quads) { convert(cc_vtx); return cc_vtx.getLimitTangent(); } subdivide(cc_vtx); return 2.0f * cc_vtx.getLimitTangent(); } /* gets limit tangent in the direction of edge vtx -> ring[edge_valence-2] */ __forceinline Vertex getSecondLimitTangent() const { CatmullClark1Ring cc_vtx; /* fast path for quad only rings */ if (only_quads) { convert(cc_vtx); return cc_vtx.getSecondLimitTangent(); } subdivide(cc_vtx); return 2.0f * cc_vtx.getSecondLimitTangent(); } /* gets limit vertex */ __forceinline Vertex getLimitVertex() const { CatmullClark1Ring cc_vtx; /* fast path for quad only rings */ if (only_quads) convert(cc_vtx); else subdivide(cc_vtx); return cc_vtx.getLimitVertex(); } friend __forceinline embree_ostream operator<<(embree_ostream o, const GeneralCatmullClark1RingT &c) { o << "vtx " << c.vtx << " size = " << c.edge_valence << ", border_face = " << c.border_face << ", " << " face_valence = " << c.face_valence << ", edge_level = " << c.edge_level << ", vertex_level = " << c.vertex_level << ", ring: " << embree_endl; for (size_t v=0, f=0; f " << c.ring[i]; if (i == v) o << " crease = " << c.faces[f].crease_weight; o << embree_endl; } } return o; } }; }