Updated meshoptimizer.

This commit is contained in:
Бранимир Караџић
2019-12-21 22:04:30 -08:00
parent 609ee1b7c4
commit 6a5d1b1c7a
8 changed files with 507 additions and 254 deletions

View File

@@ -37,7 +37,7 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: cmake configure
run: cmake . -DBUILD_DEMO=ON -DBUILD_TOOLS=ON -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" -A ${{matrix.arch}}
run: cmake . -DMESHOPT_BUILD_DEMO=ON -DMESHOPT_BUILD_TOOLS=ON -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$<CONFIG:Debug>:Debug>" -A ${{matrix.arch}}
- name: cmake test
shell: bash # necessary for fail-fast
run: |

View File

@@ -26,7 +26,7 @@ script:
- if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make -j2 config=debug test; fi
- if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make -j2 config=release test; fi
- if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then make -j2 config=release gltfpack; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake -G "$TARGET" -DBUILD_DEMO=ON -DBUILD_TOOLS=ON; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake -G "$TARGET" -DMESHOPT_BUILD_DEMO=ON -DMESHOPT_BUILD_TOOLS=ON; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Debug -verbosity:minimal; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then ./Debug/demo.exe demo/pirate.obj; fi
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then cmake --build . -- -property:Configuration=Release -verbosity:minimal; fi

View File

@@ -1,15 +1,16 @@
cmake_minimum_required(VERSION 3.0)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.15)
if (CMAKE_VERSION VERSION_GREATER 3.15 OR CMAKE_VERSION VERSION_EQUAL 3.15)
cmake_policy(SET CMP0091 NEW) # Enables use of MSVC_RUNTIME_LIBRARY
cmake_policy(SET CMP0092 NEW) # Enables clean /W4 override for MSVC
endif()
project(meshoptimizer VERSION 0.13 LANGUAGES CXX)
option(BUILD_DEMO "Build demo" OFF)
option(BUILD_TOOLS "Build tools" OFF)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(MESHOPT_BUILD_DEMO "Build demo" OFF)
option(MESHOPT_BUILD_TOOLS "Build tools" OFF)
option(MESHOPT_BUILD_SHARED_LIBS "Build shared libraries" OFF)
set(MESHOPT_BUILD_TOOLS_GLFW_FOLDER_NAME "" CACHE STRING "Custom folder to look for GLFW")
set(SOURCES
src/meshoptimizer.h
@@ -38,7 +39,7 @@ endif()
add_library(meshoptimizer ${SOURCES})
target_include_directories(meshoptimizer INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>")
if(BUILD_SHARED_LIBS)
if(MESHOPT_BUILD_SHARED_LIBS)
set_target_properties(meshoptimizer PROPERTIES CXX_VISIBILITY_PRESET hidden)
set_target_properties(meshoptimizer PROPERTIES VISIBILITY_INLINES_HIDDEN ON)
@@ -52,20 +53,38 @@ endif()
set(TARGETS meshoptimizer)
if(BUILD_DEMO)
if(MESHOPT_BUILD_DEMO)
add_executable(demo demo/main.cpp demo/miniz.cpp demo/tests.cpp tools/meshloader.cpp)
target_link_libraries(demo meshoptimizer)
endif()
if(BUILD_TOOLS)
if(MESHOPT_BUILD_TOOLS)
add_executable(gltfpack tools/gltfpack.cpp tools/meshloader.cpp tools/basistoktx.cpp)
target_link_libraries(gltfpack meshoptimizer)
list(APPEND TARGETS gltfpack)
if(BUILD_SHARED_LIBS)
if(MESHOPT_BUILD_SHARED_LIBS)
string(CONCAT RPATH "$ORIGIN/../" ${CMAKE_INSTALL_LIBDIR})
set_target_properties(gltfpack PROPERTIES INSTALL_RPATH ${RPATH})
endif()
if(NOT (MESHOPT_BUILD_TOOLS_GLFW_FOLDER_NAME STREQUAL ""))
message(STATUS "Using GLFW3 from: ${MESHOPT_BUILD_TOOLS_GLFW_FOLDER_NAME}")
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "")
set(GLFW_BUILD_TESTS OFF CACHE BOOL "")
set(GLFW_BUILD_DOCS OFF CACHE BOOL "")
set(GLFW_INSTALL OFF CACHE BOOL "")
add_subdirectory(${MESHOPT_BUILD_TOOLS_GLFW_FOLDER_NAME})
set(glfw3_FOUND TRUE)
set(glfw3_LIBRARY glfw)
else()
find_package(glfw3 3.3 QUIET)
endif()
if (glfw3_FOUND)
add_executable(lodviewer tools/lodviewer.cpp tools/meshloader.cpp)
target_link_libraries(lodviewer ${glfw3_LIBRARY} meshoptimizer)
endif()
endif()
include(GNUInstallDirs)

View File

@@ -2609,108 +2609,109 @@ THREE.GLTFLoader = ( function () {
}
return Promise.all( pending ).then( function ( originalMaterials ) {
pending.push( parser.loadGeometries( primitives ) );
return parser.loadGeometries( primitives ).then( function ( geometries ) {
return Promise.all( pending ).then( function ( results ) {
var meshes = [];
var materials = results.slice( 0, results.length - 1 );
var geometries = results[ results.length - 1 ];
for ( var i = 0, il = geometries.length; i < il; i ++ ) {
var meshes = [];
var geometry = geometries[ i ];
var primitive = primitives[ i ];
for ( var i = 0, il = geometries.length; i < il; i ++ ) {
// 1. create Mesh
var geometry = geometries[ i ];
var primitive = primitives[ i ];
var mesh;
// 1. create Mesh
var material = originalMaterials[ i ];
var mesh;
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
primitive.mode === undefined ) {
var material = materials[ i ];
// .isSkinnedMesh isn't in glTF spec. See .markDefs()
mesh = meshDef.isSkinnedMesh === true
? new THREE.SkinnedMesh( geometry, material )
: new THREE.Mesh( geometry, material );
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
primitive.mode === undefined ) {
if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
// .isSkinnedMesh isn't in glTF spec. See .markDefs()
mesh = meshDef.isSkinnedMesh === true
? new THREE.SkinnedMesh( geometry, material )
: new THREE.Mesh( geometry, material );
// we normalize floating point skin weight array to fix malformed assets (see #15319)
// it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
mesh.normalizeSkinWeights();
if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
}
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
mesh.drawMode = THREE.TriangleStripDrawMode;
} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
mesh.drawMode = THREE.TriangleFanDrawMode;
}
} else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
mesh = new THREE.LineSegments( geometry, material );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
mesh = new THREE.Line( geometry, material );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
mesh = new THREE.LineLoop( geometry, material );
} else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
mesh = new THREE.Points( geometry, material );
} else {
throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
// we normalize floating point skin weight array to fix malformed assets (see #15319)
// it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
mesh.normalizeSkinWeights();
}
if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
updateMorphTargets( mesh, meshDef );
mesh.drawMode = THREE.TriangleStripDrawMode;
} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
mesh.drawMode = THREE.TriangleFanDrawMode;
}
mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
if ( geometries.length > 1 ) mesh.name += '_' + i;
mesh = new THREE.LineSegments( geometry, material );
assignExtrasToUserData( mesh, meshDef );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
parser.assignFinalMaterial( mesh );
mesh = new THREE.Line( geometry, material );
meshes.push( mesh );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
mesh = new THREE.LineLoop( geometry, material );
} else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
mesh = new THREE.Points( geometry, material );
} else {
throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
}
if ( meshes.length === 1 ) {
if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
return meshes[ 0 ];
updateMorphTargets( mesh, meshDef );
}
var group = new THREE.Group();
mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
for ( var i = 0, il = meshes.length; i < il; i ++ ) {
if ( geometries.length > 1 ) mesh.name += '_' + i;
group.add( meshes[ i ] );
assignExtrasToUserData( mesh, meshDef );
}
parser.assignFinalMaterial( mesh );
return group;
meshes.push( mesh );
} );
}
if ( meshes.length === 1 ) {
return meshes[ 0 ];
}
var group = new THREE.Group();
for ( var i = 0, il = meshes.length; i < il; i ++ ) {
group.add( meshes[ i ] );
}
return group;
} );

View File

@@ -125,7 +125,12 @@ static void createDfd(std::vector<uint32_t>& result, int channels, bool srgb)
for (int i = 0; i < channels; ++i)
{
uint32_t qualifiers = (srgb && i == 3) ? KHR_DF_SAMPLE_DATATYPE_LINEAR : 0;
KHR_DFDSETSVAL(dfd, i, CHANNELID, channel_enums[i]);
KHR_DFDSETSVAL(dfd, i, QUALIFIERS, qualifiers);
KHR_DFDSETSVAL(dfd, i, SAMPLELOWER, 0);
KHR_DFDSETSVAL(dfd, i, SAMPLEUPPER, ~0u);
}
}

View File

@@ -1258,6 +1258,66 @@ cgltf_result cgltf_validate(cgltf_data* data)
}
}
for (cgltf_size i = 0; i < data->nodes_count; ++i)
{
cgltf_node* p1 = data->nodes[i].parent;
cgltf_node* p2 = p1 ? p1->parent : NULL;
while (p1 && p2)
{
if (p1 == p2)
{
return cgltf_result_invalid_gltf;
}
p1 = p1->parent;
p2 = p2->parent ? p2->parent->parent : NULL;
}
}
for (cgltf_size i = 0; i < data->scenes_count; ++i)
{
for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j)
{
if (data->scenes[i].nodes[j]->parent)
{
return cgltf_result_invalid_gltf;
}
}
}
for (cgltf_size i = 0; i < data->animations_count; ++i)
{
for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j)
{
cgltf_animation_channel* channel = &data->animations[i].channels[j];
if (!channel->target_node)
{
continue;
}
cgltf_size components = 1;
if (channel->target_path == cgltf_animation_path_type_weights)
{
if (!channel->target_node->mesh || !channel->target_node->mesh->primitives_count)
{
return cgltf_result_invalid_gltf;
}
components = channel->target_node->mesh->primitives[0].targets_count;
}
cgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1;
if (channel->sampler->input->count * components * values != channel->sampler->output->count)
{
return cgltf_result_data_too_short;
}
}
}
return cgltf_result_success;
}

View File

@@ -76,6 +76,31 @@ struct Mesh
std::vector<const char*> target_names;
};
struct Track
{
cgltf_node* node;
cgltf_animation_path_type path;
bool dummy;
size_t components; // 1 unless path is cgltf_animation_path_type_weights
cgltf_interpolation_type interpolation;
std::vector<float> time; // empty for resampled or constant animations
std::vector<Attr> data;
};
struct Animation
{
const char* name;
float start;
int frames;
std::vector<Track> tracks;
};
struct Settings
{
int pos_bits;
@@ -290,7 +315,7 @@ void transformMesh(Mesh& mesh, const cgltf_node* node)
}
}
void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes)
void parseMeshes(cgltf_data* data, std::vector<Mesh>& meshes)
{
for (size_t ni = 0; ni < data->nodes_count; ++ni)
{
@@ -318,7 +343,7 @@ void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes)
continue;
}
Mesh result;
Mesh result = {};
result.node = &node;
@@ -563,6 +588,49 @@ void parseMeshesObj(fastObjMesh* obj, cgltf_data* data, std::vector<Mesh>& meshe
}
}
void parseAnimations(cgltf_data* data, std::vector<Animation>& animations)
{
for (size_t i = 0; i < data->animations_count; ++i)
{
const cgltf_animation& animation = data->animations[i];
Animation result = {};
result.name = animation.name;
for (size_t j = 0; j < animation.channels_count; ++j)
{
const cgltf_animation_channel& channel = animation.channels[j];
if (!channel.target_node)
{
fprintf(stderr, "Warning: ignoring channel %d of animation %d because it has no target node\n", int(j), int(i));
continue;
}
Track track = {};
track.node = channel.target_node;
track.path = channel.target_path;
track.components = (channel.target_path == cgltf_animation_path_type_weights) ? track.node->mesh->primitives[0].targets_count : 1;
track.interpolation = channel.sampler->interpolation;
readAccessor(track.time, channel.sampler->input);
readAccessor(track.data, channel.sampler->output);
result.tracks.push_back(track);
}
if (result.tracks.empty())
{
fprintf(stderr, "Warning: ignoring animation %d because it has no valid tracks\n", int(i));
continue;
}
animations.push_back(result);
}
}
bool areTextureViewsEqual(const cgltf_texture_view& lhs, const cgltf_texture_view& rhs)
{
if (lhs.has_transform != rhs.has_transform)
@@ -2300,69 +2368,46 @@ void writeAccessor(std::string& json, size_t view, size_t offset, cgltf_type typ
float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type)
{
if (type == cgltf_animation_path_type_rotation)
switch (type)
{
float error = 1.f - fabsf(l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3]);
case cgltf_animation_path_type_translation:
return std::max(std::max(fabsf(l.f[0] - r.f[0]), fabsf(l.f[1] - r.f[1])), fabsf(l.f[2] - r.f[2]));
return error;
}
else
{
float error = 0;
for (int k = 0; k < 4; ++k)
error += fabsf(r.f[k] - l.f[k]);
case cgltf_animation_path_type_rotation:
return acosf(std::min(1.f, fabsf(l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3])));
return error;
case cgltf_animation_path_type_scale:
return std::max(std::max(fabsf(l.f[0] / r.f[0] - 1), fabsf(l.f[1] / r.f[1] - 1)), fabsf(l.f[2] / r.f[2] - 1));
case cgltf_animation_path_type_weights:
return fabsf(l.f[0] - r.f[0]);
default:
assert(!"Uknown animation path");
return 0;
}
}
bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node, Attr* out_first = 0)
float getDeltaTolerance(cgltf_animation_path_type type)
{
const float tolerance = 1e-3f;
size_t value_stride = (sampler.interpolation == cgltf_interpolation_type_cubic_spline) ? 3 : 1;
size_t value_offset = (sampler.interpolation == cgltf_interpolation_type_cubic_spline) ? 1 : 0;
size_t components = (type == cgltf_animation_path_type_weights) ? target_node->mesh->primitives[0].targets_count : 1;
assert(sampler.input->count * value_stride * components == sampler.output->count);
std::vector<Attr> output;
readAccessor(output, sampler.output);
for (size_t j = 0; j < components; ++j)
switch (type)
{
Attr first = output[j * value_stride + value_offset];
case cgltf_animation_path_type_translation:
return 0.001f; // linear
for (size_t i = 1; i < sampler.input->count; ++i)
{
const Attr& attr = output[(i * components + j) * value_stride + value_offset];
case cgltf_animation_path_type_rotation:
return 0.001f; // radians
if (getDelta(first, attr, type) > tolerance)
return false;
}
case cgltf_animation_path_type_scale:
return 0.001f; // ratio
if (sampler.interpolation == cgltf_interpolation_type_cubic_spline)
{
for (size_t i = 0; i < sampler.input->count; ++i)
{
for (int k = 0; k < 2; ++k)
{
const Attr& t = output[(i * components + j) * 3 + k * 2];
case cgltf_animation_path_type_weights:
return 0.001f; // linear
float error = fabsf(t.f[0]) + fabsf(t.f[1]) + fabsf(t.f[2]) + fabsf(t.f[3]);
if (error > tolerance)
return false;
}
}
}
default:
assert(!"Uknown animation path");
return 0;
}
if (out_first)
*out_first = output[value_offset];
return true;
}
Attr interpolateLinear(const Attr& l, const Attr& r, float t, cgltf_animation_path_type type)
@@ -2447,22 +2492,15 @@ Attr interpolateHermite(const Attr& v0, const Attr& t0, const Attr& v1, const At
return lerp;
}
void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node, int frames, float mint, int freq)
void resampleKeyframes(std::vector<Attr>& data, const std::vector<float>& input, const std::vector<Attr>& output, cgltf_animation_path_type type, cgltf_interpolation_type interpolation, size_t components, int frames, float mint, int freq)
{
size_t components = (type == cgltf_animation_path_type_weights) ? target_node->mesh->primitives[0].targets_count : 1;
std::vector<float> input;
readAccessor(input, sampler.input);
std::vector<Attr> output;
readAccessor(output, sampler.output);
size_t cursor = 0;
for (int i = 0; i < frames; ++i)
{
float time = mint + float(i) / freq;
while (cursor + 1 < sampler.input->count)
while (cursor + 1 < input.size())
{
float next_time = input[cursor + 1];
@@ -2472,7 +2510,7 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
cursor++;
}
if (cursor + 1 < sampler.input->count)
if (cursor + 1 < input.size())
{
float cursor_time = input[cursor + 0];
float next_time = input[cursor + 1];
@@ -2483,7 +2521,7 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
for (size_t j = 0; j < components; ++j)
{
switch (sampler.interpolation)
switch (interpolation)
{
case cgltf_interpolation_type_linear:
{
@@ -2517,7 +2555,7 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
}
else
{
size_t offset = (sampler.interpolation == cgltf_interpolation_type_cubic_spline) ? cursor * 3 + 1 : cursor;
size_t offset = (interpolation == cgltf_interpolation_type_cubic_spline) ? cursor * 3 + 1 : cursor;
for (size_t j = 0; j < components; ++j)
{
@@ -2528,59 +2566,123 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
}
}
void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes)
bool isTrackEqual(const std::vector<Attr>& data, cgltf_animation_path_type type, int frames, const Attr* value, size_t components)
{
for (size_t i = 0; i < data->animations_count; ++i)
assert(data.size() == frames * components);
float tolerance = getDeltaTolerance(type);
for (int i = 0; i < frames; ++i)
{
const cgltf_animation& animation = data->animations[i];
for (size_t j = 0; j < animation.channels_count; ++j)
for (size_t j = 0; j < components; ++j)
{
const cgltf_animation_channel& channel = animation.channels[j];
const cgltf_animation_sampler& sampler = *channel.sampler;
float delta = getDelta(value[j], data[i * components + j], type);
if (!channel.target_node)
continue;
if (delta > tolerance)
return false;
}
}
NodeInfo& ni = nodes[channel.target_node - data->nodes];
return true;
}
void getBaseTransform(Attr* result, size_t components, cgltf_animation_path_type type, cgltf_node* node)
{
switch (type)
{
case cgltf_animation_path_type_translation:
memcpy(result->f, node->translation, 3 * sizeof(float));
break;
case cgltf_animation_path_type_rotation:
memcpy(result->f, node->rotation, 4 * sizeof(float));
break;
case cgltf_animation_path_type_scale:
memcpy(result->f, node->scale, 3 * sizeof(float));
break;
case cgltf_animation_path_type_weights:
if (node->weights_count)
{
assert(node->weights_count == components);
memcpy(result->f, node->weights, components * sizeof(float));
}
else if (node->mesh && node->mesh->weights_count)
{
assert(node->mesh->weights_count == components);
memcpy(result->f, node->mesh->weights, components * sizeof(float));
}
break;
default:
assert(!"Unknown animation path");
}
}
void processAnimation(Animation& animation, const Settings& settings)
{
float mint = 0, maxt = 0;
for (size_t i = 0; i < animation.tracks.size(); ++i)
{
const Track& track = animation.tracks[i];
assert(!track.time.empty());
mint = std::min(mint, track.time.front());
maxt = std::max(maxt, track.time.back());
}
// round the number of frames to nearest but favor the "up" direction
// this means that at 10 Hz resampling, we will try to preserve the last frame <10ms
// but if the last frame is <2ms we favor just removing this data
int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f);
animation.start = mint;
animation.frames = frames;
std::vector<Attr> base;
for (size_t i = 0; i < animation.tracks.size(); ++i)
{
Track& track = animation.tracks[i];
std::vector<Attr> result;
resampleKeyframes(result, track.time, track.data, track.path, track.interpolation, track.components, frames, mint, settings.anim_freq);
track.time.clear();
track.data.swap(result);
if (isTrackEqual(track.data, track.path, frames, &track.data[0], track.components))
{
// track is constant (equal to first keyframe), we only need the first keyframe
track.data.resize(track.components);
// track.dummy is true iff track redundantly sets up the value to be equal to default node transform
base.resize(track.components);
getBaseTransform(&base[0], track.components, track.path, track.node);
track.dummy = isTrackEqual(track.data, track.path, 1, &base[0], track.components);
}
}
}
void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Animation>& animations)
{
for (size_t i = 0; i < animations.size(); ++i)
{
const Animation& animation = animations[i];
for (size_t j = 0; j < animation.tracks.size(); ++j)
{
const Track& track = animation.tracks[j];
// mark nodes that have animation tracks that change their base transform as animated
Attr first = {};
if (!isTrackConstant(sampler, channel.target_path, channel.target_node, &first))
if (!track.dummy)
{
ni.animated_paths |= (1 << channel.target_path);
}
else if (channel.target_path == cgltf_animation_path_type_weights)
{
// we currently preserve constant weight tracks because the usecase is very rare and
// isTrackConstant doesn't return the full set of weights to compare against
ni.animated_paths |= (1 << channel.target_path);
}
else
{
Attr base = {};
NodeInfo& ni = nodes[track.node - data->nodes];
switch (channel.target_path)
{
case cgltf_animation_path_type_translation:
memcpy(base.f, channel.target_node->translation, 3 * sizeof(float));
break;
case cgltf_animation_path_type_rotation:
memcpy(base.f, channel.target_node->rotation, 4 * sizeof(float));
break;
case cgltf_animation_path_type_scale:
memcpy(base.f, channel.target_node->scale, 3 * sizeof(float));
break;
default:
assert(!"Unknown target path");
}
const float tolerance = 1e-3f;
if (getDelta(base, first, channel.target_path) > tolerance)
{
ni.animated_paths |= (1 << channel.target_path);
}
ni.animated_paths |= (1 << track.path);
}
}
}
@@ -2594,7 +2696,7 @@ void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes)
}
}
void markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Mesh>& meshes, const Settings& settings)
void markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Mesh>& meshes, const std::vector<Animation>& animations, const Settings& settings)
{
// mark all joints as kept
for (size_t i = 0; i < data->skins_count; ++i)
@@ -2611,17 +2713,17 @@ void markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::
}
// mark all animated nodes as kept
for (size_t i = 0; i < data->animations_count; ++i)
for (size_t i = 0; i < animations.size(); ++i)
{
const cgltf_animation& animation = data->animations[i];
const Animation& animation = animations[i];
for (size_t j = 0; j < animation.channels_count; ++j)
for (size_t j = 0; j < animation.tracks.size(); ++j)
{
const cgltf_animation_channel& channel = animation.channels[j];
const Track& track = animation.tracks[j];
if (channel.target_node)
if (settings.anim_const || !track.dummy)
{
NodeInfo& ni = nodes[channel.target_node - data->nodes];
NodeInfo& ni = nodes[track.node - data->nodes];
ni.keep = true;
}
@@ -3355,62 +3457,46 @@ void writeNode(std::string& json, const cgltf_node& node, const std::vector<Node
append(json, "}");
}
void writeAnimation(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const cgltf_animation& animation, cgltf_data* data, const std::vector<NodeInfo>& nodes, const Settings& settings)
void writeAnimation(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector<NodeInfo>& nodes, const Settings& settings)
{
std::vector<const cgltf_animation_channel*> tracks;
std::vector<const Track*> tracks;
for (size_t j = 0; j < animation.channels_count; ++j)
for (size_t j = 0; j < animation.tracks.size(); ++j)
{
const cgltf_animation_channel& channel = animation.channels[j];
const Track& track = animation.tracks[j];
if (!channel.target_node)
{
fprintf(stderr, "Warning: ignoring channel %d of animation %d because it has no target node\n", int(j), int(&animation - data->animations));
continue;
}
const NodeInfo& ni = nodes[channel.target_node - data->nodes];
const NodeInfo& ni = nodes[track.node - data->nodes];
if (!ni.keep)
continue;
if (!settings.anim_const && (ni.animated_paths & (1 << channel.target_path)) == 0)
if (!settings.anim_const && (ni.animated_paths & (1 << track.path)) == 0)
continue;
tracks.push_back(&channel);
tracks.push_back(&track);
}
if (tracks.empty())
{
fprintf(stderr, "Warning: ignoring animation %d because it has no valid tracks\n", int(&animation - data->animations));
fprintf(stderr, "Warning: ignoring animation %d because it has no valid tracks\n", int(i));
return;
}
float mint = 0, maxt = 0;
bool needs_time = false;
bool needs_pose = false;
for (size_t j = 0; j < tracks.size(); ++j)
{
const cgltf_animation_channel& channel = *tracks[j];
const cgltf_animation_sampler& sampler = *channel.sampler;
const Track& track = *tracks[j];
mint = std::min(mint, sampler.input->min[0]);
maxt = std::max(maxt, sampler.input->max[0]);
bool tc = isTrackConstant(sampler, channel.target_path, channel.target_node);
bool tc = track.data.size() == track.components;
needs_time = needs_time || !tc;
needs_pose = needs_pose || tc;
}
// round the number of frames to nearest but favor the "up" direction
// this means that at 10 Hz resampling, we will try to preserve the last frame <10ms
// but if the last frame is <2ms we favor just removing this data
int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f);
size_t time_accr = needs_time ? writeAnimationTime(views, json_accessors, accr_offset, mint, frames, settings) : 0;
size_t pose_accr = needs_pose ? writeAnimationTime(views, json_accessors, accr_offset, mint, 1, settings) : 0;
size_t time_accr = needs_time ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, animation.frames, settings) : 0;
size_t pose_accr = needs_pose ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 1, settings) : 0;
std::string json_samplers;
std::string json_channels;
@@ -3419,23 +3505,19 @@ void writeAnimation(std::string& json, std::vector<BufferView>& views, std::stri
for (size_t j = 0; j < tracks.size(); ++j)
{
const cgltf_animation_channel& channel = *tracks[j];
const cgltf_animation_sampler& sampler = *channel.sampler;
const Track& track = *tracks[j];
bool tc = isTrackConstant(sampler, channel.target_path, channel.target_node);
std::vector<Attr> track;
resampleKeyframes(track, sampler, channel.target_path, channel.target_node, tc ? 1 : frames, mint, settings.anim_freq);
bool tc = track.data.size() == track.components;
std::string scratch;
StreamFormat format = writeKeyframeStream(scratch, channel.target_path, track);
StreamFormat format = writeKeyframeStream(scratch, track.path, track.data);
size_t view = getBufferView(views, BufferView::Kind_Keyframe, channel.target_path, format.stride, settings.compress && channel.target_path != cgltf_animation_path_type_weights);
size_t view = getBufferView(views, BufferView::Kind_Keyframe, track.path, format.stride, settings.compress && track.path != cgltf_animation_path_type_weights);
size_t offset = views[view].data.size();
views[view].data += scratch;
comma(json_accessors);
writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, track.size());
writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, track.data.size());
size_t data_accr = accr_offset++;
@@ -3446,10 +3528,10 @@ void writeAnimation(std::string& json, std::vector<BufferView>& views, std::stri
append(json_samplers, data_accr);
append(json_samplers, "}");
const NodeInfo& tni = nodes[channel.target_node - data->nodes];
const NodeInfo& tni = nodes[track.node - data->nodes];
size_t target_node = size_t(tni.remap);
if (channel.target_path == cgltf_animation_path_type_weights)
if (track.path == cgltf_animation_path_type_weights)
{
assert(tni.meshes.size() == 1);
target_node = tni.meshes[0];
@@ -3461,7 +3543,7 @@ void writeAnimation(std::string& json, std::vector<BufferView>& views, std::stri
append(json_channels, ",\"target\":{\"node\":");
append(json_channels, target_node);
append(json_channels, ",\"path\":\"");
append(json_channels, animationPath(channel.target_path));
append(json_channels, animationPath(track.path));
append(json_channels, "\"}}");
track_offset++;
@@ -3732,17 +3814,23 @@ void printAttributeStats(const std::vector<BufferView>& views, BufferView::Kind
}
}
void process(cgltf_data* data, const char* input_path, const char* output_path, std::vector<Mesh>& meshes, const Settings& settings, std::string& json, std::string& bin, std::string& fallback)
void process(cgltf_data* data, const char* input_path, const char* output_path, std::vector<Mesh>& meshes, std::vector<Animation>& animations, const Settings& settings, std::string& json, std::string& bin, std::string& fallback)
{
if (settings.verbose)
{
printf("input: %d nodes, %d meshes (%d primitives), %d materials, %d skins, %d animations\n",
int(data->nodes_count), int(data->meshes_count), int(meshes.size()), int(data->materials_count), int(data->skins_count), int(data->animations_count));
int(data->nodes_count), int(data->meshes_count), int(meshes.size()), int(data->materials_count), int(data->skins_count), int(animations.size()));
printMeshStats(meshes, "input");
}
for (size_t i = 0; i < animations.size(); ++i)
{
processAnimation(animations[i], settings);
}
std::vector<NodeInfo> nodes(data->nodes_count);
markAnimated(data, nodes);
markAnimated(data, nodes, animations);
for (size_t i = 0; i < meshes.size(); ++i)
{
@@ -3771,17 +3859,12 @@ void process(cgltf_data* data, const char* input_path, const char* output_path,
mergeMeshes(meshes, settings);
filterEmptyMeshes(meshes);
markNeededNodes(data, nodes, meshes, settings);
markNeededNodes(data, nodes, meshes, animations, settings);
std::vector<MaterialInfo> materials(data->materials_count);
markNeededMaterials(data, materials, meshes);
if (settings.verbose)
{
printMeshStats(meshes, "input");
}
for (size_t i = 0; i < meshes.size(); ++i)
{
processMesh(meshes[i], settings);
@@ -3789,11 +3872,6 @@ void process(cgltf_data* data, const char* input_path, const char* output_path,
filterEmptyMeshes(meshes); // some meshes may become empty after processing
if (settings.verbose)
{
printMeshStats(meshes, "output");
}
std::vector<ImageInfo> images(data->images_count);
analyzeImages(data, images);
@@ -4026,11 +4104,11 @@ void process(cgltf_data* data, const char* input_path, const char* output_path,
append(json_skins, "}");
}
for (size_t i = 0; i < data->animations_count; ++i)
for (size_t i = 0; i < animations.size(); ++i)
{
const cgltf_animation& animation = data->animations[i];
const Animation& animation = animations[i];
writeAnimation(json_animations, views, json_accessors, accr_offset, animation, data, nodes, settings);
writeAnimation(json_animations, views, json_accessors, accr_offset, animation, i, data, nodes, settings);
}
for (size_t i = 0; i < data->cameras_count; ++i)
@@ -4110,6 +4188,7 @@ void process(cgltf_data* data, const char* input_path, const char* output_path,
if (settings.verbose)
{
printMeshStats(meshes, "output");
printSceneStats(views, meshes, node_offset, mesh_offset, material_offset, json.size(), bin.size());
}
@@ -4212,6 +4291,7 @@ int gltfpack(const char* input, const char* output, const Settings& settings)
{
cgltf_data* data = 0;
std::vector<Mesh> meshes;
std::vector<Animation> animations;
const char* iext = strrchr(input, '.');
@@ -4240,7 +4320,8 @@ int gltfpack(const char* input, const char* output, const Settings& settings)
return 2;
}
parseMeshesGltf(data, meshes);
parseMeshes(data, meshes);
parseAnimations(data, animations);
}
else if (iext && (strcmp(iext, ".obj") == 0 || strcmp(iext, ".OBJ") == 0))
{
@@ -4274,7 +4355,7 @@ int gltfpack(const char* input, const char* output, const Settings& settings)
}
std::string json, bin, fallback;
process(data, input, output, meshes, settings, json, bin, fallback);
process(data, input, output, meshes, animations, settings, json, bin, fallback);
cgltf_free(data);

View File

@@ -49,6 +49,9 @@ struct Mesh
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
bool hasnormals;
bool hastexture;
// TODO: this is debug only visualization and will go away at some point
std::vector<unsigned char> kinds;
std::vector<unsigned int> loop;
@@ -73,6 +76,9 @@ Mesh parseObj(const char* path)
size_t vertex_offset = 0;
size_t index_offset = 0;
bool hasnormals = false;
bool hastexture = false;
for (unsigned int i = 0; i < obj->face_count; ++i)
{
for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
@@ -91,6 +97,9 @@ Mesh parseObj(const char* path)
obj->texcoords[gi.t * 2 + 1],
};
hasnormals |= (gi.n > 0);
hastexture |= (gi.t > 0);
// triangulate polygon on the fly; offset-3 is always the first polygon vertex
if (j >= 3)
{
@@ -119,6 +128,9 @@ Mesh parseObj(const char* path)
result.vertices.resize(total_vertices);
meshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]);
result.hasnormals = hasnormals;
result.hastexture = hastexture;
return result;
}
@@ -188,6 +200,9 @@ Mesh parseGltf(const char* path)
size_t vertex_offset = 0;
size_t index_offset = 0;
bool hasnormals = false;
bool hastexture = false;
for (size_t ni = 0; ni < data->nodes_count; ++ni)
{
if (!data->nodes[ni].mesh)
@@ -234,6 +249,8 @@ Mesh parseGltf(const char* path)
result.vertices[vertex_offset + i].ny = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9];
result.vertices[vertex_offset + i].nz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10];
}
hasnormals = true;
}
if (cgltf_accessor* at = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_texcoord))
@@ -246,6 +263,8 @@ Mesh parseGltf(const char* path)
result.vertices[vertex_offset + i].tx = ptr[0];
result.vertices[vertex_offset + i].ty = ptr[1];
}
hastexture = true;
}
vertex_offset += ap->count;
@@ -253,6 +272,9 @@ Mesh parseGltf(const char* path)
}
}
result.hasnormals = hasnormals;
result.hastexture = hastexture;
std::vector<unsigned int> remap(total_indices);
size_t unique_vertices = meshopt_generateVertexRemap(&remap[0], &result.indices[0], total_indices, &result.vertices[0], total_vertices, sizeof(Vertex));
@@ -290,8 +312,12 @@ bool saveObj(const Mesh& mesh, const char* path)
for (size_t i = 0; i < vertcount; ++i)
{
fprintf(obj, "v %f %f %f\n", verts[i].px, verts[i].py, verts[i].pz);
fprintf(obj, "vn %f %f %f\n", verts[i].nx, verts[i].ny, verts[i].nz);
fprintf(obj, "vt %f %f %f\n", verts[i].tx, verts[i].ty, 0.f);
if (mesh.hasnormals)
fprintf(obj, "vn %f %f %f\n", verts[i].nx, verts[i].ny, verts[i].nz);
if (mesh.hastexture)
fprintf(obj, "vt %f %f %f\n", verts[i].tx, verts[i].ty, 0.f);
}
for (size_t i = 0; i < tris.size(); i += 3)
@@ -300,7 +326,14 @@ bool saveObj(const Mesh& mesh, const char* path)
unsigned int i1 = tris[i + 1] + 1;
unsigned int i2 = tris[i + 2] + 1;
fprintf(obj, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", i0, i0, i0, i1, i1, i1, i2, i2, i2);
if (mesh.hasnormals && mesh.hastexture)
fprintf(obj, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", i0, i0, i0, i1, i1, i1, i2, i2, i2);
else if (mesh.hasnormals && !mesh.hastexture)
fprintf(obj, "f %d//%d %d//%d %d//%d\n", i0, i0, i1, i1, i2, i2);
else if (!mesh.hasnormals && mesh.hastexture)
fprintf(obj, "f %d/%d %d/%d %d/%d\n", i0, i0, i1, i1, i2, i2);
else
fprintf(obj, "f %d %d %dd\n", i0, i1, i2);
}
fclose(obj);
@@ -324,6 +357,57 @@ Mesh optimize(const Mesh& mesh, int lod)
return result;
}
void computeNormals(Mesh& mesh)
{
if (mesh.hasnormals)
return;
for (size_t i = 0; i < mesh.vertices.size(); ++i)
{
Vertex& v = mesh.vertices[i];
v.nx = v.ny = v.nz = 0.f;
}
for (size_t i = 0; i < mesh.indices.size(); i += 3)
{
Vertex& v0 = mesh.vertices[mesh.indices[i + 0]];
Vertex& v1 = mesh.vertices[mesh.indices[i + 1]];
Vertex& v2 = mesh.vertices[mesh.indices[i + 2]];
float v10[3] = {v1.px - v0.px, v1.py - v0.py, v1.pz - v0.pz};
float v20[3] = {v2.px - v0.px, v2.py - v0.py, v2.pz - v0.pz};
float normalx = v10[1] * v20[2] - v10[2] * v20[1];
float normaly = v10[2] * v20[0] - v10[0] * v20[2];
float normalz = v10[0] * v20[1] - v10[1] * v20[0];
v0.nx += normalx;
v0.ny += normaly;
v0.nz += normalz;
v1.nx += normalx;
v1.ny += normaly;
v1.nz += normalz;
v2.nx += normalx;
v2.ny += normaly;
v2.nz += normalz;
}
for (size_t i = 0; i < mesh.vertices.size(); ++i)
{
Vertex& v = mesh.vertices[i];
float nl = sqrtf(v.nx * v.nx + v.ny * v.ny + v.nz * v.nz);
float ns = (nl == 0.f) ? 0.f : 1.f / nl;
v.nx *= ns;
v.ny *= ns;
v.nz *= ns;
}
}
void display(int x, int y, int width, int height, const Mesh& mesh, const Options& options)
{
glViewport(x, y, width, height);
@@ -607,6 +691,9 @@ int main(int argc, char** argv)
int x = int(i) % cols;
int y = int(i) / cols;
if (options.mode == Options::Mode_Normals)
computeNormals(f.lodmesh);
display(x * tilew, y * tileh, tilew, tileh, f.lodmesh, options);
}