diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a5b2557..26fb50f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,11 +104,14 @@ set(RGL_SOURCE_FILES src/gpu/helpersKernels.cu src/gpu/gaussianNoiseKernels.cu src/gpu/nodeKernels.cu + src/gpu/sceneKernels.cu src/scene/Scene.cpp src/scene/Mesh.cpp src/scene/Entity.cpp src/scene/Texture.cpp src/scene/ASBuildScratchpad.cpp + src/scene/animator/ExternalAnimator.cpp + src/scene/animator/SkeletonAnimator.cpp src/graph/GraphRunCtx.cpp src/graph/Node.cpp src/graph/GaussianNoiseAngularHitpointNode.cpp diff --git a/docs/Usage.md b/docs/Usage.md index c0ed60fc..70b8fc2c 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -95,7 +95,7 @@ rgl_mat3x4f entity_tf = { { 0, 1, 0, 0 }, { 0, 0, 1, 5 }} }; -rgl_entity_set_pose(cube_entity, &entity_tf); +rgl_entity_set_transform(cube_entity, &entity_tf); // Create a Graph representation of a lidar that sends 1 ray and is situated at (x,y,z) = (0, 0, 0), facing positive Z rgl_mat3x4f ray_tf = { diff --git a/include/rgl/api/core.h b/include/rgl/api/core.h index bd262f7b..3f84878b 100644 --- a/include/rgl/api/core.h +++ b/include/rgl/api/core.h @@ -151,6 +151,31 @@ static_assert(std::is_trivial::value); static_assert(std::is_standard_layout::value); #endif +/** + * Describes 4 bone weights affecting a mesh vertex. + * The sum of all weights for a given vertex should equal 1 (RGL do not normalize them). + * If a vertex is affected by fewer than 4 bones, each of the remaining weight values must be 0. + * bone_indexes for unused bones must still be valid (filled with the existing bone indexes). + */ +typedef struct +{ + /** + * Array for skinning weights. + * The weight at each index corresponds to the bone_idx with the same index. + */ + float weights[4]; + /** + * Array for 4 bone indexes affecting a vertex. + */ + int32_t bone_indexes[4]; +} rgl_bone_weights_t; + +#ifdef __cplusplus +static_assert(sizeof(rgl_bone_weights_t) == 4 * sizeof(float) + 4 * sizeof(int32_t)); +static_assert(std::is_trivial::value); +static_assert(std::is_standard_layout::value); +#endif + /** * Radar object class used in object tracking. */ @@ -369,7 +394,8 @@ typedef enum : int32_t * - linear velocity * - angular velocity * - mesh deformations (e.g. skinning) - * The aforementioned are inferred from calls to `rgl_entity_set_pose`, `rgl_scene_set_time` and `rgl_mesh_update_vertices`. + * The aforementioned are inferred from calls to + * `rgl_entity_set_transform`, `rgl_scene_set_time` and `rgl_entity_apply_external_animation`, `rgl_entity_set_pose_world`. */ RGL_FIELD_ABSOLUTE_VELOCITY_VEC3_F32, @@ -512,6 +538,28 @@ RGL_API rgl_status_t rgl_mesh_create(rgl_mesh_t* out_mesh, const rgl_vec3f* vert */ RGL_API rgl_status_t rgl_mesh_set_texture_coords(rgl_mesh_t mesh, const rgl_vec2f* uvs, int32_t uv_count); +/** + * Assign bone weights to given Mesh. + * + * @param mesh Mesh to modify. + * @param bone_weights An array of rgl_bone_weights_t objects that associate vertices with the bones affecting them. + The bone weights object at each index corresponds to the vertex with the same index (`vertices` array of `rgl_mesh_create` API call). + * @param bone_weights_count Number of elements in the bone_weights array. It must be equal to the vertex count of the Mesh! + */ +RGL_API rgl_status_t rgl_mesh_set_bone_weights(rgl_mesh_t mesh, const rgl_bone_weights_t* bone_weights, + int32_t bone_weights_count); + +/** + * Assign restposes to given Mesh. + * + * @param mesh Mesh to modify. + * @param restposes An array containing inverse of the transformation matrix of the bone in restpose for each bone. + * Restpose at each index in the array corresponds to the bone with the same index. + * Typically, the restpose is the same as the bindpose. + * @param bones_count Number of elements in the restposes array. + */ +RGL_API rgl_status_t rgl_mesh_set_restposes(rgl_mesh_t mesh, const rgl_mat3x4f* restposes, int32_t bones_count); + /** * Informs that the given Mesh will be no longer used. * The Mesh will be destroyed after all referring Entities are destroyed. @@ -519,16 +567,6 @@ RGL_API rgl_status_t rgl_mesh_set_texture_coords(rgl_mesh_t mesh, const rgl_vec2 */ RGL_API rgl_status_t rgl_mesh_destroy(rgl_mesh_t mesh); -/** - * Updates Mesh vertex data. The number of vertices must not change. - * This function is intended to update animated Meshes. - * Should be called after rgl_scene_set_time to ensure proper velocity computation. - * @param mesh Mesh to modify - * @param vertices An array of rgl_vec3f or binary-compatible data representing Mesh vertices - * @param vertex_count Number of elements in the vertices array. It must be equal to the original vertex count! - */ -RGL_API rgl_status_t rgl_mesh_update_vertices(rgl_mesh_t mesh, const rgl_vec3f* vertices, int32_t vertex_count); - /** * Assigns value true to out_alive if the given mesh is known and has not been destroyed, * assigns value false otherwise. @@ -561,7 +599,20 @@ RGL_API rgl_status_t rgl_entity_destroy(rgl_entity_t entity); * @param entity Entity to modify * @param transform Pointer to rgl_mat3x4f (or binary-compatible data) representing desired (Entity -> world) coordinate system transform. */ -RGL_API rgl_status_t rgl_entity_set_pose(rgl_entity_t entity, const rgl_mat3x4f* transform); +RGL_API rgl_status_t rgl_entity_set_transform(rgl_entity_t entity, const rgl_mat3x4f* transform); + +/** + * Set the current pose of the given Entity in world coordinates. + * The pose stands for bone transforms used in skeleton animation. + * The mesh associated with this entity must have bone weights and restposes assigned. + * Since it is expected the pose is already in world coordinates, the API call `rgl_entity_set_transform` should no longer be called on this entity. + * Should be called after rgl_scene_set_time to ensure proper velocity computation. + * @param entity Entity to modify. + * @param pose An array containing transformation matrices of the bones in world coordinates. + * Bone transform at each index corresponds to the bone with the same index. + * @param bones_count Number of elements in the pose array. It must be equal to restposes count in the associated mesh! + */ +RGL_API rgl_status_t rgl_entity_set_pose_world(rgl_entity_t entity, const rgl_mat3x4f* pose, int32_t bones_count); /** * Set instance ID of the given Entity. @@ -586,6 +637,17 @@ RGL_API rgl_status_t rgl_entity_set_intensity_texture(rgl_entity_t entity, rgl_t */ RGL_API rgl_status_t rgl_entity_set_laser_retro(rgl_entity_t entity, float retro); +/** + * Provides updated vertices to the Entity resulted from external animation system. + * It does not modify Mesh API object bound to the Entity. + * The number of vertices must not change. + * Should be called after rgl_scene_set_time to ensure proper velocity computation. + * @param entity Entity to modify + * @param vertices An array of rgl_vec3f or binary-compatible data representing Mesh vertices + * @param vertex_count Number of elements in the vertices array. It must be equal to the original vertex count! + */ +RGL_API rgl_status_t rgl_entity_apply_external_animation(rgl_entity_t entity, const rgl_vec3f* vertices, int32_t vertex_count); + /** * Assigns value true to out_alive if the given entity is known and has not been destroyed, * assigns value false otherwise. @@ -723,7 +785,7 @@ RGL_API rgl_status_t rgl_node_raytrace(rgl_node_t* node, rgl_scene_t scene); * Necessary for velocity distortion or calculating fields: RGL_FIELD_RELATIVE_VELOCITY_VEC3_F32 and RGL_FIELD_RADIAL_SPEED_F32. * Relative velocity calculation: * To calculate relative velocity the pipeline must allow to compute absolute velocities. For more details refer to API calls documentation: - * `rgl_scene_set_time`, `rgl_entity_set_pose`, and `rgl_mesh_update_vertices` + * `rgl_scene_set_time`, `rgl_entity_set_transform`, `rgl_entity_set_pose_world`, and `rgl_entity_apply_external_animation` * @param node RaytraceNode to modify * @param linear_velocity 3D vector for linear velocity in units per second. * @param angular_velocity 3D vector for angular velocity in radians per second (roll, pitch, yaw). diff --git a/src/api/apiCore.cpp b/src/api/apiCore.cpp index 767a583d..829a35c6 100644 --- a/src/api/apiCore.cpp +++ b/src/api/apiCore.cpp @@ -217,43 +217,65 @@ void TapeCore::tape_mesh_set_texture_coords(const YAML::Node& yamlNode, Playback yamlNode[2].as()); } -RGL_API rgl_status_t rgl_mesh_destroy(rgl_mesh_t mesh) +RGL_API rgl_status_t rgl_mesh_set_bone_weights(rgl_mesh_t mesh, const rgl_bone_weights_t* bone_weights, + int32_t bone_weights_count) { auto status = rglSafeCall([&]() { - RGL_API_LOG("rgl_mesh_destroy(mesh={})", (void*) mesh); + RGL_API_LOG("rgl_mesh_set_bone_weights(mesh={}, bone_weights={})", (void*) mesh, + repr(bone_weights, bone_weights_count)); CHECK_ARG(mesh != nullptr); + CHECK_ARG(bone_weights != nullptr); + CHECK_ARG(bone_weights_count > 0); GraphRunCtx::synchronizeAll(); // Prevent races with graph threads - Mesh::release(mesh); + Mesh::validatePtr(mesh)->setBoneWeights(bone_weights, bone_weights_count); }); - TAPE_HOOK(mesh); + TAPE_HOOK(mesh, TAPE_ARRAY(bone_weights, bone_weights_count), bone_weights_count); return status; } -void TapeCore::tape_mesh_destroy(const YAML::Node& yamlNode, PlaybackState& state) +void TapeCore::tape_mesh_set_bone_weights(const YAML::Node& yamlNode, PlaybackState& state) { - auto meshId = yamlNode[0].as(); - rgl_mesh_destroy(state.meshes.at(meshId)); - state.meshes.erase(meshId); + rgl_mesh_set_bone_weights(state.meshes.at(yamlNode[0].as()), + state.getPtr(yamlNode[1]), yamlNode[2].as()); } -RGL_API rgl_status_t rgl_mesh_update_vertices(rgl_mesh_t mesh, const rgl_vec3f* vertices, int32_t vertex_count) +RGL_API rgl_status_t rgl_mesh_set_restposes(rgl_mesh_t mesh, const rgl_mat3x4f* restposes, int32_t restposes_count) { auto status = rglSafeCall([&]() { - RGL_API_LOG("rgl_mesh_update_vertices(mesh={}, vertices={})", (void*) mesh, repr(vertices, vertex_count)); + RGL_API_LOG("rgl_mesh_set_restposes(mesh={}, restposes={})", (void*) mesh, repr(restposes, restposes_count)); CHECK_ARG(mesh != nullptr); - CHECK_ARG(vertices != nullptr); - CHECK_ARG(vertex_count > 0); + CHECK_ARG(restposes != nullptr); + CHECK_ARG(restposes_count > 0); GraphRunCtx::synchronizeAll(); // Prevent races with graph threads - Mesh::validatePtr(mesh)->updateVertices(reinterpret_cast(vertices), vertex_count); + Mesh::validatePtr(mesh)->setRestposes(reinterpret_cast(restposes), restposes_count); }); - TAPE_HOOK(mesh, TAPE_ARRAY(vertices, vertex_count), vertex_count); + TAPE_HOOK(mesh, TAPE_ARRAY(restposes, restposes_count), restposes_count); return status; } -void TapeCore::tape_mesh_update_vertices(const YAML::Node& yamlNode, PlaybackState& state) +void TapeCore::tape_mesh_set_restposes(const YAML::Node& yamlNode, PlaybackState& state) { - rgl_mesh_update_vertices(state.meshes.at(yamlNode[0].as()), state.getPtr(yamlNode[1]), - yamlNode[2].as()); + rgl_mesh_set_restposes(state.meshes.at(yamlNode[0].as()), state.getPtr(yamlNode[1]), + yamlNode[2].as()); +} + +RGL_API rgl_status_t rgl_mesh_destroy(rgl_mesh_t mesh) +{ + auto status = rglSafeCall([&]() { + RGL_API_LOG("rgl_mesh_destroy(mesh={})", (void*) mesh); + CHECK_ARG(mesh != nullptr); + GraphRunCtx::synchronizeAll(); // Prevent races with graph threads + Mesh::release(mesh); + }); + TAPE_HOOK(mesh); + return status; +} + +void TapeCore::tape_mesh_destroy(const YAML::Node& yamlNode, PlaybackState& state) +{ + auto meshId = yamlNode[0].as(); + rgl_mesh_destroy(state.meshes.at(meshId)); + state.meshes.erase(meshId); } rgl_status_t rgl_mesh_is_alive(rgl_mesh_t mesh, bool* out_alive) @@ -308,10 +330,10 @@ void TapeCore::tape_entity_destroy(const YAML::Node& yamlNode, PlaybackState& st state.entities.erase(entityId); } -RGL_API rgl_status_t rgl_entity_set_pose(rgl_entity_t entity, const rgl_mat3x4f* transform) +RGL_API rgl_status_t rgl_entity_set_transform(rgl_entity_t entity, const rgl_mat3x4f* transform) { auto status = rglSafeCall([&]() { - RGL_API_LOG("rgl_entity_set_pose(entity={}, transform={})", (void*) entity, repr(transform, 1)); + RGL_API_LOG("rgl_entity_set_transform(entity={}, transform={})", (void*) entity, repr(transform, 1)); CHECK_ARG(entity != nullptr); CHECK_ARG(transform != nullptr); GraphRunCtx::synchronizeAll(); // Prevent races with graph threads @@ -322,9 +344,30 @@ RGL_API rgl_status_t rgl_entity_set_pose(rgl_entity_t entity, const rgl_mat3x4f* return status; } -void TapeCore::tape_entity_set_pose(const YAML::Node& yamlNode, PlaybackState& state) +void TapeCore::tape_entity_set_transform(const YAML::Node& yamlNode, PlaybackState& state) +{ + rgl_entity_set_transform(state.entities.at(yamlNode[0].as()), + state.getPtr(yamlNode[1])); +} + +RGL_API rgl_status_t rgl_entity_set_pose_world(rgl_entity_t entity, const rgl_mat3x4f* pose, int32_t bones_count) +{ + auto status = rglSafeCall([&]() { + RGL_API_LOG("rgl_entity_set_pose_world(entity={}, pose={})", (void*) entity, repr(pose, bones_count)); + CHECK_ARG(entity != nullptr); + CHECK_ARG(pose != nullptr); + CHECK_ARG(bones_count > 0); + GraphRunCtx::synchronizeAll(); // Prevent races with graph threads + Entity::validatePtr(entity)->setPoseAndAnimate(reinterpret_cast(pose), bones_count); + }); + TAPE_HOOK(entity, TAPE_ARRAY(pose, bones_count), bones_count); + return status; +} + +void TapeCore::tape_entity_set_pose_world(const YAML::Node& yamlNode, PlaybackState& state) { - rgl_entity_set_pose(state.entities.at(yamlNode[0].as()), state.getPtr(yamlNode[1])); + rgl_entity_set_pose_world(state.entities.at(yamlNode[0].as()), + state.getPtr(yamlNode[1]), yamlNode[2].as()); } RGL_API rgl_status_t rgl_entity_set_id(rgl_entity_t entity, int32_t id) @@ -382,6 +425,27 @@ void TapeCore::tape_entity_set_laser_retro(const YAML::Node& yamlNode, PlaybackS yamlNode[1].as::type>()); } +RGL_API rgl_status_t rgl_entity_apply_external_animation(rgl_entity_t entity, const rgl_vec3f* vertices, int32_t vertex_count) +{ + auto status = rglSafeCall([&]() { + RGL_API_LOG("rgl_entity_apply_external_animation(entity={}, vertices={})", (void*) entity, + repr(vertices, vertex_count)); + CHECK_ARG(entity != nullptr); + CHECK_ARG(vertices != nullptr); + CHECK_ARG(vertex_count > 0); + GraphRunCtx::synchronizeAll(); // Prevent races with graph threads + Entity::validatePtr(entity)->applyExternalAnimation(reinterpret_cast(vertices), vertex_count); + }); + TAPE_HOOK(entity, TAPE_ARRAY(vertices, vertex_count), vertex_count); + return status; +} + +void TapeCore::tape_entity_apply_external_animation(const YAML::Node& yamlNode, PlaybackState& state) +{ + rgl_entity_apply_external_animation(state.entities.at(yamlNode[0].as()), + state.getPtr(yamlNode[1]), yamlNode[2].as()); +} + rgl_status_t rgl_entity_is_alive(rgl_entity_t entity, bool* out_alive) { auto status = rglSafeCall([&]() { diff --git a/src/gpu/helpersKernels.cu b/src/gpu/helpersKernels.cu index 5e4b0cf9..f2d9432c 100644 --- a/src/gpu/helpersKernels.cu +++ b/src/gpu/helpersKernels.cu @@ -31,19 +31,3 @@ void gpuSetupRandomNumberGenerator(cudaStream_t stream, size_t elementsCount, un { run(kSetupRandomNumberGenerator, stream, elementsCount, seed, outPHILOXStates); } -// Updates vertices and calculates their displacement. -// Input: newVertices and oldVertices -// Output: verticesDisplacement and newVertices -__global__ void kUpdateVertices(size_t vertexCount, Vec3f* newVerticesToDisplacement, Vec3f* oldToNewVertices) -{ - LIMIT(vertexCount); - // See Mesh::updateVertices to understand the logic here. - Vec3f newVertex = newVerticesToDisplacement[tid]; - newVerticesToDisplacement[tid] -= oldToNewVertices[tid]; - oldToNewVertices[tid] = newVertex; -} - -void gpuUpdateVertices(cudaStream_t stream, size_t vertexCount, Vec3f* newVerticesToDisplacement, Vec3f* oldToNewVertices) -{ - run(kUpdateVertices, stream, vertexCount, newVerticesToDisplacement, oldToNewVertices); -} diff --git a/src/gpu/helpersKernels.hpp b/src/gpu/helpersKernels.hpp index 85b38408..2396dc86 100644 --- a/src/gpu/helpersKernels.hpp +++ b/src/gpu/helpersKernels.hpp @@ -20,5 +20,3 @@ void gpuSetupRandomNumberGenerator(cudaStream_t stream, size_t elementsCount, unsigned int seed, curandStatePhilox4_32_10_t* outPHILOXStates); - -void gpuUpdateVertices(cudaStream_t stream, size_t vertexCount, Vec3f* newVerticesToDisplacement, Vec3f* oldToNewVertices); diff --git a/src/gpu/optixPrograms.cu b/src/gpu/optixPrograms.cu index de6a90fa..5063bc2b 100644 --- a/src/gpu/optixPrograms.cu +++ b/src/gpu/optixPrograms.cu @@ -239,7 +239,6 @@ extern "C" __global__ void __closesthit__() if (wasSkinned) { Mat3x4f objectToWorld; optixGetObjectToWorldTransformMatrix(reinterpret_cast(objectToWorld.rc)); - // TODO(msz-rai): To verify if rotation is needed (in some tests it produces more realistic results) const Vec3f& vA = objectToWorld.rotation() * entityData.vertexDisplacementSincePrevFrame[triangleIndices.x()]; const Vec3f& vB = objectToWorld.rotation() * entityData.vertexDisplacementSincePrevFrame[triangleIndices.y()]; const Vec3f& vC = objectToWorld.rotation() * entityData.vertexDisplacementSincePrevFrame[triangleIndices.z()]; diff --git a/src/gpu/sceneKernels.cu b/src/gpu/sceneKernels.cu new file mode 100644 index 00000000..51bd74ad --- /dev/null +++ b/src/gpu/sceneKernels.cu @@ -0,0 +1,63 @@ +// Copyright 2024 Robotec.AI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +__global__ void kPerformSkeletonAnimation(size_t vertexCount, const Vec3f* restposeVertices, const BoneWeights* boneWeights, + const Mat3x4f* animationMatrices, Vec3f* skinnedVertices) +{ + LIMIT(vertexCount); + + skinnedVertices[tid] = (animationMatrices[boneWeights[tid].boneIndexes.x] * restposeVertices[tid]) * + boneWeights[tid].weights.x; + skinnedVertices[tid] += (animationMatrices[boneWeights[tid].boneIndexes.y] * restposeVertices[tid]) * + boneWeights[tid].weights.y; + skinnedVertices[tid] += (animationMatrices[boneWeights[tid].boneIndexes.z] * restposeVertices[tid]) * + boneWeights[tid].weights.z; + skinnedVertices[tid] += (animationMatrices[boneWeights[tid].boneIndexes.w] * restposeVertices[tid]) * + boneWeights[tid].weights.w; +} + +__global__ void kCalculateAnimationMatrices(size_t boneCount, const Mat3x4f* restposes, Mat3x4f* animationMatrices) +{ + LIMIT(boneCount); + animationMatrices[tid] = animationMatrices[tid] * restposes[tid]; +} + +// Updates vertices and calculates their displacement. +// Input: newVertices and oldVertices +// Output: verticesDisplacement and newVertices +__global__ void kUpdateVertices(size_t vertexCount, Vec3f* newVerticesToDisplacement, Vec3f* oldToNewVertices) +{ + LIMIT(vertexCount); + // See ExternalAnimator::animate or SkeletonAnimator::animate to understand the logic here. + Vec3f newVertex = newVerticesToDisplacement[tid]; + newVerticesToDisplacement[tid] -= oldToNewVertices[tid]; + oldToNewVertices[tid] = newVertex; +} + +void gpuPerformSkeletonAnimation(cudaStream_t stream, size_t vertexCount, size_t boneCount, const Vec3f* restposeVertices, + const BoneWeights* boneWeights, const Mat3x4f* restposes, Mat3x4f* animationMatrices, + Vec3f* skinnedVertices) +{ + run(kCalculateAnimationMatrices, stream, boneCount, restposes, animationMatrices); + run(kPerformSkeletonAnimation, stream, vertexCount, restposeVertices, boneWeights, animationMatrices, skinnedVertices); +} + +void gpuUpdateVerticesWithDisplacement(cudaStream_t stream, size_t vertexCount, Vec3f* newVerticesToDisplacement, + Vec3f* oldToNewVertices) +{ + run(kUpdateVertices, stream, vertexCount, newVerticesToDisplacement, oldToNewVertices); +} diff --git a/src/gpu/sceneKernels.hpp b/src/gpu/sceneKernels.hpp new file mode 100644 index 00000000..26cb15a1 --- /dev/null +++ b/src/gpu/sceneKernels.hpp @@ -0,0 +1,28 @@ +// Copyright 2024 Robotec.AI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include +#include +#include + +void gpuPerformSkeletonAnimation(cudaStream_t stream, size_t vertexCount, size_t boneCount, const Vec3f* restposeVertices, + const BoneWeights* boneWeights, const Mat3x4f* restposes, Mat3x4f* animationMatrices, + Vec3f* skinnedVertices); + +void gpuUpdateVerticesWithDisplacement(cudaStream_t stream, size_t vertexCount, Vec3f* newVerticesToDisplacement, + Vec3f* oldToNewVertices); diff --git a/src/repr.hpp b/src/repr.hpp index abfe21bc..83ffcca0 100644 --- a/src/repr.hpp +++ b/src/repr.hpp @@ -157,3 +157,20 @@ struct fmt::formatter v.radial_speed_separation_threshold, v.azimuth_separation_threshold); } }; + +template<> +struct fmt::formatter +{ + template + constexpr auto parse(ParseContext& ctx) + { + return ctx.begin(); + } + + template + auto format(const rgl_bone_weights_t& v, FormatContext& ctx) + { + return fmt::format_to(ctx.out(), "(b=({},{},{},{}), w=({},{},{},{})", v.bone_indexes[0], v.bone_indexes[1], + v.bone_indexes[2], v.bone_indexes[3], v.weights[0], v.weights[1], v.weights[2], v.weights[3]); + } +}; diff --git a/src/scene/ASBuildScratchpad.hpp b/src/scene/ASBuildScratchpad.hpp index 245593dc..ae7928e5 100644 --- a/src/scene/ASBuildScratchpad.hpp +++ b/src/scene/ASBuildScratchpad.hpp @@ -26,7 +26,7 @@ */ struct ASBuildScratchpad { - friend struct Mesh; + friend struct GASBuilder; friend struct Scene; void resizeToFit(OptixBuildInput input, OptixAccelBuildOptions options); diff --git a/src/scene/BoneWeights.hpp b/src/scene/BoneWeights.hpp new file mode 100644 index 00000000..c210ebfb --- /dev/null +++ b/src/scene/BoneWeights.hpp @@ -0,0 +1,29 @@ +// Copyright 2024 Robotec.AI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include + +/* + * Same as `rgl_bone_weights_t` but uses vector types to speed up CUDA operations. + */ +struct BoneWeights +{ + float4 weights; + int4 boneIndexes; +}; +static_assert(sizeof(BoneWeights) == sizeof(rgl_bone_weights_t)); diff --git a/src/scene/Entity.cpp b/src/scene/Entity.cpp index 10744a88..4a62f2a7 100644 --- a/src/scene/Entity.cpp +++ b/src/scene/Entity.cpp @@ -16,6 +16,13 @@ API_OBJECT_INSTANCE(Entity); +// Helper class to combine multiple callables into one overload set for std::visit. +template +struct Visitor : Callable... +{ + using Callable::operator()...; +}; + std::shared_ptr Entity::create(std::shared_ptr mesh) { auto entity = APIObject::create(mesh); @@ -77,3 +84,54 @@ std::optional Entity::getPreviousFrameLocalToWorldTransform() const return formerTransformInfo.matrix; } + +void Entity::applyExternalAnimation(const Vec3f* vertices, std::size_t vertexCount) +{ + if (!std::holds_alternative(animator)) { + animator = ExternalAnimator(mesh->dVertices); + } + auto externalAnimator = std::get(animator); + externalAnimator.animate(vertices, vertexCount); + updateAnimationTime(); +} + +void Entity::setPoseAndAnimate(const Mat3x4f* pose, std::size_t bonesCount) +{ + if (!std::holds_alternative(animator)) { + animator = SkeletonAnimator(mesh); + } + auto skeletonAnimator = std::get(animator); + skeletonAnimator.animate(pose, bonesCount); + updateAnimationTime(); +} + +void Entity::updateAnimationTime() +{ + formerAnimationTime = currentAnimationTime; + currentAnimationTime = Scene::instance().getTime(); + Scene::instance().requestASRebuild(); // Vertices themselves + Scene::instance().requestSBTRebuild(); // Vertices displacement +} + +const Vec3f* Entity::getVertexDisplacementSincePrevFrame() +{ + if (!formerAnimationTime.has_value() || formerAnimationTime != Scene::instance().getPrevTime()) { + return nullptr; + } + + using ReturnT = const Vec3f*; + return std::visit( + Visitor ReturnT { return nullptr; }), + decltype([](const ExternalAnimator& a) -> ReturnT { return a.dVertexAnimationDisplacement->getReadPtr(); }), + decltype([](const SkeletonAnimator& a) -> ReturnT { return a.dVertexAnimationDisplacement->getReadPtr(); })>{}, + animator); +} + +std::optional::Ptr> Entity::getAnimatedVertices() +{ + using ReturnT = std::optional::Ptr>; + return std::visit(Visitor ReturnT { return std::nullopt; }), + decltype([](const ExternalAnimator& a) -> ReturnT { return a.dAnimatedVertices; }), + decltype([](const SkeletonAnimator& a) -> ReturnT { return a.dAnimatedVertices; })>{}, + animator); +} diff --git a/src/scene/Entity.hpp b/src/scene/Entity.hpp index f610d56d..6d2de035 100644 --- a/src/scene/Entity.hpp +++ b/src/scene/Entity.hpp @@ -16,16 +16,20 @@ #include +#include + #include #include #include #include +#include +#include #include /** * Entity represents an object on a scene, consisting of: * - reference to mesh - * - pose (local-to-world transform) + * - transform (local-to-world) * - (optional) reference to intensity texture * - (optional) id (for instance segmentation) * - etc. @@ -69,6 +73,36 @@ struct Entity : APIObject */ std::optional getPreviousFrameLocalToWorldTransform() const; + /** + * Updates animated vertices with the provided ones. Vertex count must be equal to original vertex count of the mesh. + */ + void applyExternalAnimation(const Vec3f* vertices, std::size_t vertexCount); + + /** + * Performs skeleton animation based on provided pose. Number of bones must be equal to restposes count defined in the mesh. + */ + void setPoseAndAnimate(const Mat3x4f* pose, std::size_t bonesCount); + + /** + * Returns whether the given entity has been animated at least once. + * @return true if entity is animated. + */ + bool isAnimated() const { return !std::holds_alternative(animator); } + + /** + * Returns an array of deformed vertices due to animation. + * If vertices were not animated, returns nullopt. + * @return Device array of animated vertices if available. + */ + std::optional::Ptr> getAnimatedVertices(); + + /** + * Returns an array describing displacement of each vertex between current and previous state, due to animation. + * If vertices were not animated in the previous frame, returns NULL (equivalent to an array of zero vectors). + * @return Pointer to GPU-accessible array, same size as vertexCount. May be NULL. + */ + const Vec3f* getVertexDisplacementSincePrevFrame(); + private: /** * Creates Entity with given mesh and identity transform. @@ -79,6 +113,11 @@ struct Entity : APIObject */ Entity(std::shared_ptr mesh); + /** + * Updates animation time to current scene time and requests AS & SBT to rebuild. + */ + void updateAnimationTime(); + private: struct TransformWithTime { @@ -93,4 +132,8 @@ struct Entity : APIObject std::shared_ptr mesh{}; std::shared_ptr intensityTexture{}; + + std::variant animator = std::monostate(); + std::optional