Branch data Line data Source code
1 : :
2 : : /**
3 : : * meshoptimizer - version 0.13
4 : : *
5 : : * Copyright (C) 2016-2019, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
6 : : * Report bugs and download new versions at https://github.com/zeux/meshoptimizer
7 : : *
8 : : * This library is distributed under the MIT License. See notice at the end of this file.
9 : : */
10 : : #pragma once
11 : :
12 : : #include <assert.h>
13 : : #include <stddef.h>
14 : :
15 : : /* Version macro; major * 1000 + minor * 10 + patch */
16 : : #define MESHOPTIMIZER_VERSION 130
17 : :
18 : : /* If no API is defined, assume default */
19 : : #ifndef MESHOPTIMIZER_API
20 : : #define MESHOPTIMIZER_API
21 : : #endif
22 : :
23 : : /* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */
24 : : #define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API
25 : :
26 : : /* C interface */
27 : : #ifdef __cplusplus
28 : : extern "C" {
29 : : #endif
30 : :
31 : : /**
32 : : * Vertex attribute stream, similar to glVertexPointer
33 : : * Each element takes size bytes, with stride controlling the spacing between successive elements.
34 : : */
35 : : struct meshopt_Stream
36 : : {
37 : : const void *data;
38 : : size_t size;
39 : : size_t stride;
40 : : };
41 : :
42 : : /**
43 : : * Generates a vertex remap table from the vertex buffer and an optional index buffer and returns number of unique vertices
44 : : * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.
45 : : * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.
46 : : *
47 : : * destination must contain enough space for the resulting remap table (vertex_count elements)
48 : : * indices can be NULL if the input is unindexed
49 : : */
50 : : MESHOPTIMIZER_API size_t meshopt_generateVertexRemap( unsigned int *destination, const unsigned int *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size );
51 : :
52 : : /**
53 : : * Generates a vertex remap table from multiple vertex streams and an optional index buffer and returns number of unique vertices
54 : : * As a result, all vertices that are binary equivalent map to the same (new) location, with no gaps in the resulting sequence.
55 : : * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer/meshopt_remapIndexBuffer.
56 : : * To remap vertex buffers, you will need to call meshopt_remapVertexBuffer for each vertex stream.
57 : : *
58 : : * destination must contain enough space for the resulting remap table (vertex_count elements)
59 : : * indices can be NULL if the input is unindexed
60 : : */
61 : : MESHOPTIMIZER_API size_t meshopt_generateVertexRemapMulti( unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream *streams, size_t stream_count );
62 : :
63 : : /**
64 : : * Generates vertex buffer from the source vertex buffer and remap table generated by meshopt_generateVertexRemap
65 : : *
66 : : * destination must contain enough space for the resulting vertex buffer (unique_vertex_count elements, returned by meshopt_generateVertexRemap)
67 : : * vertex_count should be the initial vertex count and not the value returned by meshopt_generateVertexRemap
68 : : */
69 : : MESHOPTIMIZER_API void meshopt_remapVertexBuffer( void *destination, const void *vertices, size_t vertex_count, size_t vertex_size, const unsigned int *remap );
70 : :
71 : : /**
72 : : * Generate index buffer from the source index buffer and remap table generated by meshopt_generateVertexRemap
73 : : *
74 : : * destination must contain enough space for the resulting index buffer (index_count elements)
75 : : * indices can be NULL if the input is unindexed
76 : : */
77 : : MESHOPTIMIZER_API void meshopt_remapIndexBuffer( unsigned int *destination, const unsigned int *indices, size_t index_count, const unsigned int *remap );
78 : :
79 : : /**
80 : : * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
81 : : * All vertices that are binary equivalent (wrt first vertex_size bytes) map to the first vertex in the original vertex buffer.
82 : : * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.
83 : : *
84 : : * destination must contain enough space for the resulting index buffer (index_count elements)
85 : : */
86 : : MESHOPTIMIZER_API void meshopt_generateShadowIndexBuffer( unsigned int *destination, const unsigned int *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride );
87 : :
88 : : /**
89 : : * Generate index buffer that can be used for more efficient rendering when only a subset of the vertex attributes is necessary
90 : : * All vertices that are binary equivalent (wrt specified streams) map to the first vertex in the original vertex buffer.
91 : : * This makes it possible to use the index buffer for Z pre-pass or shadowmap rendering, while using the original index buffer for regular rendering.
92 : : *
93 : : * destination must contain enough space for the resulting index buffer (index_count elements)
94 : : */
95 : : MESHOPTIMIZER_API void meshopt_generateShadowIndexBufferMulti( unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count, const struct meshopt_Stream *streams, size_t stream_count );
96 : :
97 : : /**
98 : : * Vertex transform cache optimizer
99 : : * Reorders indices to reduce the number of GPU vertex shader invocations
100 : : * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
101 : : *
102 : : * destination must contain enough space for the resulting index buffer (index_count elements)
103 : : */
104 : : MESHOPTIMIZER_API void meshopt_optimizeVertexCache( unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count );
105 : :
106 : : /**
107 : : * Vertex transform cache optimizer for FIFO caches
108 : : * Reorders indices to reduce the number of GPU vertex shader invocations
109 : : * Generally takes ~3x less time to optimize meshes but produces inferior results compared to meshopt_optimizeVertexCache
110 : : * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
111 : : *
112 : : * destination must contain enough space for the resulting index buffer (index_count elements)
113 : : * cache_size should be less than the actual GPU cache size to avoid cache thrashing
114 : : */
115 : : MESHOPTIMIZER_API void meshopt_optimizeVertexCacheFifo( unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count, unsigned int cache_size );
116 : :
117 : : /**
118 : : * Overdraw optimizer
119 : : * Reorders indices to reduce the number of GPU vertex shader invocations and the pixel overdraw
120 : : * If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
121 : : *
122 : : * destination must contain enough space for the resulting index buffer (index_count elements)
123 : : * indices must contain index data that is the result of meshopt_optimizeVertexCache (*not* the original mesh indices!)
124 : : * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
125 : : * threshold indicates how much the overdraw optimizer can degrade vertex cache efficiency (1.05 = up to 5%) to reduce overdraw more efficiently
126 : : */
127 : : MESHOPTIMIZER_API void meshopt_optimizeOverdraw( unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold );
128 : :
129 : : /**
130 : : * Vertex fetch cache optimizer
131 : : * Reorders vertices and changes indices to reduce the amount of GPU memory fetches during vertex processing
132 : : * Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused
133 : : * This functions works for a single vertex stream; for multiple vertex streams, use meshopt_optimizeVertexFetchRemap + meshopt_remapVertexBuffer for each stream.
134 : : *
135 : : * destination must contain enough space for the resulting vertex buffer (vertex_count elements)
136 : : * indices is used both as an input and as an output index buffer
137 : : */
138 : : MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetch( void *destination, unsigned int *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size );
139 : :
140 : : /**
141 : : * Vertex fetch cache optimizer
142 : : * Generates vertex remap to reduce the amount of GPU memory fetches during vertex processing
143 : : * Returns the number of unique vertices, which is the same as input vertex count unless some vertices are unused
144 : : * The resulting remap table should be used to reorder vertex/index buffers using meshopt_remapVertexBuffer/meshopt_remapIndexBuffer
145 : : *
146 : : * destination must contain enough space for the resulting remap table (vertex_count elements)
147 : : */
148 : : MESHOPTIMIZER_API size_t meshopt_optimizeVertexFetchRemap( unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count );
149 : :
150 : : /**
151 : : * Index buffer encoder
152 : : * Encodes index data into an array of bytes that is generally much smaller (<1.5 bytes/triangle) and compresses better (<1 bytes/triangle) compared to original.
153 : : * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
154 : : * For maximum efficiency the index buffer being encoded has to be optimized for vertex cache and vertex fetch first.
155 : : *
156 : : * buffer must contain enough space for the encoded index buffer (use meshopt_encodeIndexBufferBound to compute worst case size)
157 : : */
158 : : MESHOPTIMIZER_API size_t meshopt_encodeIndexBuffer( unsigned char *buffer, size_t buffer_size, const unsigned int *indices, size_t index_count );
159 : : MESHOPTIMIZER_API size_t meshopt_encodeIndexBufferBound( size_t index_count, size_t vertex_count );
160 : :
161 : : /**
162 : : * Index buffer decoder
163 : : * Decodes index data from an array of bytes generated by meshopt_encodeIndexBuffer
164 : : * Returns 0 if decoding was successful, and an error code otherwise
165 : : * The decoder is safe to use for untrusted input, but it may produce garbage data (e.g. out of range indices).
166 : : *
167 : : * destination must contain enough space for the resulting index buffer (index_count elements)
168 : : */
169 : : MESHOPTIMIZER_API int meshopt_decodeIndexBuffer( void *destination, size_t index_count, size_t index_size, const unsigned char *buffer, size_t buffer_size );
170 : :
171 : : /**
172 : : * Vertex buffer encoder
173 : : * Encodes vertex data into an array of bytes that is generally smaller and compresses better compared to original.
174 : : * Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
175 : : * This function works for a single vertex stream; for multiple vertex streams, call meshopt_encodeVertexBuffer for each stream.
176 : : *
177 : : * buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size)
178 : : */
179 : : MESHOPTIMIZER_API size_t meshopt_encodeVertexBuffer( unsigned char *buffer, size_t buffer_size, const void *vertices, size_t vertex_count, size_t vertex_size );
180 : : MESHOPTIMIZER_API size_t meshopt_encodeVertexBufferBound( size_t vertex_count, size_t vertex_size );
181 : :
182 : : /**
183 : : * Vertex buffer decoder
184 : : * Decodes vertex data from an array of bytes generated by meshopt_encodeVertexBuffer
185 : : * Returns 0 if decoding was successful, and an error code otherwise
186 : : * The decoder is safe to use for untrusted input, but it may produce garbage data.
187 : : *
188 : : * destination must contain enough space for the resulting vertex buffer (vertex_count * vertex_size bytes)
189 : : */
190 : : MESHOPTIMIZER_API int meshopt_decodeVertexBuffer( void *destination, size_t vertex_count, size_t vertex_size, const unsigned char *buffer, size_t buffer_size );
191 : :
192 : : /**
193 : : * Experimental: Mesh simplifier
194 : : * Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible
195 : : * The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.
196 : : * If not all attributes from the input mesh are required, it's recommended to reindex the mesh using meshopt_generateShadowIndexBuffer prior to simplification.
197 : : * Returns the number of indices after simplification, with destination containing new index data
198 : : * The resulting index buffer references vertices from the original vertex buffer.
199 : : * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
200 : : *
201 : : * destination must contain enough space for the *source* index buffer (since optimization is iterative, this means index_count elements - *not* target_index_count!)
202 : : * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
203 : : */
204 : : MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplify( unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error );
205 : :
206 : : /**
207 : : * Experimental: Mesh simplifier (sloppy)
208 : : * Reduces the number of triangles in the mesh, sacrificing mesh apperance for simplification performance
209 : : * The algorithm doesn't preserve mesh topology but is always able to reach target triangle count.
210 : : * Returns the number of indices after simplification, with destination containing new index data
211 : : * The resulting index buffer references vertices from the original vertex buffer.
212 : : * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
213 : : *
214 : : * destination must contain enough space for the target index buffer
215 : : * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
216 : : */
217 : : MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy( unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count );
218 : :
219 : : /**
220 : : * Experimental: Point cloud simplifier
221 : : * Reduces the number of points in the cloud to reach the given target
222 : : * Returns the number of points after simplification, with destination containing new index data
223 : : * The resulting index buffer references vertices from the original vertex buffer.
224 : : * If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
225 : : *
226 : : * destination must contain enough space for the target index buffer
227 : : * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
228 : : */
229 : : MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints( unsigned int *destination, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_vertex_count );
230 : :
231 : : /**
232 : : * Mesh stripifier
233 : : * Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles
234 : : * Returns the number of indices in the resulting strip, with destination containing new index data
235 : : * For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
236 : : * Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.
237 : : *
238 : : * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound
239 : : * restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles
240 : : */
241 : : MESHOPTIMIZER_API size_t meshopt_stripify( unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count, unsigned int restart_index );
242 : : MESHOPTIMIZER_API size_t meshopt_stripifyBound( size_t index_count );
243 : :
244 : : /**
245 : : * Mesh unstripifier
246 : : * Converts a triangle strip to a triangle list
247 : : * Returns the number of indices in the resulting list, with destination containing new index data
248 : : *
249 : : * destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound
250 : : */
251 : : MESHOPTIMIZER_API size_t meshopt_unstripify( unsigned int *destination, const unsigned int *indices, size_t index_count, unsigned int restart_index );
252 : : MESHOPTIMIZER_API size_t meshopt_unstripifyBound( size_t index_count );
253 : :
254 : : struct meshopt_VertexCacheStatistics
255 : : {
256 : : unsigned int vertices_transformed;
257 : : unsigned int warps_executed;
258 : : float acmr; /* transformed vertices / triangle count; best case 0.5, worst case 3.0, optimum depends on topology */
259 : : float atvr; /* transformed vertices / vertex count; best case 1.0, worst case 6.0, optimum is 1.0 (each vertex is transformed once) */
260 : : };
261 : :
262 : : /**
263 : : * Vertex transform cache analyzer
264 : : * Returns cache hit statistics using a simplified FIFO model
265 : : * Results may not match actual GPU performance
266 : : */
267 : : MESHOPTIMIZER_API struct meshopt_VertexCacheStatistics meshopt_analyzeVertexCache( const unsigned int *indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int primgroup_size );
268 : :
269 : : struct meshopt_OverdrawStatistics
270 : : {
271 : : unsigned int pixels_covered;
272 : : unsigned int pixels_shaded;
273 : : float overdraw; /* shaded pixels / covered pixels; best case 1.0 */
274 : : };
275 : :
276 : : /**
277 : : * Overdraw analyzer
278 : : * Returns overdraw statistics using a software rasterizer
279 : : * Results may not match actual GPU performance
280 : : *
281 : : * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
282 : : */
283 : : MESHOPTIMIZER_API struct meshopt_OverdrawStatistics meshopt_analyzeOverdraw( const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
284 : :
285 : : struct meshopt_VertexFetchStatistics
286 : : {
287 : : unsigned int bytes_fetched;
288 : : float overfetch; /* fetched bytes / vertex buffer size; best case 1.0 (each byte is fetched once) */
289 : : };
290 : :
291 : : /**
292 : : * Vertex fetch cache analyzer
293 : : * Returns cache hit statistics using a simplified direct mapped model
294 : : * Results may not match actual GPU performance
295 : : */
296 : : MESHOPTIMIZER_API struct meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch( const unsigned int *indices, size_t index_count, size_t vertex_count, size_t vertex_size );
297 : :
298 : : struct meshopt_Meshlet
299 : : {
300 : : unsigned int vertices[64];
301 : : unsigned char indices[126][3];
302 : : unsigned char triangle_count;
303 : : unsigned char vertex_count;
304 : : };
305 : :
306 : : /**
307 : : * Experimental: Meshlet builder
308 : : * Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer
309 : : * The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers.
310 : : * For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
311 : : *
312 : : * destination must contain enough space for all meshlets, worst case size can be computed with meshopt_buildMeshletsBound
313 : : * max_vertices and max_triangles can't exceed limits statically declared in meshopt_Meshlet (max_vertices <= 64, max_triangles <= 126)
314 : : */
315 : : MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshlets( struct meshopt_Meshlet *destination, const unsigned int *indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles );
316 : : MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_buildMeshletsBound( size_t index_count, size_t max_vertices, size_t max_triangles );
317 : :
318 : : struct meshopt_Bounds
319 : : {
320 : : /* bounding sphere, useful for frustum and occlusion culling */
321 : : float center[3];
322 : : float radius;
323 : :
324 : : /* normal cone, useful for backface culling */
325 : : float cone_apex[3];
326 : : float cone_axis[3];
327 : : float cone_cutoff; /* = cos(angle/2) */
328 : :
329 : : /* normal cone axis and cutoff, stored in 8-bit SNORM format; decode using x/127.0 */
330 : : signed char cone_axis_s8[3];
331 : : signed char cone_cutoff_s8;
332 : : };
333 : :
334 : : /**
335 : : * Experimental: Cluster bounds generator
336 : : * Creates bounding volumes that can be used for frustum, backface and occlusion culling.
337 : : *
338 : : * For backface culling with orthographic projection, use the following formula to reject backfacing clusters:
339 : : * dot(view, cone_axis) >= cone_cutoff
340 : : *
341 : : * For perspective projection, you can the formula that needs cone apex in addition to axis & cutoff:
342 : : * dot(normalize(cone_apex - camera_position), cone_axis) >= cone_cutoff
343 : : *
344 : : * Alternatively, you can use the formula that doesn't need cone apex and uses bounding sphere instead:
345 : : * dot(normalize(center - camera_position), cone_axis) >= cone_cutoff + radius / length(center - camera_position)
346 : : * or an equivalent formula that doesn't have a singularity at center = camera_position:
347 : : * dot(center - camera_position, cone_axis) >= cone_cutoff * length(center - camera_position) + radius
348 : : *
349 : : * The formula that uses the apex is slightly more accurate but needs the apex; if you are already using bounding sphere
350 : : * to do frustum/occlusion culling, the formula that doesn't use the apex may be preferable.
351 : : *
352 : : * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
353 : : * index_count should be less than or equal to 256*3 (the function assumes clusters of limited size)
354 : : */
355 : : MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeClusterBounds( const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
356 : : MESHOPTIMIZER_EXPERIMENTAL struct meshopt_Bounds meshopt_computeMeshletBounds( const struct meshopt_Meshlet *meshlet, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
357 : :
358 : : /**
359 : : * Experimental: Spatial sorter
360 : : * Generates a remap table that can be used to reorder points for spatial locality.
361 : : * Resulting remap table maps old vertices to new vertices and can be used in meshopt_remapVertexBuffer.
362 : : *
363 : : * destination must contain enough space for the resulting remap table (vertex_count elements)
364 : : */
365 : : MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortRemap( unsigned int *destination, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
366 : :
367 : : /**
368 : : * Experimental: Spatial sorter
369 : : * Reorders triangles for spatial locality, and generates a new index buffer. The resulting index buffer can be used with other functions like optimizeVertexCache.
370 : : *
371 : : * destination must contain enough space for the resulting index buffer (index_count elements)
372 : : * indices must contain index data that is the result of meshopt_optimizeVertexCache (*not* the original mesh indices!)
373 : : * vertex_positions should have float3 position in the first 12 bytes of each vertex - similar to glVertexPointer
374 : : */
375 : : MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles( unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
376 : :
377 : : /**
378 : : * Set allocation callbacks
379 : : * These callbacks will be used instead of the default operator new/operator delete for all temporary allocations in the library.
380 : : * Note that all algorithms only allocate memory for temporary use.
381 : : * allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first.
382 : : */
383 : : MESHOPTIMIZER_API void meshopt_setAllocator( void *( *allocate )( size_t ), void ( *deallocate )( void * ) );
384 : :
385 : : #ifdef __cplusplus
386 : : } /* extern "C" */
387 : : #endif
388 : :
389 : : /* Quantization into commonly supported data formats */
390 : : #ifdef __cplusplus
391 : :
392 : : /**
393 : : * Quantize a float in [0..1] range into an N-bit fixed point unorm value
394 : : * Assumes reconstruction function (q / (2^N-1)), which is the case for fixed-function normalized fixed point conversion
395 : : * Maximum reconstruction error: 1/2^(N+1)
396 : : */
397 : : inline int meshopt_quantizeUnorm( float v, int N );
398 : :
399 : : /**
400 : : * Quantize a float in [-1..1] range into an N-bit fixed point snorm value
401 : : * Assumes reconstruction function (q / (2^(N-1)-1)), which is the case for fixed-function normalized fixed point conversion (except early OpenGL versions)
402 : : * Maximum reconstruction error: 1/2^N
403 : : */
404 : : inline int meshopt_quantizeSnorm( float v, int N );
405 : :
406 : : /**
407 : : * Quantize a float into half-precision floating point value
408 : : * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest
409 : : * Representable magnitude range: [6e-5; 65504]
410 : : * Maximum relative reconstruction error: 5e-4
411 : : */
412 : : inline unsigned short meshopt_quantizeHalf( float v );
413 : :
414 : : /**
415 : : * Quantize a float into a floating point value with a limited number of significant mantissa bits
416 : : * Generates +-inf for overflow, preserves NaN, flushes denormals to zero, rounds to nearest
417 : : * Assumes N is in a valid mantissa precision range, which is 1..23
418 : : */
419 : : inline float meshopt_quantizeFloat( float v, int N );
420 : : #endif
421 : :
422 : : /**
423 : : * C++ template interface
424 : : *
425 : : * These functions mirror the C interface the library provides, providing template-based overloads so that
426 : : * the caller can use an arbitrary type for the index data, both for input and output.
427 : : * When the supplied type is the same size as that of unsigned int, the wrappers are zero-cost; when it's not,
428 : : * the wrappers end up allocating memory and copying index data to convert from one type to another.
429 : : */
430 : : #if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS)
431 : : template <typename T>
432 : : inline size_t meshopt_generateVertexRemap( unsigned int *destination, const T *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size );
433 : : template <typename T>
434 : : inline size_t meshopt_generateVertexRemapMulti( unsigned int *destination, const T *indices, size_t index_count, size_t vertex_count, const meshopt_Stream *streams, size_t stream_count );
435 : : template <typename T>
436 : : inline void meshopt_remapIndexBuffer( T *destination, const T *indices, size_t index_count, const unsigned int *remap );
437 : : template <typename T>
438 : : inline void meshopt_generateShadowIndexBuffer( T *destination, const T *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride );
439 : : template <typename T>
440 : : inline void meshopt_generateShadowIndexBufferMulti( T *destination, const T *indices, size_t index_count, size_t vertex_count, const meshopt_Stream *streams, size_t stream_count );
441 : : template <typename T>
442 : : inline void meshopt_optimizeVertexCache( T *destination, const T *indices, size_t index_count, size_t vertex_count );
443 : : template <typename T>
444 : : inline void meshopt_optimizeVertexCacheFifo( T *destination, const T *indices, size_t index_count, size_t vertex_count, unsigned int cache_size );
445 : : template <typename T>
446 : : inline void meshopt_optimizeOverdraw( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold );
447 : : template <typename T>
448 : : inline size_t meshopt_optimizeVertexFetchRemap( unsigned int *destination, const T *indices, size_t index_count, size_t vertex_count );
449 : : template <typename T>
450 : : inline size_t meshopt_optimizeVertexFetch( void *destination, T *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size );
451 : : template <typename T>
452 : : inline size_t meshopt_encodeIndexBuffer( unsigned char *buffer, size_t buffer_size, const T *indices, size_t index_count );
453 : : template <typename T>
454 : : inline int meshopt_decodeIndexBuffer( T *destination, size_t index_count, const unsigned char *buffer, size_t buffer_size );
455 : : template <typename T>
456 : : inline size_t meshopt_simplify( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error );
457 : : template <typename T>
458 : : inline size_t meshopt_simplifySloppy( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count );
459 : : template <typename T>
460 : : inline size_t meshopt_stripify( T *destination, const T *indices, size_t index_count, size_t vertex_count, T restart_index );
461 : : template <typename T>
462 : : inline size_t meshopt_unstripify( T *destination, const T *indices, size_t index_count, T restart_index );
463 : : template <typename T>
464 : : inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache( const T *indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int buffer_size );
465 : : template <typename T>
466 : : inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw( const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
467 : : template <typename T>
468 : : inline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch( const T *indices, size_t index_count, size_t vertex_count, size_t vertex_size );
469 : : template <typename T>
470 : : inline size_t meshopt_buildMeshlets( meshopt_Meshlet *destination, const T *indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles );
471 : : template <typename T>
472 : : inline meshopt_Bounds meshopt_computeClusterBounds( const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
473 : : template <typename T>
474 : : inline void meshopt_spatialSortTriangles( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride );
475 : : #endif
476 : :
477 : : /* Inline implementation */
478 : : #ifdef __cplusplus
479 : : inline int meshopt_quantizeUnorm( float v, int N )
480 : : {
481 : : const float scale = float( ( 1 << N ) - 1 );
482 : :
483 : : v = ( v >= 0 ) ? v : 0;
484 : : v = ( v <= 1 ) ? v : 1;
485 : :
486 : : return int( v * scale + 0.5f );
487 : : }
488 : :
489 : : inline int meshopt_quantizeSnorm( float v, int N )
490 : : {
491 : : const float scale = float( ( 1 << ( N - 1 ) ) - 1 );
492 : :
493 : : float round = ( v >= 0 ? 0.5f : -0.5f );
494 : :
495 : : v = ( v >= -1 ) ? v : -1;
496 : : v = ( v <= +1 ) ? v : +1;
497 : :
498 : : return int( v * scale + round );
499 : : }
500 : :
501 : : inline unsigned short meshopt_quantizeHalf( float v )
502 : : {
503 : : union { float f; unsigned int ui; } u = {v};
504 : : unsigned int ui = u.ui;
505 : :
506 : : int s = ( ui >> 16 ) & 0x8000;
507 : : int em = ui & 0x7fffffff;
508 : :
509 : : /* bias exponent and round to nearest; 112 is relative exponent bias (127-15) */
510 : : int h = ( em - ( 112 << 23 ) + ( 1 << 12 ) ) >> 13;
511 : :
512 : : /* underflow: flush to zero; 113 encodes exponent -14 */
513 : : h = ( em < ( 113 << 23 ) ) ? 0 : h;
514 : :
515 : : /* overflow: infinity; 143 encodes exponent 16 */
516 : : h = ( em >= ( 143 << 23 ) ) ? 0x7c00 : h;
517 : :
518 : : /* NaN; note that we convert all types of NaN to qNaN */
519 : : h = ( em > ( 255 << 23 ) ) ? 0x7e00 : h;
520 : :
521 : : return ( unsigned short )( s | h );
522 : : }
523 : :
524 : : inline float meshopt_quantizeFloat( float v, int N )
525 : : {
526 : : union { float f; unsigned int ui; } u = {v};
527 : : unsigned int ui = u.ui;
528 : :
529 : : const int mask = ( 1 << ( 23 - N ) ) - 1;
530 : : const int round = ( 1 << ( 23 - N ) ) >> 1;
531 : :
532 : : int e = ui & 0x7f800000;
533 : : unsigned int rui = ( ui + round ) & ~mask;
534 : :
535 : : /* round all numbers except inf/nan; this is important to make sure nan doesn't overflow into -0 */
536 : : ui = e == 0x7f800000 ? ui : rui;
537 : :
538 : : /* flush denormals to zero */
539 : : ui = e == 0 ? 0 : ui;
540 : :
541 : : u.ui = ui;
542 : : return u.f;
543 : : }
544 : : #endif
545 : :
546 : : /* Internal implementation helpers */
547 : : #ifdef __cplusplus
548 : : class meshopt_Allocator
549 : : {
550 : : public:
551 : : template <typename T>
552 : : struct StorageT
553 : : {
554 : : static void *( *allocate )( size_t );
555 : : static void ( *deallocate )( void * );
556 : : };
557 : :
558 : : typedef StorageT<void> Storage;
559 : :
560 : 0 : meshopt_Allocator()
561 : 0 : : blocks()
562 : 0 : , count( 0 )
563 : : {
564 : 0 : }
565 : :
566 : 0 : ~meshopt_Allocator()
567 : : {
568 : 0 : for ( size_t i = count; i > 0; --i )
569 : 0 : Storage::deallocate( blocks[i - 1] );
570 : 0 : }
571 : :
572 : 0 : template <typename T> T *allocate( size_t size )
573 : : {
574 : 0 : assert( count < sizeof( blocks ) / sizeof( blocks[0] ) );
575 : 0 : T *result = static_cast<T *>( Storage::allocate( size > size_t( -1 ) / sizeof( T ) ? size_t( -1 ) : size * sizeof( T ) ) );
576 : 0 : blocks[count++] = result;
577 : 0 : return result;
578 : : }
579 : :
580 : : private:
581 : : void *blocks[16];
582 : : size_t count;
583 : : };
584 : :
585 : : // This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker
586 : : template <typename T> void *( *meshopt_Allocator::StorageT<T>::allocate )( size_t ) = operator new;
587 : : template <typename T> void ( *meshopt_Allocator::StorageT<T>::deallocate )( void * ) = operator delete;
588 : : #endif
589 : :
590 : : /* Inline implementation for C++ templated wrappers */
591 : : #if defined(__cplusplus) && !defined(MESHOPTIMIZER_NO_WRAPPERS)
592 : : template <typename T, bool ZeroCopy = sizeof( T ) == sizeof( unsigned int )>
593 : : struct meshopt_IndexAdapter;
594 : :
595 : : template <typename T>
596 : : struct meshopt_IndexAdapter<T, false>
597 : : {
598 : : T *result;
599 : : unsigned int *data;
600 : : size_t count;
601 : :
602 : : meshopt_IndexAdapter( T *result_, const T *input, size_t count_ )
603 : : : result( result_ )
604 : : , data( 0 )
605 : : , count( count_ )
606 : : {
607 : : size_t size = count > size_t( -1 ) / sizeof( unsigned int ) ? size_t( -1 ) : count * sizeof( unsigned int );
608 : :
609 : : data = static_cast<unsigned int *>( meshopt_Allocator::Storage::allocate( size ) );
610 : :
611 : : if ( input )
612 : : {
613 : : for ( size_t i = 0; i < count; ++i )
614 : : data[i] = input[i];
615 : : }
616 : : }
617 : :
618 : : ~meshopt_IndexAdapter()
619 : : {
620 : : if ( result )
621 : : {
622 : : for ( size_t i = 0; i < count; ++i )
623 : : result[i] = T( data[i] );
624 : : }
625 : :
626 : : meshopt_Allocator::Storage::deallocate( data );
627 : : }
628 : : };
629 : :
630 : : template <typename T>
631 : : struct meshopt_IndexAdapter<T, true>
632 : : {
633 : : unsigned int *data;
634 : :
635 : : meshopt_IndexAdapter( T *result, const T *input, size_t )
636 : : : data( reinterpret_cast<unsigned int *>( result ? result : const_cast<T *>( input ) ) )
637 : : {
638 : : }
639 : : };
640 : :
641 : : template <typename T>
642 : : inline size_t meshopt_generateVertexRemap( unsigned int *destination, const T *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size )
643 : : {
644 : : meshopt_IndexAdapter<T> in( 0, indices, indices ? index_count : 0 );
645 : :
646 : : return meshopt_generateVertexRemap( destination, indices ? in.data : 0, index_count, vertices, vertex_count, vertex_size );
647 : : }
648 : :
649 : : template <typename T>
650 : : inline size_t meshopt_generateVertexRemapMulti( unsigned int *destination, const T *indices, size_t index_count, size_t vertex_count, const meshopt_Stream *streams, size_t stream_count )
651 : : {
652 : : meshopt_IndexAdapter<T> in( 0, indices, indices ? index_count : 0 );
653 : :
654 : : return meshopt_generateVertexRemapMulti( destination, indices ? in.data : 0, index_count, vertex_count, streams, stream_count );
655 : : }
656 : :
657 : : template <typename T>
658 : : inline void meshopt_remapIndexBuffer( T *destination, const T *indices, size_t index_count, const unsigned int *remap )
659 : : {
660 : : meshopt_IndexAdapter<T> in( 0, indices, indices ? index_count : 0 );
661 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
662 : :
663 : : meshopt_remapIndexBuffer( out.data, indices ? in.data : 0, index_count, remap );
664 : : }
665 : :
666 : : template <typename T>
667 : : inline void meshopt_generateShadowIndexBuffer( T *destination, const T *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size, size_t vertex_stride )
668 : : {
669 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
670 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
671 : :
672 : : meshopt_generateShadowIndexBuffer( out.data, in.data, index_count, vertices, vertex_count, vertex_size, vertex_stride );
673 : : }
674 : :
675 : : template <typename T>
676 : : inline void meshopt_generateShadowIndexBufferMulti( T *destination, const T *indices, size_t index_count, size_t vertex_count, const meshopt_Stream *streams, size_t stream_count )
677 : : {
678 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
679 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
680 : :
681 : : meshopt_generateShadowIndexBufferMulti( out.data, in.data, index_count, vertex_count, streams, stream_count );
682 : : }
683 : :
684 : : template <typename T>
685 : : inline void meshopt_optimizeVertexCache( T *destination, const T *indices, size_t index_count, size_t vertex_count )
686 : : {
687 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
688 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
689 : :
690 : : meshopt_optimizeVertexCache( out.data, in.data, index_count, vertex_count );
691 : : }
692 : :
693 : : template <typename T>
694 : : inline void meshopt_optimizeVertexCacheFifo( T *destination, const T *indices, size_t index_count, size_t vertex_count, unsigned int cache_size )
695 : : {
696 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
697 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
698 : :
699 : : meshopt_optimizeVertexCacheFifo( out.data, in.data, index_count, vertex_count, cache_size );
700 : : }
701 : :
702 : : template <typename T>
703 : : inline void meshopt_optimizeOverdraw( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, float threshold )
704 : : {
705 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
706 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
707 : :
708 : : meshopt_optimizeOverdraw( out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, threshold );
709 : : }
710 : :
711 : : template <typename T>
712 : : inline size_t meshopt_optimizeVertexFetchRemap( unsigned int *destination, const T *indices, size_t index_count, size_t vertex_count )
713 : : {
714 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
715 : :
716 : : return meshopt_optimizeVertexFetchRemap( destination, in.data, index_count, vertex_count );
717 : : }
718 : :
719 : : template <typename T>
720 : : inline size_t meshopt_optimizeVertexFetch( void *destination, T *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size )
721 : : {
722 : : meshopt_IndexAdapter<T> inout( indices, indices, index_count );
723 : :
724 : : return meshopt_optimizeVertexFetch( destination, inout.data, index_count, vertices, vertex_count, vertex_size );
725 : : }
726 : :
727 : : template <typename T>
728 : : inline size_t meshopt_encodeIndexBuffer( unsigned char *buffer, size_t buffer_size, const T *indices, size_t index_count )
729 : : {
730 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
731 : :
732 : : return meshopt_encodeIndexBuffer( buffer, buffer_size, in.data, index_count );
733 : : }
734 : :
735 : : template <typename T>
736 : : inline int meshopt_decodeIndexBuffer( T *destination, size_t index_count, const unsigned char *buffer, size_t buffer_size )
737 : : {
738 : : char index_size_valid[sizeof( T ) == 2 || sizeof( T ) == 4 ? 1 : -1];
739 : : ( void )index_size_valid;
740 : :
741 : : return meshopt_decodeIndexBuffer( destination, index_count, sizeof( T ), buffer, buffer_size );
742 : : }
743 : :
744 : : template <typename T>
745 : : inline size_t meshopt_simplify( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error )
746 : : {
747 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
748 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
749 : :
750 : : return meshopt_simplify( out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count, target_error );
751 : : }
752 : :
753 : : template <typename T>
754 : : inline size_t meshopt_simplifySloppy( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count )
755 : : {
756 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
757 : : meshopt_IndexAdapter<T> out( destination, 0, target_index_count );
758 : :
759 : : return meshopt_simplifySloppy( out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, target_index_count );
760 : : }
761 : :
762 : : template <typename T>
763 : : inline size_t meshopt_stripify( T *destination, const T *indices, size_t index_count, size_t vertex_count, T restart_index )
764 : : {
765 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
766 : : meshopt_IndexAdapter<T> out( destination, 0, ( index_count / 3 ) * 5 );
767 : :
768 : : return meshopt_stripify( out.data, in.data, index_count, vertex_count, unsigned( restart_index ) );
769 : : }
770 : :
771 : : template <typename T>
772 : : inline size_t meshopt_unstripify( T *destination, const T *indices, size_t index_count, T restart_index )
773 : : {
774 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
775 : : meshopt_IndexAdapter<T> out( destination, 0, ( index_count - 2 ) * 3 );
776 : :
777 : : return meshopt_unstripify( out.data, in.data, index_count, unsigned( restart_index ) );
778 : : }
779 : :
780 : : template <typename T>
781 : : inline meshopt_VertexCacheStatistics meshopt_analyzeVertexCache( const T *indices, size_t index_count, size_t vertex_count, unsigned int cache_size, unsigned int warp_size, unsigned int buffer_size )
782 : : {
783 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
784 : :
785 : : return meshopt_analyzeVertexCache( in.data, index_count, vertex_count, cache_size, warp_size, buffer_size );
786 : : }
787 : :
788 : : template <typename T>
789 : : inline meshopt_OverdrawStatistics meshopt_analyzeOverdraw( const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride )
790 : : {
791 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
792 : :
793 : : return meshopt_analyzeOverdraw( in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride );
794 : : }
795 : :
796 : : template <typename T>
797 : : inline meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch( const T *indices, size_t index_count, size_t vertex_count, size_t vertex_size )
798 : : {
799 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
800 : :
801 : : return meshopt_analyzeVertexFetch( in.data, index_count, vertex_count, vertex_size );
802 : : }
803 : :
804 : : template <typename T>
805 : : inline size_t meshopt_buildMeshlets( meshopt_Meshlet *destination, const T *indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles )
806 : : {
807 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
808 : :
809 : : return meshopt_buildMeshlets( destination, in.data, index_count, vertex_count, max_vertices, max_triangles );
810 : : }
811 : :
812 : : template <typename T>
813 : : inline meshopt_Bounds meshopt_computeClusterBounds( const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride )
814 : : {
815 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
816 : :
817 : : return meshopt_computeClusterBounds( in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride );
818 : : }
819 : :
820 : : template <typename T>
821 : : inline void meshopt_spatialSortTriangles( T *destination, const T *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride )
822 : : {
823 : : meshopt_IndexAdapter<T> in( 0, indices, index_count );
824 : : meshopt_IndexAdapter<T> out( destination, 0, index_count );
825 : :
826 : : meshopt_spatialSortTriangles( out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride );
827 : : }
828 : : #endif
829 : :
830 : : /**
831 : : * Copyright (c) 2016-2019 Arseny Kapoulkine
832 : : *
833 : : * Permission is hereby granted, free of charge, to any person
834 : : * obtaining a copy of this software and associated documentation
835 : : * files (the "Software"), to deal in the Software without
836 : : * restriction, including without limitation the rights to use,
837 : : * copy, modify, merge, publish, distribute, sublicense, and/or sell
838 : : * copies of the Software, and to permit persons to whom the
839 : : * Software is furnished to do so, subject to the following
840 : : * conditions:
841 : : *
842 : : * The above copyright notice and this permission notice shall be
843 : : * included in all copies or substantial portions of the Software.
844 : : *
845 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
846 : : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
847 : : * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
848 : : * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
849 : : * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
850 : : * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
851 : : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
852 : : * OTHER DEALINGS IN THE SOFTWARE.
853 : : */
|