// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 /********************************************************************** *< FILE: shaveVrayStaticVoxelPrim.cpp -- iplementation file DESCRIPTION: Static (non motion blurred) hair primitive CREATED BY: Vladimir Dubovoy HISTORY: created 20-08-2008 ( as part of 3ds Max + VRay hair shaders) merged 31-03-2010 *> **********************************************************************/ #include "assert.h" #include "shaveVrayStaticVoxelPrim.h" #include "shaveVrayInstance.h" //#include "HairVrPrimsSharedFunctions.h" #include "misc_ray.h" #if defined(VRAY30) || defined(VRAY40) using namespace VUtils; #endif shaveVrayStaticVoxelPrim::shaveVrayStaticVoxelPrim(IHairVoxel* voxel,VR::VRayCore *vray, shaveVrayInstance *inst) :shaveVrayVoxelPrim(voxel,vray,inst) #if defined(VRAY30) || defined(VRAY40) ,staticHairTree(NULL) #endif { _firstid() = -1; _pts() = NULL; _uns() = NULL; _vns() = NULL; _strands() = NULL; #if defined(VRAY30) || defined(VRAY40) minLeaf=0.0f; leafCoeff=1.0f; maxDepth=80; const VRaySequenceData &sdata=vray->getSequenceData(); const VRayFrameData &fdata=vray->getFrameData(); threadman=sdata.threadManager; prog=sdata.progress; #if VRAY_DLL_VERSION < 0x36000 tessParams.setFDataParams(fdata.camToWorld.offs, fdata.fov, fdata.imgWidth); #else tessParams.setFDataParams(fdata.camToWorld.offs, fdata.fov, fdata.imgWidth, fdata.worldToCam, fdata.projType, fdata.zoomFractor, fdata.devAspect); #endif tessParams.setSubdivThresh(inst->plugin->edgeLen); #else _pool_Vector().init(sizeof(VR::Vector)*3*numknots(), 1000); #endif } shaveVrayStaticVoxelPrim::~shaveVrayStaticVoxelPrim() { //these should be released in ::collapse() //but we add this code here in any case if(pts()) delete [] pts(); if(uns()) delete [] uns(); if(vns()) delete [] vns(); #if defined(VRAY30) || defined(VRAY40) if(staticHairTree) delete staticHairTree; #endif } /* | from StaticPrimitive */ void shaveVrayStaticVoxelPrim::getBBox (VR::StaticBox &bbox) { //DebugPrint("PRIM> shaveVrayStaticVoxelPrim::getBBox "); if(voxel()) { VERT pmin; VERT pmax; voxel()->GetBbox(pmin,pmax); bbox.pmin.x = pmin.x; bbox.pmin.y = pmin.y; bbox.pmin.z = pmin.z; bbox.pmax.x = pmax.x; bbox.pmax.y = pmax.y; bbox.pmax.z = pmax.z; /*DebugPrint("[%f %f %f][%f %f %f]\n", bbox.pmin.x, bbox.pmin.y, bbox.pmin.z, bbox.pmax.x, bbox.pmax.y, bbox.pmax.z);*/ VR::Vector scnoffs = VR::toVector(sceneOffset()); bbox.pmin -= scnoffs; bbox.pmax -= scnoffs; } } /* | from StaticPrimitive */ bool shaveVrayStaticVoxelPrim::splittable () { //DebugPrint("PRIM> shaveVrayStaticVoxelPrim::splitable\n"); return true; } /* | from StaticPrimitive */ void shaveVrayStaticVoxelPrim::split (int dim, VR::real middle, VR::StaticBox &bLeft, VR::StaticBox &bRight) { //DebugPrint("PRIM> shaveVrayStaticVoxelPrim::split [%i %f]\n",dim,middle); VR::StaticBox bbox; getBBox(bbox); bbox.split(dim, middle, bLeft, bRight); } /* | from StaticPrimitive */ int shaveVrayStaticVoxelPrim::expandable() { return true; } /* | from StaticHairGenerator */ int shaveVrayStaticVoxelPrim::getNumSides() const { return 4; } /* | from StaticHairGenerator */ int shaveVrayStaticVoxelPrim::getNumKnots() { return numknots(); } /* | from StaticHairGenerator */ void shaveVrayStaticVoxelPrim::getSidesUV(float *&uc, float *&vc) { uc = _puc(); vc = _pvc(); } /* | from StaticHairGenerator */ void shaveVrayStaticVoxelPrim::getSidesUV(const float *&uc, const float *&vc) const { uc = puc(); vc = pvc(); //int size = eNumSides*sizeof(float); //memcpy((void*)uc,puc(),size); //memcpy((void*)vc,pvc(),size); } /* | from StaticHairGenerator */ int shaveVrayStaticVoxelPrim::getFlatNormal() const { return true; } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getRadius() { return 1.0f; //not sure } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getLength() { return 100.f; //not sure } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getTaper() { return 0.0f; //not sure } /* | from StaticHairGenerator */ VR::Vector shaveVrayStaticVoxelPrim::getGravity() { return VR::Vector(0.0f, 0.0f, 0.0f); //not sure } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getLengthRand() { return 0.0f; } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getRadiusRand() { return 0.0f; } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getGravityRand() { return 0.0f; } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getDirRand() { return 0.0f; } /* | from StaticHairGenerator */ float shaveVrayStaticVoxelPrim::getBend() { return 0.0f; } int shaveVrayStaticVoxelPrim::generateStrand(int faceIndex, int strandIndex, const VR::Vector *pv, const VR::Vector *nv, VR::Vector *pts, VR::Vector *un, VR::Vector *vn) { assert(false); //maybe we do not need to change array elementws as far as we passed initalized strands return true; } /* | from StaticHairGenerator */ VR::Vector shaveVrayStaticVoxelPrim::getStrandBaryCoords(int faceIndex, int strandIndex) const { return VR::Vector(1.0f, 0.0f, 0.f); } /* | from StaticHairGenerator */ VR::Vector* shaveVrayStaticVoxelPrim::newVectors(int threadIndex) { #if defined(VRAY30) || defined(VRAY40) return NULL; #else return (VR::Vector*) _pool_Vector().newElement(); #endif } /* | from StaticHairGenerator */ void shaveVrayStaticVoxelPrim::deleteVectors(VR::Vector *x, int threadIndex) { _pool_Vector().releaseElement(x); } /* | from GeometryGenerator */ #if defined(VRAY30) VR::Vector shaveVrayStaticVoxelPrim::getGNormal(VR::RSRay &rsray) { return rsray.is.primitive->getGNormal(rsray); } VR::Vector shaveVrayStaticVoxelPrim::getNormal(VR::RSRay &rsray) { return rsray.is.primitive->getNormal(rsray); } #elif defined(VRAY40) VR::ShadeVec shaveVrayStaticVoxelPrim::getGNormal(VR::RSRay &rsray) { return rsray.is.primitive->getGNormal(rsray); } VR::ShadeVec shaveVrayStaticVoxelPrim::getNormal(VR::RSRay &rsray) { return rsray.is.primitive->getNormal(rsray); } #endif #if defined(VRAY30) || defined(VRAY40) /* * ########################################### * Implementation for V-Ray 3.0+ * ########################################### */ int shaveVrayStaticVoxelPrim::initHairData() { const HairType &hair=voxel()->GetHair(); int numHairs=hair.GetNumStrands(); //assume all hairs have the same number of segments _numknots()=hair.face_end[0]-hair.face_start[0]; // hair data will hold data from voxel hairData.numVertices=VR::IntRefList(numHairs); hairData.vertices=VR::VectorRefList(numHairs*_numknots()); hairData.widths=VR::FloatRefList(numHairs*_numknots()); hairData.colors=VR::ColorRefList(numHairs*_numknots()); VR::Vector scnoffs=VR::toVector(sceneOffset()); // fill in our hair data for(int i=0; iplugin->dynHairTessel) { tesselateHairData(tmHairData, &tessHairData, NULL, NULL, tessParams); } return numHairs; } #if defined(VRAY30) void shaveVrayStaticVoxelPrim::storeInGlobalTree(VR::RayServerInterface2 *rayserver) #elif defined(VRAY40) void shaveVrayStaticVoxelPrim::storeInGlobalTree(VR::RayServerInterface *rayserver) #endif { if (!staticHairTree) { int num_hairs=initHairData(); if(firstid()==-1) _firstid()=rayserver->getNewRenderIDArray(num_hairs+1); staticHairTree=new StaticHairTreePrimitive(firstid(), &tmHairData, NULL, 0); } rayserver->storeStaticHairData(staticHairTree, this, firstid(), tmHairData); } int shaveVrayStaticVoxelPrim::expand(VR::DynamicRaycaster< VR::StaticBox > *raycaster, int threadIndex) { int num_hairs=initHairData(); if(firstid()==-1) _firstid()=raycaster->getNewRenderIDArray(num_hairs+1); #if VRAY_DLL_VERSION < 0x31000 staticHairTree=new VR::StaticHairTreePrimitive(firstid()+1, &tmHairData, NULL, true); #else #if VRAY_DLL_VERSION >= 0x40000 RayServerInterface* rayserver = vrayCore->getSequenceData().rayserver; #else RayServerInterface2* rayserver = vrayCore->getSequenceData().rayserver; #endif VoxelTree * tree = rayserver->newStaticVoxelTree(&tmHairData, voxelTreeType_hairTree); staticHairTree=new StaticHairTreePrimitive(firstid()+1, &tmHairData, NULL, tree); #endif #if VRAY_DLL_VERSION < 0x36000 staticHairTree->visibleToCamera=_hinst()->primary_visibility; // should we intersect this hair tree wil cam rays? #else staticHairTree->visibleToCamera = _hinst()->getPrimaryVisibility(); #endif staticHairTree->build(prog, threadman, maxDepth, minLeaf, leafCoeff); raycaster->insertPrimitive(threadIndex, staticHairTree, this, firstid()); int totalVertices=numknots()*num_hairs; uint64 memUsage=staticHairTree->getMemUsage(); return memUsage+3*sizeof(VR::Vector)*totalVertices; } int shaveVrayStaticVoxelPrim::collapse(VR::DynamicRaycaster< VR::StaticBox > *raycaster, int threadIndex) { raycaster->removePrimitive(threadIndex, staticHairTree); uint64 memUsage=staticHairTree->getMemUsage(); delete staticHairTree; staticHairTree=NULL; memUsage+=3*sizeof(Vector)*tmHairData.hairData->vertices.size(); tmHairData.freeMem(); hairData.freeMem(); tessHairData.freeMem(); return memUsage; } void shaveVrayStaticVoxelPrim::getStaticIntersectionData(int strandIdx, int segmentIdx, const VR::Vector* &p, const VR::Vector* &dirs) { int startIdx = tmHairData.hairData->vertexOffsets[strandIdx] + segmentIdx; p = &tmHairData.worldSpaceVertices[startIdx]; dirs = &tmHairData.dirs[startIdx]; } void shaveVrayStaticVoxelPrim::setCommonIntersectionData(VR::RSRay &rsray, void *isd, int strandIdx, int segmentIdx, float wParam, const ShadeVec &gNormal, const ShadeVec &normal) { IntersectionData &isData=*((IntersectionData*) isd); RSIntersection &is=rsray.is; isData.primitive=rsray.is.primitive; isData.skipTag=is.skipTag; isData.sb=getShadeable(); isData.sd=getExtTexMapping(); isData.si=getExtShadeData(); isData.surfaceProps=NULL; isData.volume=NULL; isData.wpointCoeff=is.t; #if defined(VRAY30) isData.wpoint=rsray.p+TracePoint(rsray.dir)*is.t; #elif defined(VRAY40) isData.wpoint=rsray.p+rsray.dir*is.t; #endif isData.gnormal=gNormal; isData.normal=normal; // These will be used to get per-vertex attributes like color, transparency, etc. isData.extra_int[1]=strandIdx; // the hair strand index isData.faceIndex=segmentIdx; // the segment index within a hair strand isData.bary = ShadeVec(wParam, 0.0f, 0.0f); // the length along the segment isData.faceBase = ShadeVec(0.0f, 0.0f, 0.0f); isData.faceEdge0 = ShadeVec(0.0f, 0.0f, 0.0f); isData.faceEdge1 = ShadeVec(0.0f, 0.0f, 0.0f); } #if defined(VRAY30) void shaveVrayStaticVoxelPrim::setCommonIntersectionData(const VR::RayBunchParams& params, VR::PrimitiveIntersections& result, const RAY_IDX idx, int strandIdx, int segmentIdx, float wParam, const VUtils::Vector &gNormal, const VUtils::Vector &normal) { VR::Ireal t = result.rayDistances()[ idx ]; for(int d=0; d<3; d++) result.isectPoints(d)[idx] = params.origins(d)[idx] + params.dirs(d)[idx] * t; for(int d=0; d<3; d++) result.geomNormals(d)[idx] = gNormal[d]; for(int d=0; d<3; d++) result.smoothNormals(d)[idx] = normal[d]; // These will be used to get per-vertex attributes like color, transparency, etc. result.extraInts(1)[idx] = strandIdx; // the hair strand index result.faceIndices()[idx] = segmentIdx; // the segment index within a hair strand VR::Vector bary = VR::Vector(wParam, 0.0f, 0.0f); // the length along the segment for(int d=0; d<3; d++) result.baryCoords(d)[idx] = bary[d]; for(int d=0; d<3; d++) result.facesBase(d)[idx] = 0.0f; for(int d=0; d<3; d++) result.facesEdge0(d)[idx] = 0.0f; for(int d=0; d<3; d++) result.facesEdge1(d)[idx] = 0.0f; } #endif void shaveVrayStaticVoxelPrim::setIntersectionData(VR::RSRay &rsray, void *isd) { VR::StaticHairTreePrimitive *treePrim=static_cast(rsray.is.primitive); int strandIdx=treePrim->getStrandIndex(rsray); int segmentIdx=treePrim->getSegmentIndex(rsray); const VR::Vector *p, *dirs; getStaticIntersectionData(strandIdx, segmentIdx, p, dirs); setCommonIntersectionData(rsray, isd, strandIdx, segmentIdx, treePrim->getWparam(rsray, p), treePrim->getGNormal(rsray, p), treePrim->getNormal(rsray, p, dirs, getFlatNormal())); } #if defined(VRAY30) void shaveVrayStaticVoxelPrim::setIntersectionData(const VR::RayBunchParams& params, VR::PrimitiveIntersections& result, const RAY_IDX* idxs, size_t count) { // Iterate over the active rays for( size_t i = 0; i < count; i++ ) { const RAY_IDX idx = idxs[ i ]; VR::StaticHairTreePrimitive *treePrim=static_cast(result.primitives()[idx]); int strandIdx=treePrim->getStrandIndex(params, result, idx); int segmentIdx=treePrim->getSegmentIndex(params, result, idx); const VR::Vector *p, *dirs; getStaticIntersectionData(strandIdx, segmentIdx, p, dirs); setCommonIntersectionData(params, result, idx, strandIdx, segmentIdx, treePrim->getWparam(params, result, idx, p), treePrim->getGNormal(params, result, idx, p), treePrim->getNormal(params, result, idx, p, dirs, getFlatNormal())); } } #endif VR::ShadeVec shaveVrayStaticVoxelPrim::getHairDir(const VR::VRayContext &rc) const { int strandIdx, segmentIdx; shaveVrayGetHairParams(rc, strandIdx, segmentIdx); int pos=hairData.vertexOffsets[strandIdx]+segmentIdx; float blend=rc.rayresult.bary[0]; VR::ShadeVec result(0.0f, 0.0f, 0.0f); const VR::VectorRefList &vertices=tmHairData.worldSpaceVertices; if (segmentIdx==0) { VR::ShadeVec v=VR::toShadeVec(vertices[pos]); VR::ShadeVec vnext=VR::toShadeVec(vertices[pos+1]); result=normalize0(vnext-v); } else { VR::ShadeVec vprev=VR::toShadeVec(vertices[pos-1]); VR::ShadeVec v=VR::toShadeVec(vertices[pos]); VR::ShadeVec vnext=VR::toShadeVec(vertices[pos+1]); VR::ShadeVec prev_dir = normalize0(v-vprev); VR::ShadeVec curr_dir = normalize0(vnext-v); result=normalize0(prev_dir + (curr_dir - prev_dir)*blend); } return result; } #else //VRAY30 not defined below this line /* * ########################################### * Implementation for V-Ray <= 2.99 * ########################################### */ /* | from StaticPrimitive */ int shaveVrayStaticVoxelPrim::expand (VR::DynamicRaycaster< VR::StaticBox > *raycaster, int threadIndex) { const HairType& hair = voxel()->GetHair(); int numHairs = hair.GetNumStrands(); if(numHairs == 0) return 0; if(firstid() == -1) _firstid() = raycaster->getNewRenderIDArray(numHairs); //assume all hairs have the same number of segments _numknots() = hair.face_end[0] - hair.face_start[0]; int nalloc = numknots()*numHairs; _pts() = new VR::Vector[nalloc]; _uns() = new VR::Vector[nalloc]; _vns() = new VR::Vector[nalloc]; _strands() = new VR::StaticHairStrand[numHairs]; for(int i = 0; i < numHairs; i++) { int pts_offset = i*numknots(); float root_radius = hair.radiusroot[i]; float delta_radius = hair.radiustip[i] - root_radius; VR::Vector initialNormal; VR::Vector sv0, sv1; hair.GetVert(0, sv0.x, sv0.y, sv0.z); hair.GetVert(1, sv1.x, sv1.y, sv1.z); initialNormal=VR::normalize0(sv1-sv0); // Compute the initial tangent vectors - these are later on propagated along the // strand length to avoids issues with flipped tangent vectors. VR::Vector u,v; VR::computeTangentVectors(initialNormal, u, v); for(int j = 0; j < numknots(); j++) { int vert_idx = hair.face_start[i] + j; int pt_idx = pts_offset + j; hair.GetVert(vert_idx,_pt(pt_idx).x,_pt(pt_idx).y,_pt(pt_idx).z); _pt(pt_idx) -= sceneOffset(); if(j == 0) { float radius = root_radius; if (radius<0.0001f) radius=0.0001f; // For the first point, just use the tangent vectors directly _un(pt_idx) = u*radius; _vn(pt_idx) = v*radius; } else { float t = (float)j/(float)(numknots()-1); float radius = root_radius + t*delta_radius; if (radius<0.0001f) radius=0.0001f; VR::Vector v0, v1; hair.GetVert(vert_idx, v1.x, v1.y, v1.z); hair.GetVert(vert_idx-1, v0.x, v0.y, v0.z); VR::Vector d=v1-v0; float dlenSqr=d.lengthSqr(); if (dlenSqr>1e-12f) { VR::Vector nu=u-d*((u*d)/sqrtf(dlenSqr)); // Project the previous u vector on the current cross plane float nuLenSqr=lengthSqr(nu); if (nuLenSqr>1e-12f) { u=nu/sqrtf(nuLenSqr); v=normalize(d^nu); } } _un(pt_idx) = u*radius; _vn(pt_idx) = v*radius; } } _strand(i).init(static_cast(this),pts(pts_offset), uns(pts_offset), vns(pts_offset)); raycaster->insertPrimitive(threadIndex, &_strand(i), static_cast(this), i + firstid()); } #ifdef _DEBUG #ifdef WIN32 assert(_CrtCheckMemory()); #endif #endif return numHairs*sizeof(VR::StaticHairStrand) + numknots()*numHairs*3*sizeof(VR::Vector); } /* | from StaticPrimitive */ int shaveVrayStaticVoxelPrim::collapse (VR::DynamicRaycaster< VR::StaticBox > *raycaster, int threadIndex) { const HairType& hair = voxel()->GetHair(); int numHairs = hair.GetNumStrands(); if(numHairs == 0) return 0; if(strands()) { for (int i=0; i < numHairs; i++) raycaster->removePrimitive(threadIndex, &_strand(i)); delete[] strands(); _strands() = NULL; } if(pts()) { delete [] pts(); _pts() = NULL; } if(uns()) { delete [] uns(); _uns() = NULL; } if(vns()) { delete [] vns(); _vns() = NULL; } _pool_Vector().freeMem(); return numHairs*sizeof(VR::StaticHairStrand) + numknots()*numHairs*3*sizeof(VR::Vector); } /* | from GeometryGenerator */ void shaveVrayStaticVoxelPrim::setIntersectionData(VR::RSRay &rsray, void *isd) { VR::IntersectionData &isData=*((VR::IntersectionData*) isd); VR::RSIntersection &is=rsray.is; //assert(PRIM_TYPE_STATIC_HAIR_SEGMENT_LINE == is.primitive->type()); VR::StaticHairStrand *s=(VR::StaticHairStrand*) (is.primitive->owner); // VR::RayParams* rayparams = (VR::RayParams*)rsray.rayparams; // assert( rayparams->currentPass == RPASS_GI); // assert( rayparams->currentPass == RPASS_LIGHTMAP); isData.primitive=is.primitive; isData.skipTag =is.skipTag; isData.faceIndex= s->ownerIndex - firstid(); if(ownbsdf()) isData.sb = shade(); else isData.sb = shdata(); isData.sd = shdata(); isData.si = /*NULL;*/ shinst(); isData.volume=NULL; isData.bary= VR::Vector(0.5f, 0.5f, 0.5f); isData.wpointCoeff=is.t; isData.gnormal=getGNormal(rsray); isData.gnormal.makeNormalized(); //if(isData.gnormal.length() < 0.99f) //{ // printf("gn %f %f %f\n",isData.gnormal.x, isData.gnormal.y, isData.gnormal.z); // fflush(stdout); //} isData.normal =getNormal(rsray); isData.wpoint=rsray.p + VR::TracePoint(rsray.dir)*is.t; isData.extraf=(float) s->getSegmentIndex((VR::StaticHairSegmentLine*) is.primitive); isData.bary[2]=((VR::StaticHairSegmentLine*) is.primitive)->getWparam(rsray); // Vector *verts=fface->getVerts(); // isData.faceBase=verts[0]; // isData.faceEdge0=verts[1]-verts[0]; // isData.faceEdge1=verts[2]-verts[0]; // We don't have any meaningful data here isData.surfaceProps = NULL; // sfprops(); } void shaveVrayStaticVoxelPrim::setIntersectionData(const VR::RayBunchParams& params, VR::PrimitiveIntersections& result, const RAY_IDX* idxs, size_t count) { for(size_t ii=0; ii(result.primitives()[idx]); VR::StaticHairStrand *strand=static_cast(segment->owner); float t=result.rayDistances()[idx]; for(int d=0; d<3; d++) result.isectPoints(d)[idx]=params.origins(d)[idx]+double(params.dirs(d)[idx])*t; VR::Vector gnormal = segment->getGNormal(params, result, idx); for(int d=0; d<3; d++) result.geomNormals(d)[idx]=gnormal[d]; VR::Vector normal = segment->getNormal(params, result, idx); for(int d=0; d<3; d++) result.smoothNormals(d)[idx]=normal[d]; // These will be used to get per-vertex attributes like color, transparency, etc. result.faceIndices()[idx]=strand->getSegmentIndex(segment); // result.extraFloats(6)[idx]=(float)strand->getSegmentIndex(segment); // the segment index within a hair strand result.extraInts(1)[idx]=strand->ownerIndex - firstid(); // emil 10 Nov VR::Vector bary(0.5f, 0.5f, segment->getWparam(params, result, idx)); // the length along the segment for(int d=0; d<3; d++) result.baryCoords(d)[idx]=bary[d]; for(int d=0; d<3; d++) result.facesBase(d)[idx]=0.0f; for(int d=0; d<3; d++) result.facesEdge0(d)[idx]=0.0f; for(int d=0; d<3; d++) result.facesEdge1(d)[idx]=0.0f; } } #endif //VRAY30