Added stripifier for X PV obj set output

This commit is contained in:
korenkonder
2023-10-10 15:03:20 +03:00
parent 22a840f45d
commit a5cec111f3
7 changed files with 966 additions and 23 deletions
+144
View File
@@ -0,0 +1,144 @@
/**
* meshoptimizer - version 0.18
*
* Copyright (C) 2016-2023, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://github.com/zeux/meshoptimizer
*
* This library is distributed under the MIT License. See notice at the end of this file.
*/
#pragma once
#include <assert.h>
#include <stddef.h>
/* Version macro; major * 1000 + minor * 10 + patch */
#define MESHOPTIMIZER_VERSION 180 /* 0.18 */
/* If no API is defined, assume default */
#ifndef MESHOPTIMIZER_API
#define MESHOPTIMIZER_API
#endif
/* Set the calling-convention for alloc/dealloc function pointers */
#ifndef MESHOPTIMIZER_ALLOC_CALLCONV
#ifdef _MSC_VER
#define MESHOPTIMIZER_ALLOC_CALLCONV __cdecl
#else
#define MESHOPTIMIZER_ALLOC_CALLCONV
#endif
#endif
/* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */
#define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API
/* C interface */
#ifdef __cplusplus
extern "C" {
#endif
/**
* Vertex attribute stream
* Each element takes size bytes, beginning at data, with stride controlling the spacing between successive elements (stride >= size).
*/
struct meshopt_Stream
{
const void* data;
size_t size;
size_t stride;
};
/**
* Mesh stripifier
* Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles
* Returns the number of indices in the resulting strip, with destination containing new index data
* For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
* Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.
*
* destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound
* restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles
*/
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);
MESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count);
/**
* Mesh unstripifier
* Converts a triangle strip to a triangle list
* Returns the number of indices in the resulting list, with destination containing new index data
*
* destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound
*/
MESHOPTIMIZER_API size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index);
MESHOPTIMIZER_API size_t meshopt_unstripifyBound(size_t index_count);
#ifdef __cplusplus
} /* extern "C" */
#endif
/* Internal implementation helpers */
#ifdef __cplusplus
class meshopt_Allocator
{
public:
template <typename T>
struct StorageT
{
static void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t);
static void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*);
};
typedef StorageT<void> Storage;
meshopt_Allocator()
: blocks()
, count(0)
{
}
~meshopt_Allocator()
{
for (size_t i = count; i > 0; --i)
Storage::deallocate(blocks[i - 1]);
}
template <typename T> T* allocate(size_t size)
{
assert(count < sizeof(blocks) / sizeof(blocks[0]));
T* result = static_cast<T*>(Storage::allocate(size > size_t(-1) / sizeof(T) ? size_t(-1) : size * sizeof(T)));
blocks[count++] = result;
return result;
}
private:
void* blocks[24];
size_t count;
};
// This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker
template <typename T> void* (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT<T>::allocate)(size_t) = operator new;
template <typename T> void (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT<T>::deallocate)(void*) = operator delete;
#endif
/**
* Copyright (c) 2016-2023 Arseny Kapoulkine
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
+295
View File
@@ -0,0 +1,295 @@
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
#include <assert.h>
#include <limits.h>
#include <string.h>
// This work is based on:
// Francine Evans, Steven Skiena and Amitabh Varshney. Optimizing Triangle Strips for Fast Rendering. 1996
namespace meshopt
{
static unsigned int findStripFirst(const unsigned int buffer[][3], unsigned int buffer_size, const unsigned int* valence)
{
unsigned int index = 0;
unsigned int iv = ~0u;
for (size_t i = 0; i < buffer_size; ++i)
{
unsigned int va = valence[buffer[i][0]], vb = valence[buffer[i][1]], vc = valence[buffer[i][2]];
unsigned int v = (va < vb && va < vc) ? va : (vb < vc) ? vb : vc;
if (v < iv)
{
index = unsigned(i);
iv = v;
}
}
return index;
}
static int findStripNext(const unsigned int buffer[][3], unsigned int buffer_size, unsigned int e0, unsigned int e1)
{
for (size_t i = 0; i < buffer_size; ++i)
{
unsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];
if (e0 == a && e1 == b)
return (int(i) << 2) | 2;
else if (e0 == b && e1 == c)
return (int(i) << 2) | 0;
else if (e0 == c && e1 == a)
return (int(i) << 2) | 1;
}
return -1;
}
} // namespace meshopt
size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index)
{
assert(destination != indices);
assert(index_count % 3 == 0);
using namespace meshopt;
meshopt_Allocator allocator;
const size_t buffer_capacity = 8;
unsigned int buffer[buffer_capacity][3] = {};
unsigned int buffer_size = 0;
size_t index_offset = 0;
unsigned int strip[2] = {};
unsigned int parity = 0;
size_t strip_size = 0;
// compute vertex valence; this is used to prioritize starting triangle for strips
unsigned int* valence = allocator.allocate<unsigned int>(vertex_count);
memset(valence, 0, vertex_count * sizeof(unsigned int));
for (size_t i = 0; i < index_count; ++i)
{
unsigned int index = indices[i];
assert(index < vertex_count);
valence[index]++;
}
int next = -1;
while (buffer_size > 0 || index_offset < index_count)
{
assert(next < 0 || (size_t(next >> 2) < buffer_size && (next & 3) < 3));
// fill triangle buffer
while (buffer_size < buffer_capacity && index_offset < index_count)
{
buffer[buffer_size][0] = indices[index_offset + 0];
buffer[buffer_size][1] = indices[index_offset + 1];
buffer[buffer_size][2] = indices[index_offset + 2];
buffer_size++;
index_offset += 3;
}
assert(buffer_size > 0);
if (next >= 0)
{
unsigned int i = next >> 2;
unsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];
unsigned int v = buffer[i][next & 3];
// ordered removal from the buffer
memmove(buffer[i], buffer[i + 1], (buffer_size - i - 1) * sizeof(buffer[0]));
buffer_size--;
// update vertex valences for strip start heuristic
valence[a]--;
valence[b]--;
valence[c]--;
// find next triangle (note that edge order flips on every iteration)
// in some cases we need to perform a swap to pick a different outgoing triangle edge
// for [a b c], the default strip edge is [b c], but we might want to use [a c]
int cont = findStripNext(buffer, buffer_size, parity ? strip[1] : v, parity ? v : strip[1]);
int swap = cont < 0 ? findStripNext(buffer, buffer_size, parity ? v : strip[0], parity ? strip[0] : v) : -1;
if (cont < 0 && swap >= 0)
{
// [a b c] => [a b a c]
destination[strip_size++] = strip[0];
destination[strip_size++] = v;
// next strip has same winding
// ? a b => b a v
strip[1] = v;
next = swap;
}
else
{
// emit the next vertex in the strip
destination[strip_size++] = v;
// next strip has flipped winding
strip[0] = strip[1];
strip[1] = v;
parity ^= 1;
next = cont;
}
}
else
{
// if we didn't find anything, we need to find the next new triangle
// we use a heuristic to maximize the strip length
unsigned int i = findStripFirst(buffer, buffer_size, &valence[0]);
unsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];
// ordered removal from the buffer
memmove(buffer[i], buffer[i + 1], (buffer_size - i - 1) * sizeof(buffer[0]));
buffer_size--;
// update vertex valences for strip start heuristic
valence[a]--;
valence[b]--;
valence[c]--;
// we need to pre-rotate the triangle so that we will find a match in the existing buffer on the next iteration
int ea = findStripNext(buffer, buffer_size, c, b);
int eb = findStripNext(buffer, buffer_size, a, c);
int ec = findStripNext(buffer, buffer_size, b, a);
// in some cases we can have several matching edges; since we can pick any edge, we pick the one with the smallest
// triangle index in the buffer. this reduces the effect of stripification on ACMR and additionally - for unclear
// reasons - slightly improves the stripification efficiency
int mine = INT_MAX;
mine = (ea >= 0 && mine > ea) ? ea : mine;
mine = (eb >= 0 && mine > eb) ? eb : mine;
mine = (ec >= 0 && mine > ec) ? ec : mine;
if (ea == mine)
{
// keep abc
next = ea;
}
else if (eb == mine)
{
// abc -> bca
unsigned int t = a;
a = b, b = c, c = t;
next = eb;
}
else if (ec == mine)
{
// abc -> cab
unsigned int t = c;
c = b, b = a, a = t;
next = ec;
}
if (restart_index)
{
if (strip_size)
destination[strip_size++] = restart_index;
destination[strip_size++] = a;
destination[strip_size++] = b;
destination[strip_size++] = c;
// new strip always starts with the same edge winding
strip[0] = b;
strip[1] = c;
parity = 1;
}
else
{
if (strip_size)
{
// connect last strip using degenerate triangles
destination[strip_size++] = strip[1];
destination[strip_size++] = a;
}
// note that we may need to flip the emitted triangle based on parity
// we always end up with outgoing edge "cb" in the end
unsigned int e0 = parity ? c : b;
unsigned int e1 = parity ? b : c;
destination[strip_size++] = a;
destination[strip_size++] = e0;
destination[strip_size++] = e1;
strip[0] = e0;
strip[1] = e1;
parity ^= 1;
}
}
}
return strip_size;
}
size_t meshopt_stripifyBound(size_t index_count)
{
assert(index_count % 3 == 0);
// worst case without restarts is 2 degenerate indices and 3 indices per triangle
// worst case with restarts is 1 restart index and 3 indices per triangle
return (index_count / 3) * 5;
}
size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index)
{
assert(destination != indices);
size_t offset = 0;
size_t start = 0;
for (size_t i = 0; i < index_count; ++i)
{
if (restart_index && indices[i] == restart_index)
{
start = i + 1;
}
else if (i - start >= 2)
{
unsigned int a = indices[i - 2], b = indices[i - 1], c = indices[i];
// flip winding for odd triangles
if ((i - start) & 1)
{
unsigned int t = a;
a = b, b = t;
}
// although we use restart indices, strip swaps still produce degenerate triangles, so skip them
if (a != b && a != c && b != c)
{
destination[offset + 0] = a;
destination[offset + 1] = b;
destination[offset + 2] = c;
offset += 3;
}
}
}
return offset;
}
size_t meshopt_unstripifyBound(size_t index_count)
{
assert(index_count == 0 || index_count >= 3);
return (index_count == 0) ? 0 : (index_count - 2) * 3;
}
+22 -16
View File
@@ -1402,16 +1402,24 @@ static void obj_classic_read_index(obj_sub_mesh* sub_mesh, prj::shared_ptr<prj::
uint32_t* index_array = alloc->allocate<uint32_t>(num_index);
switch (sub_mesh->index_format) {
case OBJ_INDEX_U8:
for (uint32_t i = 0; i < num_index; i++) {
uint8_t idx = s.read_uint8_t();
index_array[i] = tri_strip && idx == 0xFF ? 0xFFFFFFFF : idx;
}
if (tri_strip)
for (uint32_t i = 0; i < num_index; i++) {
uint8_t idx = s.read_uint8_t();
index_array[i] = idx == 0xFF ? 0xFFFFFFFF : idx;
}
else
for (uint32_t i = 0; i < num_index; i++)
index_array[i] = s.read_uint8_t();
break;
case OBJ_INDEX_U16:
for (uint32_t i = 0; i < num_index; i++) {
uint16_t idx = s.read_uint16_t();
index_array[i] = tri_strip && idx == 0xFFFF ? 0xFFFFFFFF : idx;
}
if (tri_strip)
for (uint32_t i = 0; i < num_index; i++) {
uint16_t idx = s.read_uint16_t();
index_array[i] = idx == 0xFFFF ? 0xFFFFFFFF : idx;
}
else
for (uint32_t i = 0; i < num_index; i++)
index_array[i] = s.read_uint16_t();
break;
case OBJ_INDEX_U32:
for (uint32_t i = 0; i < num_index; i++)
@@ -1609,8 +1617,7 @@ static void obj_classic_write_model(obj* obj, stream& s, int64_t base_offset) {
s.write_int32_t(sub_mesh->num_index);
s.write_uint32_t((uint32_t)smh.index_array_offset);
s.write_uint32_t(sub_mesh->attrib.w);
s.write(0x18);
s.write_uint32_t(sub_mesh->index_offset);
s.write(0x1C);
s.position_pop();
}
}
@@ -1634,7 +1641,7 @@ static void obj_classic_write_model(obj* obj, stream& s, int64_t base_offset) {
for (int64_t& j : mh->vertex)
s.write_uint32_t((uint32_t)j);
s.write_uint32_t(mesh->attrib.w);
s.write_uint32_t(mesh->attrib.w & 0x7FFFFFFF);
s.write_uint32_t(mh->vertex_format_index);
s.write_uint32_t(mesh->reserved[0]);
s.write_uint32_t(mesh->reserved[1]);
@@ -1703,7 +1710,7 @@ static void obj_classic_read_model_mesh(obj_mesh* mesh,
for (int64_t& i : mh.vertex)
i = s.read_uint32_t();
mesh->attrib.w = s.read_uint32_t();
mesh->attrib.w = s.read_uint32_t() & 0x7FFFFFFF;
mh.vertex_format_index = s.read_uint32_t();
mesh->reserved[0] = s.read_uint32_t();
mesh->reserved[1] = s.read_uint32_t();
@@ -1751,8 +1758,7 @@ static void obj_classic_read_model_sub_mesh(obj_sub_mesh* sub_mesh,
sub_mesh->bounding_box.size.x = sub_mesh->bounding_sphere.radius * 2.0f;
sub_mesh->bounding_box.size.y = sub_mesh->bounding_sphere.radius * 2.0f;
sub_mesh->bounding_box.size.z = sub_mesh->bounding_sphere.radius * 2.0f;
s.read(0, 0x18);
sub_mesh->index_offset = s.read_uint32_t();
s.read(0, 0x1C);
if (sub_mesh->bones_per_vertex == 4 && smh.bone_index_array_offset) {
sub_mesh->bone_index_array = alloc->allocate<uint16_t>(sub_mesh->num_bone_index);
@@ -4850,7 +4856,7 @@ static void obj_modern_write_model(obj* obj, stream& s,
for (uint32_t j = 0; j < 20; j++)
s.write_offset_x(mh->vertex[j]);
s.write_uint32_t_reverse_endianness(mesh->attrib.w);
s.write_uint32_t_reverse_endianness(mesh->attrib.w & 0x7FFFFFFF);
s.write_uint32_t_reverse_endianness(mh->vertex_format_index);
s.write_uint32_t_reverse_endianness(mesh->reserved[0]);
s.write_uint32_t_reverse_endianness(mesh->reserved[1]);
@@ -4973,7 +4979,7 @@ static void obj_modern_read_model_mesh(obj_mesh* mesh,
for (int64_t& i : mh.vertex)
i = s.read_offset_x();
mesh->attrib.w = s.read_uint32_t_reverse_endianness();
mesh->attrib.w = s.read_uint32_t_reverse_endianness() & 0x7FFFFFFF;
mh.vertex_format_index = s.read_uint32_t_reverse_endianness();
mesh->reserved[0] = s.read_uint32_t_reverse_endianness();
mesh->reserved[1] = s.read_uint32_t_reverse_endianness();
+144
View File
@@ -0,0 +1,144 @@
/**
* meshoptimizer - version 0.18
*
* Copyright (C) 2016-2023, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://github.com/zeux/meshoptimizer
*
* This library is distributed under the MIT License. See notice at the end of this file.
*/
#pragma once
#include <assert.h>
#include <stddef.h>
/* Version macro; major * 1000 + minor * 10 + patch */
#define MESHOPTIMIZER_VERSION 180 /* 0.18 */
/* If no API is defined, assume default */
#ifndef MESHOPTIMIZER_API
#define MESHOPTIMIZER_API
#endif
/* Set the calling-convention for alloc/dealloc function pointers */
#ifndef MESHOPTIMIZER_ALLOC_CALLCONV
#ifdef _MSC_VER
#define MESHOPTIMIZER_ALLOC_CALLCONV __cdecl
#else
#define MESHOPTIMIZER_ALLOC_CALLCONV
#endif
#endif
/* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */
#define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API
/* C interface */
#ifdef __cplusplus
extern "C" {
#endif
/**
* Vertex attribute stream
* Each element takes size bytes, beginning at data, with stride controlling the spacing between successive elements (stride >= size).
*/
struct meshopt_Stream
{
const void* data;
size_t size;
size_t stride;
};
/**
* Mesh stripifier
* Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles
* Returns the number of indices in the resulting strip, with destination containing new index data
* For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
* Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.
*
* destination must contain enough space for the target index buffer, worst case can be computed with meshopt_stripifyBound
* restart_index should be 0xffff or 0xffffffff depending on index size, or 0 to use degenerate triangles
*/
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);
MESHOPTIMIZER_API size_t meshopt_stripifyBound(size_t index_count);
/**
* Mesh unstripifier
* Converts a triangle strip to a triangle list
* Returns the number of indices in the resulting list, with destination containing new index data
*
* destination must contain enough space for the target index buffer, worst case can be computed with meshopt_unstripifyBound
*/
MESHOPTIMIZER_API size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index);
MESHOPTIMIZER_API size_t meshopt_unstripifyBound(size_t index_count);
#ifdef __cplusplus
} /* extern "C" */
#endif
/* Internal implementation helpers */
#ifdef __cplusplus
class meshopt_Allocator
{
public:
template <typename T>
struct StorageT
{
static void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t);
static void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*);
};
typedef StorageT<void> Storage;
meshopt_Allocator()
: blocks()
, count(0)
{
}
~meshopt_Allocator()
{
for (size_t i = count; i > 0; --i)
Storage::deallocate(blocks[i - 1]);
}
template <typename T> T* allocate(size_t size)
{
assert(count < sizeof(blocks) / sizeof(blocks[0]));
T* result = static_cast<T*>(Storage::allocate(size > size_t(-1) / sizeof(T) ? size_t(-1) : size * sizeof(T)));
blocks[count++] = result;
return result;
}
private:
void* blocks[24];
size_t count;
};
// This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker
template <typename T> void* (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT<T>::allocate)(size_t) = operator new;
template <typename T> void (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT<T>::deallocate)(void*) = operator delete;
#endif
/**
* Copyright (c) 2016-2023 Arseny Kapoulkine
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
+295
View File
@@ -0,0 +1,295 @@
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
#include <assert.h>
#include <limits.h>
#include <string.h>
// This work is based on:
// Francine Evans, Steven Skiena and Amitabh Varshney. Optimizing Triangle Strips for Fast Rendering. 1996
namespace meshopt
{
static unsigned int findStripFirst(const unsigned int buffer[][3], unsigned int buffer_size, const unsigned int* valence)
{
unsigned int index = 0;
unsigned int iv = ~0u;
for (size_t i = 0; i < buffer_size; ++i)
{
unsigned int va = valence[buffer[i][0]], vb = valence[buffer[i][1]], vc = valence[buffer[i][2]];
unsigned int v = (va < vb && va < vc) ? va : (vb < vc) ? vb : vc;
if (v < iv)
{
index = unsigned(i);
iv = v;
}
}
return index;
}
static int findStripNext(const unsigned int buffer[][3], unsigned int buffer_size, unsigned int e0, unsigned int e1)
{
for (size_t i = 0; i < buffer_size; ++i)
{
unsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];
if (e0 == a && e1 == b)
return (int(i) << 2) | 2;
else if (e0 == b && e1 == c)
return (int(i) << 2) | 0;
else if (e0 == c && e1 == a)
return (int(i) << 2) | 1;
}
return -1;
}
} // namespace meshopt
size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices, size_t index_count, size_t vertex_count, unsigned int restart_index)
{
assert(destination != indices);
assert(index_count % 3 == 0);
using namespace meshopt;
meshopt_Allocator allocator;
const size_t buffer_capacity = 8;
unsigned int buffer[buffer_capacity][3] = {};
unsigned int buffer_size = 0;
size_t index_offset = 0;
unsigned int strip[2] = {};
unsigned int parity = 0;
size_t strip_size = 0;
// compute vertex valence; this is used to prioritize starting triangle for strips
unsigned int* valence = allocator.allocate<unsigned int>(vertex_count);
memset(valence, 0, vertex_count * sizeof(unsigned int));
for (size_t i = 0; i < index_count; ++i)
{
unsigned int index = indices[i];
assert(index < vertex_count);
valence[index]++;
}
int next = -1;
while (buffer_size > 0 || index_offset < index_count)
{
assert(next < 0 || (size_t(next >> 2) < buffer_size && (next & 3) < 3));
// fill triangle buffer
while (buffer_size < buffer_capacity && index_offset < index_count)
{
buffer[buffer_size][0] = indices[index_offset + 0];
buffer[buffer_size][1] = indices[index_offset + 1];
buffer[buffer_size][2] = indices[index_offset + 2];
buffer_size++;
index_offset += 3;
}
assert(buffer_size > 0);
if (next >= 0)
{
unsigned int i = next >> 2;
unsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];
unsigned int v = buffer[i][next & 3];
// ordered removal from the buffer
memmove(buffer[i], buffer[i + 1], (buffer_size - i - 1) * sizeof(buffer[0]));
buffer_size--;
// update vertex valences for strip start heuristic
valence[a]--;
valence[b]--;
valence[c]--;
// find next triangle (note that edge order flips on every iteration)
// in some cases we need to perform a swap to pick a different outgoing triangle edge
// for [a b c], the default strip edge is [b c], but we might want to use [a c]
int cont = findStripNext(buffer, buffer_size, parity ? strip[1] : v, parity ? v : strip[1]);
int swap = cont < 0 ? findStripNext(buffer, buffer_size, parity ? v : strip[0], parity ? strip[0] : v) : -1;
if (cont < 0 && swap >= 0)
{
// [a b c] => [a b a c]
destination[strip_size++] = strip[0];
destination[strip_size++] = v;
// next strip has same winding
// ? a b => b a v
strip[1] = v;
next = swap;
}
else
{
// emit the next vertex in the strip
destination[strip_size++] = v;
// next strip has flipped winding
strip[0] = strip[1];
strip[1] = v;
parity ^= 1;
next = cont;
}
}
else
{
// if we didn't find anything, we need to find the next new triangle
// we use a heuristic to maximize the strip length
unsigned int i = findStripFirst(buffer, buffer_size, &valence[0]);
unsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];
// ordered removal from the buffer
memmove(buffer[i], buffer[i + 1], (buffer_size - i - 1) * sizeof(buffer[0]));
buffer_size--;
// update vertex valences for strip start heuristic
valence[a]--;
valence[b]--;
valence[c]--;
// we need to pre-rotate the triangle so that we will find a match in the existing buffer on the next iteration
int ea = findStripNext(buffer, buffer_size, c, b);
int eb = findStripNext(buffer, buffer_size, a, c);
int ec = findStripNext(buffer, buffer_size, b, a);
// in some cases we can have several matching edges; since we can pick any edge, we pick the one with the smallest
// triangle index in the buffer. this reduces the effect of stripification on ACMR and additionally - for unclear
// reasons - slightly improves the stripification efficiency
int mine = INT_MAX;
mine = (ea >= 0 && mine > ea) ? ea : mine;
mine = (eb >= 0 && mine > eb) ? eb : mine;
mine = (ec >= 0 && mine > ec) ? ec : mine;
if (ea == mine)
{
// keep abc
next = ea;
}
else if (eb == mine)
{
// abc -> bca
unsigned int t = a;
a = b, b = c, c = t;
next = eb;
}
else if (ec == mine)
{
// abc -> cab
unsigned int t = c;
c = b, b = a, a = t;
next = ec;
}
if (restart_index)
{
if (strip_size)
destination[strip_size++] = restart_index;
destination[strip_size++] = a;
destination[strip_size++] = b;
destination[strip_size++] = c;
// new strip always starts with the same edge winding
strip[0] = b;
strip[1] = c;
parity = 1;
}
else
{
if (strip_size)
{
// connect last strip using degenerate triangles
destination[strip_size++] = strip[1];
destination[strip_size++] = a;
}
// note that we may need to flip the emitted triangle based on parity
// we always end up with outgoing edge "cb" in the end
unsigned int e0 = parity ? c : b;
unsigned int e1 = parity ? b : c;
destination[strip_size++] = a;
destination[strip_size++] = e0;
destination[strip_size++] = e1;
strip[0] = e0;
strip[1] = e1;
parity ^= 1;
}
}
}
return strip_size;
}
size_t meshopt_stripifyBound(size_t index_count)
{
assert(index_count % 3 == 0);
// worst case without restarts is 2 degenerate indices and 3 indices per triangle
// worst case with restarts is 1 restart index and 3 indices per triangle
return (index_count / 3) * 5;
}
size_t meshopt_unstripify(unsigned int* destination, const unsigned int* indices, size_t index_count, unsigned int restart_index)
{
assert(destination != indices);
size_t offset = 0;
size_t start = 0;
for (size_t i = 0; i < index_count; ++i)
{
if (restart_index && indices[i] == restart_index)
{
start = i + 1;
}
else if (i - start >= 2)
{
unsigned int a = indices[i - 2], b = indices[i - 1], c = indices[i];
// flip winding for odd triangles
if ((i - start) & 1)
{
unsigned int t = a;
a = b, b = t;
}
// although we use restart indices, strip swaps still produce degenerate triangles, so skip them
if (a != b && a != c && b != c)
{
destination[offset + 0] = a;
destination[offset + 1] = b;
destination[offset + 2] = c;
offset += 3;
}
}
}
return offset;
}
size_t meshopt_unstripifyBound(size_t index_count)
{
assert(index_count == 0 || index_count >= 3);
return (index_count == 0) ? 0 : (index_count - 2) * 3;
}
+40 -7
View File
@@ -26,6 +26,7 @@
#include "../KKdLib/interpolation.hpp"
#include "../KKdLib/sort.hpp"
#include "../KKdLib/str_utils.hpp"
#include <meshoptimizer/meshoptimizer.h>
#if BAKE_PNG
#include <lodepng/lodepng.h>
#endif
@@ -10181,11 +10182,19 @@ static void x_pv_game_split_auth_3d_material_list(x_pv_game* xpvgm,
set->obj_num = _obj_num;
obj_vertex_buffer* vertex_buffer_data = handler->vertex_buffer_data;
for (uint32_t j = 0; j < obj_num; j++)
vertex_buffer_data[j].unload();
delete[] vertex_buffer_data;
handler->vertex_buffer_num = _obj_num;
obj_vertex_buffer* vertex_buffer_data_new = new obj_vertex_buffer[_obj_num];
handler->vertex_buffer_data = vertex_buffer_data_new;
obj_index_buffer* index_buffer_data = handler->index_buffer_data;
for (uint32_t j = 0; j < obj_num; j++)
index_buffer_data[j].unload();
delete[] index_buffer_data;
handler->index_buffer_num = _obj_num;
obj_index_buffer* index_buffer_data_new = new obj_index_buffer[_obj_num];
handler->index_buffer_data = index_buffer_data_new;
@@ -10194,11 +10203,38 @@ static void x_pv_game_split_auth_3d_material_list(x_pv_game* xpvgm,
vertex_buffer_data_new++, index_buffer_data_new++) {
*obj_data_new = *obj_data;
bool init_buffer = false;
*vertex_buffer_data_new = vertex_buffer_data[j];
*index_buffer_data_new = index_buffer_data[j];
obj* obj = *obj_data;
obj_mesh* mesh_array = obj->mesh_array;
uint32_t num_mesh = obj->num_mesh;
for (uint32_t m = 0; m < num_mesh; m++) {
obj_mesh* mesh = &mesh_array[m];
obj_sub_mesh* submesh_array = mesh->submesh_array;
uint32_t num_submesh = mesh->num_submesh;
for (uint32_t n = 0; n < num_submesh; n++) {
obj_sub_mesh* submesh = &submesh_array[n];
if (submesh->primitive_type != OBJ_PRIMITIVE_TRIANGLES)
continue;
submesh->primitive_type = OBJ_PRIMITIVE_TRIANGLE_STRIP;
uint32_t* index_array = submesh->index_array;
submesh->index_array = alloc->allocate<uint32_t>(
meshopt_stripifyBound(submesh->num_index));
submesh->num_index = (uint32_t)meshopt_stripify(submesh->index_array,
index_array, submesh->num_index, mesh->num_vertex, 0xFFFFFFFF);
submesh->first_index = 0;
submesh->last_index = 0;
submesh->index_offset = 0;
}
}
bool init_buffer = false;
vertex_buffer_data_new->load(*obj_data_new);
index_buffer_data_new->load(*obj_data_new);
for (auto& k : material_colors) {
uint32_t material_index = -1;
uint32_t num_material = obj->num_material;
@@ -10376,9 +10412,6 @@ static void x_pv_game_split_auth_3d_material_list(x_pv_game* xpvgm,
obj_db.update();
delete[] vertex_buffer_data;
delete[] index_buffer_data;
obj_data = set->obj_data;
handler->obj_id_data.clear();
+26
View File
@@ -318,3 +318,29 @@ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------
meshoptimizer
MIT License
Copyright (c) 2016-2023 Arseny Kapoulkine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.