diff --git a/3rdparty/meshoptimizer/.github/workflows/build.yml b/3rdparty/meshoptimizer/.github/workflows/build.yml index 18dd693a2..00e8ba807 100644 --- a/3rdparty/meshoptimizer/.github/workflows/build.yml +++ b/3rdparty/meshoptimizer/.github/workflows/build.yml @@ -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$<$:Debug>" -A ${{matrix.arch}} + run: cmake . -DMESHOPT_BUILD_DEMO=ON -DMESHOPT_BUILD_TOOLS=ON -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -A ${{matrix.arch}} - name: cmake test shell: bash # necessary for fail-fast run: | diff --git a/3rdparty/meshoptimizer/.travis.yml b/3rdparty/meshoptimizer/.travis.yml index 038f8af71..e6cdf1a4d 100644 --- a/3rdparty/meshoptimizer/.travis.yml +++ b/3rdparty/meshoptimizer/.travis.yml @@ -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 diff --git a/3rdparty/meshoptimizer/CMakeLists.txt b/3rdparty/meshoptimizer/CMakeLists.txt index cee2348b9..4d70333a9 100644 --- a/3rdparty/meshoptimizer/CMakeLists.txt +++ b/3rdparty/meshoptimizer/CMakeLists.txt @@ -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 "$") -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) diff --git a/3rdparty/meshoptimizer/demo/GLTFLoader.js b/3rdparty/meshoptimizer/demo/GLTFLoader.js index 4af9ceeb9..830ee4183 100644 --- a/3rdparty/meshoptimizer/demo/GLTFLoader.js +++ b/3rdparty/meshoptimizer/demo/GLTFLoader.js @@ -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; } ); diff --git a/3rdparty/meshoptimizer/tools/basistoktx.cpp b/3rdparty/meshoptimizer/tools/basistoktx.cpp index deca242e4..16b7051f8 100644 --- a/3rdparty/meshoptimizer/tools/basistoktx.cpp +++ b/3rdparty/meshoptimizer/tools/basistoktx.cpp @@ -125,7 +125,12 @@ static void createDfd(std::vector& 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); } } diff --git a/3rdparty/meshoptimizer/tools/cgltf.h b/3rdparty/meshoptimizer/tools/cgltf.h index 63ecb91fc..fc30e4ca1 100644 --- a/3rdparty/meshoptimizer/tools/cgltf.h +++ b/3rdparty/meshoptimizer/tools/cgltf.h @@ -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; } diff --git a/3rdparty/meshoptimizer/tools/gltfpack.cpp b/3rdparty/meshoptimizer/tools/gltfpack.cpp index 158e2c7c7..c54d27ab1 100644 --- a/3rdparty/meshoptimizer/tools/gltfpack.cpp +++ b/3rdparty/meshoptimizer/tools/gltfpack.cpp @@ -76,6 +76,31 @@ struct Mesh std::vector 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 time; // empty for resampled or constant animations + std::vector data; +}; + +struct Animation +{ + const char* name; + + float start; + int frames; + + std::vector 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& meshes) +void parseMeshes(cgltf_data* data, std::vector& meshes) { for (size_t ni = 0; ni < data->nodes_count; ++ni) { @@ -318,7 +343,7 @@ void parseMeshesGltf(cgltf_data* data, std::vector& meshes) continue; } - Mesh result; + Mesh result = {}; result.node = &node; @@ -563,6 +588,49 @@ void parseMeshesObj(fastObjMesh* obj, cgltf_data* data, std::vector& meshe } } +void parseAnimations(cgltf_data* data, std::vector& 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 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& 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& data, const std::vector& input, const std::vector& 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 input; - readAccessor(input, sampler.input); - std::vector 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& 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& 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& 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& data, const cgltf_animation_sampler& s } } -void markAnimated(cgltf_data* data, std::vector& nodes) +bool isTrackEqual(const std::vector& 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 base; + + for (size_t i = 0; i < animation.tracks.size(); ++i) + { + Track& track = animation.tracks[i]; + + std::vector 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& nodes, const std::vector& 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& nodes) } } -void markNeededNodes(cgltf_data* data, std::vector& nodes, const std::vector& meshes, const Settings& settings) +void markNeededNodes(cgltf_data* data, std::vector& nodes, const std::vector& meshes, const std::vector& 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& 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& views, std::string& json_accessors, size_t& accr_offset, const cgltf_animation& animation, cgltf_data* data, const std::vector& nodes, const Settings& settings) +void writeAnimation(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector& nodes, const Settings& settings) { - std::vector tracks; + std::vector 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& 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 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& 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& 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& views, BufferView::Kind } } -void process(cgltf_data* data, const char* input_path, const char* output_path, std::vector& 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& meshes, std::vector& 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 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 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 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 meshes; + std::vector 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); diff --git a/3rdparty/meshoptimizer/tools/lodviewer.cpp b/3rdparty/meshoptimizer/tools/lodviewer.cpp index 54dc4c909..bdfeec9e8 100644 --- a/3rdparty/meshoptimizer/tools/lodviewer.cpp +++ b/3rdparty/meshoptimizer/tools/lodviewer.cpp @@ -49,6 +49,9 @@ struct Mesh std::vector vertices; std::vector indices; + bool hasnormals; + bool hastexture; + // TODO: this is debug only visualization and will go away at some point std::vector kinds; std::vector 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 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); }