#ifndef __VCGFOREMBREE_H #define __VCGFOREMBREE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace vcg; using namespace std; /* @Author: Paolo Fasano @Description: This class aims to integrate intel embree3 with the vcglib giving some basic methods that can be used to build more complex features. */ namespace vcg{ template class EmbreeAdaptor{ RTCDevice device = rtcNewDevice(NULL); RTCScene scene = rtcNewScene(device); //RTCGeometry geometry = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE); int threads; public: EmbreeAdaptor(){} public: EmbreeAdaptor(MeshType &m){ scene = loadVCGMeshInScene(m); } /* @Author: Paolo Fasano @Parameter: Point3f rayDirection, direction the rays are shoot towards @Description: foreach face the barycenter is found and a single ray is shoot. If the ray intersect with something the face color is set to black else is set to white. */ public: void selectVisibleFaces(MeshType &m, Point3f rayDirection, bool incrementalSelect){ if (incrementalSelect == false){ //deselect all previously selected faces for(int i = 0;i unifDirVec; Point3f dir = normalizedDir; rayhit = setRayValues(b, dir, 4); RTCRayQueryContext context; rtcInitRayQueryContext(&context); RTCIntersectArguments intersectArgs; rtcInitIntersectArguments(&intersectArgs); intersectArgs.context = &context; rtcIntersect1(scene, &rayhit, &intersectArgs); //if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID) if (rayhit.ray.tfar == std::numeric_limits::infinity()) m.face[i].SetS(); } rtcReleaseScene(scene); rtcReleaseDevice(device); } /* @Author: Paolo Fasano @Parameter: MeshType &m, reference to a mesh @Description: this method apply some preprocessing over it using standard vcglib methods. Than the mesh is loaded as a new embree geometry inside a new embree scene. The new embree variables are global to the class in order to be used with the other methods. */ public: RTCScene loadVCGMeshInScene(MeshType &m){ RTCScene loaded_scene = rtcNewScene(device); RTCGeometry geometry = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE); //a little mesh preprocessing before adding it to a RTCScene tri::RequirePerVertexNormal(m); tri::UpdateNormal::PerVertexNormalized(m); tri::UpdateNormal::PerFaceNormalized(m); tri::UpdateBounding::Box(m); tri::UpdateFlags::FaceClearV(m); float* vb = (float*) rtcSetNewGeometryBuffer(geometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, 3*sizeof(float), m.VN()); for (int i = 0;i unifDirVec; GenNormal::Fibonacci(nRay,unifDirVec); computeAmbientOcclusion(inputM, unifDirVec); } /* @Author: Paolo Fasano @Parameter: MeshType &m,reference to a mesh. @Parameter: std::vector unifDirVec, vector of direction specified by the user. @Description: for each face from the barycenter this method shoots n rays towards some user generated directions(to infinity). If the ray direction is not pointing inside than the ray is actually shoot. If the ray intersect something than the face quality of the mesh is updated with the normal of the fica multiplied by the direction. One more operation done in the AmbientOcclusion is to calculate the bent normal foreach face and save it in an attribute named "BentNormal" */ public: void computeAmbientOcclusion(MeshType &inputM, std::vector unifDirVec){ tri::UpdateQuality::FaceConstant(inputM,0); typename MeshType::template PerFaceAttributeHandle bentNormal = vcg::tri::Allocator:: template GetPerFaceAttribute(inputM,string("BentNormal")); #pragma omp parallel shared(inputM) { #pragma omp for for(int i = 0;i0){ updateRayDirection(rayhit, dir); rayhit.ray.tfar = std::numeric_limits::infinity(); rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; RTCRayQueryContext context; rtcInitRayQueryContext(&context); RTCIntersectArguments intersectArgs; rtcInitIntersectArguments(&intersectArgs); intersectArgs.context = &context; rtcIntersect1(scene, &rayhit, &intersectArgs); if (rayhit.hit.geomID == RTC_INVALID_GEOMETRY_ID){ bN+=dir; accRays++; inputM.face[i].Q()+=scalarP; } } } bentNormal[i] = bN/accRays; } } tri::UpdateColor::PerFaceQualityGray(inputM); rtcReleaseScene(scene); rtcReleaseDevice(device); } /* @Author: Paolo Fasano @Parameter: MeshType &m, reference to a mesh. @Parameter: int nRay, number of rays that must be generated and shoot. @Parameter: float Tau, the grater this value is the grater the influence of the rays that intersect with some face @Description: for each face from the barycenter this method shoots n rays towards a generated direction(to infinity). If the ray direction is not pointing inside than the ray is actually shoot. If the ray intersect something than the face quality of the mesh is updated with the normal of the fica multiplied by the direction; else, if there are no hits, the face get updated of 1-distanceHit^tau */ public: void computeObscurance(MeshType &inputM, int nRay, float tau){ std::vector unifDirVec; GenNormal::Fibonacci(nRay,unifDirVec); computeObscurance(inputM, unifDirVec, tau); } /* @Author: Paolo Fasano @Parameter: MeshType &m, reference to a mesh. @Parameter: std::vector unifDirVec, vector of direction specified by the user. @Parameter: float Tau, the grater this value is the grater the influence of the rays that intersect with some face @Description: for each face from the barycenter this method shoots n rays towards a generated direction(to infinity). If the ray direction is not pointing inside than the ray is actually shoot. If the ray intersect something than the face quality of the mesh is updated with the normal of the fica multiplied by the direction; else, if there are no hits, the face get updated of 1-distanceHit^tau */ public: void computeObscurance(MeshType &inputM, std::vector unifDirVec, float tau){ tri::UpdateQuality::FaceConstant(inputM,0); #pragma omp parallel { #pragma omp for for(int i = 0;i0){ updateRayDirection(rayhit, dir); rayhit.ray.tfar = std::numeric_limits::infinity(); rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; RTCRayQueryContext context; rtcInitRayQueryContext(&context); RTCIntersectArguments intersectArgs; rtcInitIntersectArguments(&intersectArgs); intersectArgs.context = &context; rtcIntersect1(scene, &rayhit, &intersectArgs); if (rayhit.hit.geomID == RTC_INVALID_GEOMETRY_ID) inputM.face[i].Q()+=scalarP; else inputM.face[i].Q()+=(1-powf(rayhit.ray.tfar,tau)); } } } } tri::UpdateColor::PerFaceQualityGray(inputM); rtcReleaseScene(scene); rtcReleaseDevice(device); } /* @Author: Paolo Fasano @Parameter: MeshType &m, reference to a mesh. @Parameter: int nRay, number of rays that must be generated and shoot. @Parameter: float degree, this variable represents the angle of the cone for which we consider a point as a valid direction @Description: - Use a cone centered around the inward-normal direction of a point on a surface mesh. - Send rays inside the cone to the other side of the mesh. - Calculate the SDF at a point as the weighted average of all ray lengths within one standard deviation from the median of all lengths. - Use inverse angle between the ray and the center of the cone as the weight. - The SDF is invariant to rigid body transformations of the whole mesh and oblivious to local deformations. - Small cone angles are too sensitive to local features, while large opening angles close to 180◦ expose the SDF measure to noise and errors. */ public: void computeSDF(MeshType &inputM, int nRay, float degree){ if (degree >= 180) degree = 120; tri::UpdateQuality::FaceConstant(inputM,0); //first step is to generate the cone of rays, i will generate a sphear and for each face take only the //the ones that fall in the desired degree (in respect to the opposite of face norm) std::vector unifDirVec; GenNormal::Fibonacci(nRay,unifDirVec); for (int i = 0; i < inputM.FN(); i++) { RTCRayHit rayhit = initRayValues(); Point3f b = vcg::Barycenter(inputM.face[i]); updateRayOrigin(rayhit, b); rayhit.ray.tnear = 1e-4; float weight = 0; float weight_sum = 0; float weighted_sum = 0; for(int r = 0; r::infinity(); rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; RTCRayQueryContext context; rtcInitRayQueryContext(&context); RTCIntersectArguments intersectArgs; rtcInitIntersectArguments(&intersectArgs); intersectArgs.context = &context; rtcIntersect1(scene, &rayhit, &intersectArgs); if (rayhit.ray.tfar != std::numeric_limits::infinity()) { weight = 1/angle_dir_b; //nominator and denominator for weigthed avarage weighted_sum += weight * rayhit.ray.tfar; weight_sum += weight; } } } //we assign the result of the weighted average to the quality of the face inputM.face[i].Q() = (weighted_sum / weight_sum); } tri::UpdateColor::PerFaceQualityRamp(inputM); rtcReleaseScene(scene); rtcReleaseDevice(device); } /* @Author: Paolo Fasano @Parameter: MeshType &m, reference to a mesh. @Parameter: int nRay, number of rays that must be generated and shoot. @Description: Given a mesh for each face, for each ray, it detects all the intersections with all the facets (i.e., without stopping at the first intersection), and accumulates the number. The rays are shoot two times each, one inside and one outside the mesh. After shooting all the rays, the facet is flipped if frontHit>backHit. For more informations read: Kenshi Takayama, Alec Jacobson, Ladislav Kavan, and Olga Sorkine-Hornung, A Simple Method for Correcting Facet Orientations in Polygon Meshes Based on Ray Casting, Journal of Computer Graphics Techniques (JCGT), vol. 3, no. 4, 53-63, 2014 Available online http://jcgt.org/published/0003/04/02/ */ public: void computeNormalAnalysis(MeshType &inputM, int nRay,bool parity_computation){ //bool fast_computation = false; std::vector unifDirVec; GenNormal::Fibonacci(nRay,unifDirVec); std::vector unifDirVec_EXPAND; for (Point3f p : unifDirVec) { unifDirVec_EXPAND.push_back(p); unifDirVec_EXPAND.push_back(p*-1); } tri::UpdateSelection::FaceClear(inputM); if (parity_computation){ paritySampling(inputM,unifDirVec_EXPAND); } else{ visibilitySamplig(inputM, unifDirVec_EXPAND); } // Iterate over the selected faces and flip them for (auto& face : inputM.face) { if (face.IsS()) { std::swap(face.V(1), face.V(2)); // Swap the second and third vertices } } tri::UpdateSelection::FaceClear(inputM); } /* @Author: Paolo Fasano @Parameter: Point3f origin, the origin point to search for intersections to @Description: given an origin point this methos counts how many intersection there are starting from there (only the number not the positions coordinates) */ public: int findInterceptNumber(Point3f origin, Point3f direction){ int totInterception = 0; //float totDistance = 0; //float previous_distance = 0; RTCRayHit rayhit; rayhit = setRayValues(origin, direction, 0.5); RTCRayQueryContext context; rtcInitRayQueryContext(&context); RTCIntersectArguments intersectArgs; rtcInitIntersectArguments(&intersectArgs); intersectArgs.context = &context; while(true){ rtcIntersect1(scene, &rayhit, &intersectArgs); if (rayhit.ray.tfar != std::numeric_limits::infinity()){ totInterception++; //totDistance += rayhit.ray.tfar - previous_distance; //previous_distance = rayhit.ray.tfar; // we keep the same origin point and direction but update how far from the face the ray starts shooting rayhit.ray.tnear += rayhit.ray.tfar ;//+ rayhit.ray.tfar * 0.05f; rayhit.ray.tfar = std::numeric_limits::infinity(); } else return totInterception; } return totInterception; } public: void visibilitySamplig(MeshType &inputM, std::vector unifDirVec_EXPAND){ #pragma omp parallel { #pragma omp for for(int i = 0;i::infinity()) { if (scalarP > 0){ frontHit++; frontDistance += rayhit.ray.tfar; } else{ backHit++; backDistance += rayhit.ray.tfar; } } } //std::cout<< "face "<< i <<"front hit: " << frontHit << " backhit "<< backHit << " frontDistance " << frontDistance << " backDistance " << backDistance << endl; if(frontHit < backHit || (frontHit == backHit && frontDistance < backDistance)) inputM.face[i].SetS(); } } //tri::Clean::FlipMesh(inputM,true); rtcReleaseScene(scene); rtcReleaseDevice(device); return; } public: void paritySampling(MeshType &inputM, std::vector unifDirVec_EXPAND){ #pragma omp parallel { #pragma omp for for(int i = 0;i::infinity()) { int n_hits = findInterceptNumber(b,dir); if (scalarP > 0){ frontHit += (n_hits % 2); } else{ backHit += (n_hits % 2); } } } if(frontHit > backHit ) inputM.face[i].SetS(); } } //tri::Clean::FlipMesh(inputM,true); rtcReleaseScene(scene); rtcReleaseDevice(device); return; } /* @Author: Paolo Fasano @Description: This method shoots a single ray from origin toward direction. The return tuple is composed by - bool represents if the ray hit something - point3f the coordinates at which the ray hit something - float distance between point hit and the origin point - int the id of the face hit by the ray If release_resources = true than the scene is deleted from memory after the ray is shoot, if you want to shoot multiple rays from the same scene (without the need to load the mesh again) set it to false NOTE: The following is the RIGHT WAY EmbreeAdaptor adaptor = EmbreeAdaptor(mesh); auto [hit_something, hit_face_coords, hit_distance, id] = adaptor.shoot_ray(origin, direction, false); auto [hit_something, hit_face_coords, hit_distance, id] = adaptor.shoot_ray(origin2, direction2); In a loop EmbreeAdaptor adaptor = EmbreeAdaptor(mesh); for (int i = 0; i < origins.size(); i++){ auto [hit_something, hit_face_coords, hit_distance, id] = adaptor.shoot_ray(origins[i], direction[i], false); } adaptor.release_global_resources() The following code will create an ERROR EmbreeAdaptor adaptor = EmbreeAdaptor(mesh); auto [hit_something, hit_face_coords, hit_distance, id] = adaptor.shoot_ray(origin, direction); auto [hit_something, hit_face_coords, hit_distance, id] = adaptor.shoot_ray(origin2, direction2); */ public: inline std::tuple shoot_ray(Point3f origin, Point3f direction, bool release_resources = true){ return shoot_ray(origin, direction, 1e-4, release_resources); } public: inline std::tuple shoot_ray(Point3f origin, Point3f direction, float tnear, bool release_resources = true){ bool hit_something = false; Point3f hit_face_coords(0.0f, 0.0f, 0.0f); float hit_distance = 0; int hit_face_id = 0; RTCRayHit rayhit = initRayValues(); rayhit = setRayValues(origin, direction, tnear); RTCRayQueryContext context; rtcInitRayQueryContext(&context); RTCIntersectArguments intersectArgs; rtcInitIntersectArguments(&intersectArgs); intersectArgs.context = &context; rtcIntersect1(scene, &rayhit, &intersectArgs); if (rayhit.hit.geomID != RTC_INVALID_GEOMETRY_ID){ hit_something = true; hit_face_id = rayhit.hit.primID; hit_distance = rayhit.ray.tfar; // Calculate the displacement vector along the direction float magnitude = sqrt(direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]); float scaleFactor = hit_distance / magnitude; Point3f displacement(direction[0] * scaleFactor, direction[1] * scaleFactor, direction[2] * scaleFactor); // Calculate the coordinates at the given distance hit_face_coords[0] = origin[0] + displacement[0]; hit_face_coords[1] = origin[1] + displacement[1]; hit_face_coords[2] = origin[2] + displacement[2]; } else{ hit_something = false; hit_face_id = rayhit.hit.primID; hit_distance = rayhit.ray.tfar; } if(release_resources){ release_global_resources(); } return std::make_tuple(hit_something, hit_face_coords, hit_distance, hit_face_id); } public: void release_global_resources(){ rtcReleaseScene(scene); rtcReleaseDevice(device); } /* @Author: Paolo Fasano @Parameter: MeshType &inputM, the the input mesh @Parameter: std::vector origins, the vector of Point3f (coordinates) where the vertex are created (if use_edge is true it represent the origin of the edge as well) @Parameter: std::vector directions, the vector of Point3f (coordinates) where the vertex are created (if use_edge is true it represent the direction of the edge as well) @Parameter: bool add_edge, if true Edges are generated between origins[i] and directions[i] @Description: given a vector of origins and directions, creates a vertex for each origin and each direction; if add_edges is true than it creates a edge between the i-th origin and the i-th direction. note: to save and visualize a edge you must save as follows //your edge declaration must contain the following class MyEdge : public vcg::Edge {}; //your code here int mask = vcg::tri::io::Mask::IOM_VERTCOORD; mask |= vcg::tri::io::Mask::IOM_EDGEINDEX; tri::io::ExporterPLY::Save(YOUR_MESH, "EdgeTest.ply", mask); I SUGGEST TO USE ANY OF THE visualize_ray_shoot ON A EMPTY MESH */ public: inline void visualize_ray_shoot(MeshType &inputM, std::vector origins , std::vector directions, bool add_edge = false){ if (origins.size() == directions.size()){ for (int i = 0; i < origins.size(); i++){ visualize_ray_shoot(inputM, origins[i], directions[i], add_edge); } } else std::cout<<"Error executing visualize_ray_shoot: std::vector origins and std::vector directions must have same size"<::AddEdge(inputM, origin, direction); } else{ tri::Allocator::AddVertex(inputM, origin); tri::Allocator::AddVertex(inputM, direction); } } public: inline void visualize_ray_shoot(MeshType &inputM, Point3f origin , std::vector directions, bool add_edge = false){ tri::Allocator::AddVertex(inputM, origin); for (int i = 0; i < directions.size(); i++){ tri::Allocator::AddVertex(inputM, directions[i]); if (add_edge){ tri::Allocator::AddEdge(inputM, &inputM.vert[inputM.vert.size()-i-2], &inputM.vert[inputM.vert.size()-1]); } } } public: void print_ray_informations(RTCRayHit rayhit){ std::cout<< "origin of ray " << rayhit.ray.org_x<< " " << rayhit.ray.org_y<< " " << rayhit.ray.org_z<< " " <::infinity()){ RTCRayHit rayhit = initRayValues(); updateRayOrigin(rayhit, origin); updateRayDirection(rayhit, direction); rayhit.ray.tnear = tnear; rayhit.ray.tfar = tfar; return rayhit; } }; } #endif