1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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"
25 using namespace NLMISC
;
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
)
59 // **** compute the max quality distance. ie where CTUVQ.Q== 255
60 // build a bbox around the lod.
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)
69 p0
.Pos
= bbox
.getMin();
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
84 // **** Over sample the mesh
86 _Samples
.reserve(1000);
88 const CVertexBuffer
&VB
= mesh
.getVertexBuffer();
89 CVertexBufferRead 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
++)
99 for(uint rp
=0; rp
<mesh
.getNbRdrPass(mb
);rp
++)
101 const CIndexBuffer
&pb
= mesh
.getRdrPassPrimitiveBlock(mb
, rp
);
102 CIndexBufferRead 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
);
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
);
125 // ***************************************************************************
126 bool CLodTextureBuilder::computeTexture(const CMeshMRM
&meshMRM
, NL3D::CLodCharacterTexture
&text
)
128 // a set to flag if an edge has already been overSampled
131 // **** Over sample the mesh
133 _Samples
.reserve(1000);
135 CVertexBuffer
&VB
= const_cast<CVertexBuffer
&>(meshMRM
.getVertexBuffer());
136 CVertexBufferRead 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();
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
);
158 for(uint rp
=0; rp
<meshMRM
.getNbRdrPass(lodId
);rp
++)
160 const CIndexBuffer
&pb
= meshMRM
.getRdrPassPrimitiveBlock(lodId
, rp
);
161 CIndexBufferRead 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
);
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
);
184 // ***************************************************************************
185 bool CLodTextureBuilder::computeTexture(const CMeshMRMSkinned
&meshMRM
, NL3D::CLodCharacterTexture
&text
)
187 // a set to flag if an edge has already been overSampled
190 // **** Over sample the mesh
192 _Samples
.reserve(1000);
195 meshMRM
.getVertexBuffer(tmp
);
196 CVertexBufferRead 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
);
215 for(uint rp
=0; rp
<meshMRM
.getNbRdrPass(lodId
);rp
++)
218 meshMRM
.getRdrPassPrimitiveBlock(lodId
, rp
, pb
);
219 uint matId
= meshMRM
.getRdrPassMaterial(lodId
, rp
);
220 CIndexBufferRead 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
);
229 addSampleTris(srcPos
, srcNormal
, srcUV
, vertexSize
, (uint32
*) iba
.getPtr(), pb
.getNumIndexes()/3, matId
, edgeSet
);
234 // **** compute the texture, with the samples
235 computeTextureFromSamples(text
);
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
)
254 // ensure that the material is in 0..255
255 clamp(materialId
, 0U, 255U);
257 for(uint i
=0;i
<numTris
;i
++)
261 // **** get tri indices and tri values.
268 idx
[c
]= *triPointer
++;
269 pos
[c
]= *(CVector
*)(srcPos
+ idx
[c
]*vertexSize
);
271 normal
[c
]= *(CVector
*)(srcNormal
+ idx
[c
]*vertexSize
);
273 normal
[c
]= CVector::K
;
275 uv
[c
]= *(CUV
*)(srcUV
+ idx
[c
]*vertexSize
);
280 // **** Append the 3 vertices in the samples. only if not already done
283 // special edgeId: vertStart==vertEnd
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
)
291 sample
.P
.Pos
= pos
[c
];
292 sample
.P
.Normal
= normal
[c
];
293 sample
.P
.Normal
.normalize();
295 sample
.MaterialId
= materialId
;
296 _Samples
.push_back(sample
);
300 // **** Append the interior of each edges, according to _OverSampleDistance
301 uint numEdgeOverSamples
[3];
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
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
;
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)
343 // Interpolate with barycentric coordinate rules
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
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
;
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
);
383 text
.Texture
.resize(NL3D_CLOD_TEXT_SIZE
);
384 CLodCharacterTexture::CTUVQ emptyTUVQ
;
389 fill(text
.Texture
.begin(), text
.Texture
.end(), emptyTUVQ
);
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
;
403 for(uint j
=0;j
<_Samples
.size();j
++)
405 float q
= computeQualityPixel(srcPixel
[i
], _Samples
[j
].P
);
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
;