Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / tools / 3d / build_clodtex / lod_texture_builder.cpp
blob05bb184bb72b760731fa6a4a22fade4a7a348c3f
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "lod_texture_builder.h"
21 #include "nel/misc/aabbox.h"
24 using namespace std;
25 using namespace NLMISC;
26 using namespace NL3D;
29 // ***************************************************************************
30 // Quality factor. Useful to get maximum of interseting values in the range 0..255.
31 #define NL_LTB_MAX_DISTANCE_QUALITY_FACTOR 0.05f
32 // The bigger, the lower the influence of the normal is. must be >=0
33 #define NL_LTB_NORMAL_BIAS 1.f
36 // ***************************************************************************
37 CLodTextureBuilder::CLodTextureBuilder()
39 _OverSampleDistance= 0.05f;
43 // ***************************************************************************
44 float CLodTextureBuilder::computeQualityPixel(const CPixelInfo &p0, const CPixelInfo &p1) const
46 float d= (p1.Pos-p0.Pos).norm();
47 float dotProd= p1.Normal*p0.Normal;
48 // With NL_NORMAL_BIAS==1, it return froms d*1(normals aligned) to d*3 (opposite normals)
49 return d*(NL_LTB_NORMAL_BIAS+1-dotProd);
52 // ***************************************************************************
53 void CLodTextureBuilder::setLod(const NL3D::CLodCharacterShapeBuild &lod)
55 uint i;
57 _CLod= lod;
59 // **** compute the max quality distance. ie where CTUVQ.Q== 255
60 // build a bbox around the lod.
61 CAABBox bbox;
62 bbox.setCenter(lod.Vertices[0]);
63 for(i=0;i<lod.Vertices.size();i++)
64 bbox.extend(lod.Vertices[i]);
65 /* get the opposite vertices/normals and so compute the max qualityPixel "possible" (it is still possible
66 for the CMesh to be completely out of the bbox of the CLod, but doesn't matter)
68 CPixelInfo p0, p1;
69 p0.Pos= bbox.getMin();
70 p0.Normal.set(1,0,0);
71 p1.Pos= bbox.getMax();
72 p1.Normal.set(-1,0,0);
73 _MaxDistanceQuality= computeQualityPixel(p0, p1);
74 // apply a user factor.
75 _MaxDistanceQuality*= NL_LTB_MAX_DISTANCE_QUALITY_FACTOR;
78 // ***************************************************************************
79 bool CLodTextureBuilder::computeTexture(const CMesh &mesh, NL3D::CLodCharacterTexture &text)
81 // a set to flag if an edge has already been overSampled
82 TEdgeSet edgeSet;
84 // **** Over sample the mesh
85 _Samples.clear();
86 _Samples.reserve(1000);
87 // Get vertex info
88 const CVertexBuffer &VB= mesh.getVertexBuffer();
89 CVertexBufferRead vba;
90 VB.lock (vba);
91 const uint8 *srcPos= (const uint8*)vba.getVertexCoordPointer();
92 const uint8 *srcNormal= (const uint8*)vba.getNormalCoordPointer();
93 const uint8 *srcUV= (const uint8*)vba.getTexCoordPointer();
94 uint vertexSize= VB.getVertexSize();
95 // For all matrix blocks..
96 for(uint mb=0; mb<mesh.getNbMatrixBlock();mb++)
98 // for all rdrPass
99 for(uint rp=0; rp<mesh.getNbRdrPass(mb);rp++)
101 const CIndexBuffer &pb= mesh.getRdrPassPrimitiveBlock(mb, rp);
102 CIndexBufferRead iba;
103 pb.lock (iba);
104 uint matId= mesh.getRdrPassMaterial(mb, rp);
105 // samples the tris of this pass
106 if (iba.getFormat() == CIndexBuffer::Indices16)
108 addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint16 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
110 else
112 nlassert(iba.getFormat() == CIndexBuffer::Indices32);
113 addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint32 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
119 // **** compute the texture, with the samples
120 computeTextureFromSamples(text);
122 return true;
125 // ***************************************************************************
126 bool CLodTextureBuilder::computeTexture(const CMeshMRM &meshMRM, NL3D::CLodCharacterTexture &text)
128 // a set to flag if an edge has already been overSampled
129 TEdgeSet edgeSet;
131 // **** Over sample the mesh
132 _Samples.clear();
133 _Samples.reserve(1000);
134 // Get vertex info
135 CVertexBuffer &VB= const_cast<CVertexBuffer&>(meshMRM.getVertexBuffer());
136 CVertexBufferRead vba;
137 VB.lock (vba);
138 const uint8 *srcPos= (const uint8*)vba.getVertexCoordPointer();
139 const uint8 *srcNormal= (const uint8*)vba.getNormalCoordPointer();
140 const uint8 *srcUV= (const uint8*)vba.getTexCoordPointer();
141 nlassert(srcPos);
142 nlassert(srcNormal);
143 nlassert(srcUV);
144 uint vertexSize = VB.getVertexSize();
145 // For the more precise lod
146 uint lodId= meshMRM.getNbLod()-1;
147 // Resolve Geomoprh problem: copy End to all geomorphs dest. Hence sure that all ids points to good vertex data
148 const std::vector<CMRMWedgeGeom> &geoms= meshMRM.getMeshGeom().getGeomorphs(lodId);
149 for(uint gm=0;gm<geoms.size();gm++)
151 uint srcId= geoms[gm].End;
152 // copy the geom src to the dest VB place.
153 *(CVector*)(srcPos+gm*vertexSize)= *(CVector*)(srcPos+srcId*vertexSize);
154 *(CVector*)(srcNormal+gm*vertexSize)= *(CVector*)(srcNormal+srcId*vertexSize);
155 *(CUV*)(srcUV+gm*vertexSize)= *(CUV*)(srcUV+srcId*vertexSize);
157 // for all rdrPass
158 for(uint rp=0; rp<meshMRM.getNbRdrPass(lodId);rp++)
160 const CIndexBuffer &pb= meshMRM.getRdrPassPrimitiveBlock(lodId, rp);
161 CIndexBufferRead iba;
162 pb.lock (iba);
163 uint matId= meshMRM.getRdrPassMaterial(lodId, rp);
164 // samples the tris of this pass
165 if (iba.getFormat() == CIndexBuffer::Indices16)
167 addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint16 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
169 else
171 nlassert(iba.getFormat() == CIndexBuffer::Indices32);
172 addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint32 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
177 // **** compute the texture, with the samples
178 computeTextureFromSamples(text);
180 return true;
184 // ***************************************************************************
185 bool CLodTextureBuilder::computeTexture(const CMeshMRMSkinned &meshMRM, NL3D::CLodCharacterTexture &text)
187 // a set to flag if an edge has already been overSampled
188 TEdgeSet edgeSet;
190 // **** Over sample the mesh
191 _Samples.clear();
192 _Samples.reserve(1000);
193 // Get vertex info
194 CVertexBuffer tmp;
195 meshMRM.getVertexBuffer(tmp);
196 CVertexBufferRead vba;
197 tmp.lock (vba);
198 const uint8 *srcPos= (const uint8*)vba.getVertexCoordPointer();
199 const uint8 *srcNormal= (const uint8*)vba.getNormalCoordPointer();
200 const uint8 *srcUV= (const uint8*)vba.getTexCoordPointer();
201 uint vertexSize= tmp.getVertexSize();
202 // For the more precise lod
203 uint lodId= meshMRM.getNbLod()-1;
204 // Resolve Geomoprh problem: copy End to all geomorphs dest. Hence sure that all ids points to good vertex data
205 const std::vector<CMRMWedgeGeom> &geoms= meshMRM.getMeshGeom().getGeomorphs(lodId);
206 for(uint gm=0;gm<geoms.size();gm++)
208 uint srcId= geoms[gm].End;
209 // copy the geom src to the dest VB place.
210 *(CVector*)(srcPos+gm*vertexSize)= *(CVector*)(srcPos+srcId*vertexSize);
211 *(CVector*)(srcNormal+gm*vertexSize)= *(CVector*)(srcNormal+srcId*vertexSize);
212 *(CUV*)(srcUV+gm*vertexSize)= *(CUV*)(srcUV+srcId*vertexSize);
214 // for all rdrPass
215 for(uint rp=0; rp<meshMRM.getNbRdrPass(lodId);rp++)
217 CIndexBuffer pb;
218 meshMRM.getRdrPassPrimitiveBlock(lodId, rp, pb);
219 uint matId= meshMRM.getRdrPassMaterial(lodId, rp);
220 CIndexBufferRead iba;
221 pb.lock (iba);
222 // samples the tris of this pass
223 if (iba.getFormat() == CIndexBuffer::Indices16)
225 addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint16 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
227 else
229 addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint32 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
234 // **** compute the texture, with the samples
235 computeTextureFromSamples(text);
237 return true;
240 // ***************************************************************************
241 void CLodTextureBuilder::addSampleTris(const uint8 *srcPos, const uint8 *srcNormal, const uint8 *srcUV, uint vertexSize,
242 const uint16 *triPointer, uint numTris, uint materialId, TEdgeSet &edgeSet)
244 std::vector<uint32> indices32(triPointer, triPointer + numTris * 3);
245 addSampleTris(srcPos, srcNormal, srcUV, vertexSize, &indices32[0], numTris, materialId, edgeSet);
248 // ***************************************************************************
249 void CLodTextureBuilder::addSampleTris(const uint8 *srcPos, const uint8 *srcNormal, const uint8 *srcUV, uint vertexSize,
250 const uint32 *triPointer, uint numTris, uint materialId, TEdgeSet &edgeSet)
252 nlassert(srcPos);
254 // ensure that the material is in 0..255
255 clamp(materialId, 0U, 255U);
257 for(uint i=0;i<numTris;i++)
259 uint c;
261 // **** get tri indices and tri values.
262 uint idx[3];
263 CVector pos[3];
264 CVector normal[3];
265 CUV uv[3];
266 for(c=0;c<3;c++)
268 idx[c]= *triPointer++;
269 pos[c]= *(CVector*)(srcPos + idx[c]*vertexSize);
270 if(srcNormal)
271 normal[c]= *(CVector*)(srcNormal + idx[c]*vertexSize);
272 else
273 normal[c]= CVector::K;
274 if(srcUV)
275 uv[c]= *(CUV*)(srcUV + idx[c]*vertexSize);
276 else
277 uv[c].set(0,0);
280 // **** Append the 3 vertices in the samples. only if not already done
281 for(c=0;c<3;c++)
283 // special edgeId: vertStart==vertEnd
284 TEdge edgeId;
285 edgeId.first= idx[c];
286 edgeId.second= idx[c];
287 // if success to insert in the set, then must add it to samples
288 if(edgeSet.insert(edgeId).second)
290 CSample sample;
291 sample.P.Pos= pos[c];
292 sample.P.Normal= normal[c];
293 sample.P.Normal.normalize();
294 sample.UV= uv[c];
295 sample.MaterialId= materialId;
296 _Samples.push_back(sample);
300 // **** Append the interior of each edges, according to _OverSampleDistance
301 uint numEdgeOverSamples[3];
302 for(c=0;c<3;c++)
304 uint cNext= (c+1)%3;
305 // get the edgeId
306 TEdge edgeId;
307 edgeId.first= idx[c];
308 edgeId.second= idx[cNext];
309 // compute the number of overSamples to apply to the edge
310 numEdgeOverSamples[c]= (uint)floor( (pos[cNext]-pos[c]).norm()/_OverSampleDistance );
312 // if success to insert in the set, then must add it to samples
313 if(edgeSet.insert(edgeId).second)
315 // Do it only if numOverSamples>=2
316 if(numEdgeOverSamples[c]>=2)
318 // Sample the edge, exclude endPoints.
319 for(uint s=1;s<numEdgeOverSamples[c];s++)
321 float f= s/(float)numEdgeOverSamples[c];
322 // interpoalte the sample
323 CSample sample;
324 sample.P.Pos= pos[c]*(1-f) + pos[cNext]*f;
325 sample.P.Normal= normal[c]*(1-f) + normal[cNext]*f;
326 sample.P.Normal.normalize();
327 sample.UV= uv[c]*(1-f) + uv[cNext]*f;
328 sample.MaterialId= materialId;
329 // add it
330 _Samples.push_back(sample);
336 // **** Append the interior of the triangle, if too many overSample
337 // compute min of over samples
338 uint triOverSample= min(numEdgeOverSamples[0], numEdgeOverSamples[1]);
339 triOverSample= min(triOverSample, numEdgeOverSamples[2]);
340 // If >=3 (at least one vertex in the middle)
341 if(triOverSample>=2)
343 // Interpolate with barycentric coordinate rules
344 uint s,t,v;
345 // Donc't compute on triangles edges.
346 for(s=1;s<triOverSample;s++)
348 for(t=1;t<triOverSample-s;t++)
350 // barycentric sum==1 guaranteed.
351 v= triOverSample-s-t;
352 float fs= s/(float)triOverSample;
353 float ft= t/(float)triOverSample;
354 float fv= v/(float)triOverSample;
355 // compute the sample
356 CSample sample;
357 sample.P.Pos= pos[0]*fs + pos[1]*ft + pos[2]*fv;
358 sample.P.Normal= normal[0]*fs + normal[1]*ft + normal[2]*fv;
359 sample.P.Normal.normalize();
360 sample.UV= uv[0]*fs + uv[1]*ft + uv[2]*fv;
361 sample.MaterialId= materialId;
362 // add it
363 _Samples.push_back(sample);
372 // ***************************************************************************
373 bool CLodTextureBuilder::computeTextureFromSamples(NL3D::CLodCharacterTexture &text)
375 // The lod must have correct width/height
376 if(NL3D_CLOD_TEXT_WIDTH!=_CLod.getTextureInfoWidth() || NL3D_CLOD_TEXT_HEIGHT!=_CLod.getTextureInfoHeight() )
378 nlwarning("ERROR: the _CLod must have a textureInfo size of %d/%d", NL3D_CLOD_TEXT_WIDTH, NL3D_CLOD_TEXT_HEIGHT);
379 return false;
382 // prepare dest.
383 text.Texture.resize(NL3D_CLOD_TEXT_SIZE);
384 CLodCharacterTexture::CTUVQ emptyTUVQ;
385 emptyTUVQ.T= 0;
386 emptyTUVQ.U= 0;
387 emptyTUVQ.V= 0;
388 emptyTUVQ.Q= 255;
389 fill(text.Texture.begin(), text.Texture.end(), emptyTUVQ);
391 // For all pixels.
392 const CPixelInfo *srcPixel= _CLod.getTextureInfoPtr();
393 for(uint i=0;i<NL3D_CLOD_TEXT_SIZE;i++)
395 // if the src pixel is not empty
396 if( !(srcPixel[i]==CPixelInfo::EmptyPixel) )
398 CLodCharacterTexture::CTUVQ &dstTUVQ= text.Texture[i];
400 // For All samples, search the best.
401 float minQuality= FLT_MAX;
402 uint bestIdx= 0;
403 for(uint j=0;j<_Samples.size();j++)
405 float q= computeQualityPixel(srcPixel[i], _Samples[j].P);
406 if(q<minQuality)
408 minQuality= q;
409 bestIdx= j;
413 // Then compress the sample info in the TUVQ.
414 dstTUVQ.T= _Samples[bestIdx].MaterialId;
415 dstTUVQ.U= (uint8)(sint)(_Samples[bestIdx].UV.U*256);
416 dstTUVQ.V= (uint8)(sint)(_Samples[bestIdx].UV.V*256);
417 minQuality= minQuality*255/_MaxDistanceQuality;
418 clamp(minQuality, 0, 255);
419 dstTUVQ.Q= (uint8)minQuality;
423 return true;