Improve viewer camera controls and playback seeking
This commit is contained in:
@@ -13,6 +13,10 @@ constexpr float PitchLimit = 1.45f;
|
|||||||
constexpr float MinDistance = 0.05f;
|
constexpr float MinDistance = 0.05f;
|
||||||
constexpr float OrbitSensitivity = 0.008f;
|
constexpr float OrbitSensitivity = 0.008f;
|
||||||
constexpr float ZoomSensitivity = 0.15f;
|
constexpr float ZoomSensitivity = 0.15f;
|
||||||
|
constexpr Deg VerticalFieldOfView = 35.0_degf;
|
||||||
|
constexpr float FitPadding = 1.15f;
|
||||||
|
constexpr float DefaultYaw = 0.78539816f;
|
||||||
|
constexpr float DefaultPitch = 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OrbitCameraController::setViewport(const Vector2i& windowSize, const Vector2i& framebufferSize) {
|
void OrbitCameraController::setViewport(const Vector2i& windowSize, const Vector2i& framebufferSize) {
|
||||||
@@ -23,9 +27,9 @@ void OrbitCameraController::setViewport(const Vector2i& windowSize, const Vector
|
|||||||
void OrbitCameraController::setSceneBounds(const Vector3& center, float radius) {
|
void OrbitCameraController::setSceneBounds(const Vector3& center, float radius) {
|
||||||
_pivot = center;
|
_pivot = center;
|
||||||
_sceneRadius = Math::max(radius, 0.1f);
|
_sceneRadius = Math::max(radius, 0.1f);
|
||||||
_distance = Math::max(_sceneRadius*2.5f, 0.5f);
|
_distance = Math::max((_sceneRadius/Math::sin(VerticalFieldOfView*0.5f))*FitPadding, 0.5f);
|
||||||
_yaw = 0.0f;
|
_yaw = DefaultYaw;
|
||||||
_pitch = 0.35f;
|
_pitch = DefaultPitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OrbitCameraController::reset() {
|
void OrbitCameraController::reset() {
|
||||||
@@ -34,7 +38,8 @@ void OrbitCameraController::reset() {
|
|||||||
|
|
||||||
void OrbitCameraController::orbit(const Vector2& deltaPixels) {
|
void OrbitCameraController::orbit(const Vector2& deltaPixels) {
|
||||||
_yaw -= deltaPixels.x()*OrbitSensitivity;
|
_yaw -= deltaPixels.x()*OrbitSensitivity;
|
||||||
_pitch = Math::clamp(_pitch - deltaPixels.y()*OrbitSensitivity, -PitchLimit, PitchLimit);
|
const float verticalDirection = _invertY ? 1.0f : -1.0f;
|
||||||
|
_pitch = Math::clamp(_pitch + verticalDirection*deltaPixels.y()*OrbitSensitivity, -PitchLimit, PitchLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OrbitCameraController::pan(const Vector2& deltaPixels) {
|
void OrbitCameraController::pan(const Vector2& deltaPixels) {
|
||||||
@@ -50,7 +55,7 @@ void OrbitCameraController::zoom(float wheelDelta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Matrix4 OrbitCameraController::cameraTransform() const {
|
Matrix4 OrbitCameraController::cameraTransform() const {
|
||||||
return Matrix4::lookAt(position(), _pivot, Vector3::yAxis()).invertedRigid();
|
return Matrix4::lookAt(position(), _pivot, Vector3::yAxis());
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 OrbitCameraController::position() const {
|
Vector3 OrbitCameraController::position() const {
|
||||||
@@ -64,7 +69,7 @@ Vector3 OrbitCameraController::position() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float OrbitCameraController::panScale() const {
|
float OrbitCameraController::panScale() const {
|
||||||
return 2.0f*_distance*Math::tan(35.0_degf/2.0f)/Float(_windowSize.y());
|
return 2.0f*_distance*Math::tan(VerticalFieldOfView*0.5f)/Float(_windowSize.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 OrbitCameraController::forward() const {
|
Vector3 OrbitCameraController::forward() const {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class OrbitCameraController {
|
|||||||
public:
|
public:
|
||||||
void setViewport(const Magnum::Vector2i& windowSize, const Magnum::Vector2i& framebufferSize);
|
void setViewport(const Magnum::Vector2i& windowSize, const Magnum::Vector2i& framebufferSize);
|
||||||
void setSceneBounds(const Magnum::Vector3& center, float radius);
|
void setSceneBounds(const Magnum::Vector3& center, float radius);
|
||||||
|
void setInvertY(bool invertY) { _invertY = invertY; }
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
void orbit(const Magnum::Vector2& deltaPixels);
|
void orbit(const Magnum::Vector2& deltaPixels);
|
||||||
@@ -33,7 +34,7 @@ private:
|
|||||||
float _yaw = 0.0f;
|
float _yaw = 0.0f;
|
||||||
float _pitch = 0.35f;
|
float _pitch = 0.35f;
|
||||||
float _sceneRadius = 1.0f;
|
float _sceneRadius = 1.0f;
|
||||||
|
bool _invertY = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+206
-20
@@ -6,10 +6,15 @@
|
|||||||
|
|
||||||
#include <Magnum/GL/DefaultFramebuffer.h>
|
#include <Magnum/GL/DefaultFramebuffer.h>
|
||||||
#include <Magnum/GL/Renderer.h>
|
#include <Magnum/GL/Renderer.h>
|
||||||
|
#include <Magnum/MeshTools/Compile.h>
|
||||||
|
#include <Magnum/Primitives/Cube.h>
|
||||||
|
#include <Magnum/Primitives/Grid.h>
|
||||||
|
#include <Magnum/Trade/MeshData.h>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -51,14 +56,16 @@ std::shared_ptr<CpuMesh> buildCpuMesh(const SimTK::PolygonalMesh& mesh) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasBounds = false;
|
||||||
Range3D bounds;
|
Range3D bounds;
|
||||||
bounds.min() = Vector3{std::numeric_limits<Float>::max()};
|
|
||||||
bounds.max() = Vector3{-std::numeric_limits<Float>::max()};
|
|
||||||
cpu->vertices.reserve(positions.size());
|
cpu->vertices.reserve(positions.size());
|
||||||
for(std::size_t i = 0; i != positions.size(); ++i) {
|
for(std::size_t i = 0; i != positions.size(); ++i) {
|
||||||
const Vector3 normal = normals[i].isZero() ? Vector3::yAxis() : normals[i].normalized();
|
const Vector3 normal = normals[i].isZero() ? Vector3::yAxis() : normals[i].normalized();
|
||||||
cpu->vertices.push_back({positions[i], normal});
|
cpu->vertices.push_back({positions[i], normal});
|
||||||
bounds = Math::join(bounds, Range3D::fromSize(positions[i], {}));
|
const Range3D point = Range3D::fromSize(positions[i], {});
|
||||||
|
if(hasBounds) bounds = Math::join(bounds, point);
|
||||||
|
else bounds = point;
|
||||||
|
hasBounds = true;
|
||||||
}
|
}
|
||||||
cpu->bounds = bounds;
|
cpu->bounds = bounds;
|
||||||
return cpu;
|
return cpu;
|
||||||
@@ -68,6 +75,48 @@ Matrix4 makeFrameMatrix(const Vector3& translation, const Quaternion& rotation)
|
|||||||
return Matrix4::from(rotation.toMatrix(), translation);
|
return Matrix4::from(rotation.toMatrix(), translation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CpuMesh> buildBoxMesh() {
|
||||||
|
auto cpu = std::make_shared<CpuMesh>();
|
||||||
|
|
||||||
|
const struct Face {
|
||||||
|
Vector3 normal;
|
||||||
|
Vector3 vertices[4];
|
||||||
|
} faces[] = {
|
||||||
|
{ Vector3::xAxis(), {
|
||||||
|
{ 1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f, -1.0f }, { 1.0f, 1.0f, 1.0f }, { 1.0f, -1.0f, 1.0f }
|
||||||
|
} },
|
||||||
|
{ -Vector3::xAxis(), {
|
||||||
|
{ -1.0f, -1.0f, 1.0f }, { -1.0f, 1.0f, 1.0f }, { -1.0f, 1.0f, -1.0f }, { -1.0f, -1.0f, -1.0f }
|
||||||
|
} },
|
||||||
|
{ Vector3::yAxis(), {
|
||||||
|
{ -1.0f, 1.0f, -1.0f }, { -1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, -1.0f }
|
||||||
|
} },
|
||||||
|
{ -Vector3::yAxis(), {
|
||||||
|
{ -1.0f, -1.0f, 1.0f }, { -1.0f, -1.0f, -1.0f }, { 1.0f, -1.0f, -1.0f }, { 1.0f, -1.0f, 1.0f }
|
||||||
|
} },
|
||||||
|
{ Vector3::zAxis(), {
|
||||||
|
{ -1.0f, -1.0f, 1.0f }, { 1.0f, -1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f }, { -1.0f, 1.0f, 1.0f }
|
||||||
|
} },
|
||||||
|
{ -Vector3::zAxis(), {
|
||||||
|
{ 1.0f, -1.0f, -1.0f }, { -1.0f, -1.0f, -1.0f }, { -1.0f, 1.0f, -1.0f }, { 1.0f, 1.0f, -1.0f }
|
||||||
|
} },
|
||||||
|
};
|
||||||
|
|
||||||
|
cpu->vertices.reserve(24);
|
||||||
|
cpu->indices.reserve(36);
|
||||||
|
for(const Face& face: faces) {
|
||||||
|
const UnsignedInt base = UnsignedInt(cpu->vertices.size());
|
||||||
|
for(const Vector3& position: face.vertices) cpu->vertices.push_back({position, face.normal});
|
||||||
|
cpu->indices.insert(cpu->indices.end(), {
|
||||||
|
base + 0, base + 1, base + 2,
|
||||||
|
base + 0, base + 2, base + 3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu->bounds = Range3D{{-1.0f, -1.0f, -1.0f}, {1.0f, 1.0f, 1.0f}};
|
||||||
|
return cpu;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewerApp::MeshDrawable::MeshDrawable(Object3D& object,
|
ViewerApp::MeshDrawable::MeshDrawable(Object3D& object,
|
||||||
@@ -91,11 +140,32 @@ void ViewerApp::MeshDrawable::draw(const Matrix4& transformation, Camera3D& came
|
|||||||
.draw(_mesh.mesh);
|
.draw(_mesh.mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ViewerApp::FlatDrawable::FlatDrawable(Object3D& object,
|
||||||
|
Shaders::FlatGL3D& shader,
|
||||||
|
GL::Mesh& mesh,
|
||||||
|
Color4 color,
|
||||||
|
DrawableGroup3D& drawables):
|
||||||
|
Drawable3D{object, &drawables},
|
||||||
|
_shader{shader},
|
||||||
|
_mesh{mesh},
|
||||||
|
_color{color} {}
|
||||||
|
|
||||||
|
void ViewerApp::FlatDrawable::draw(const Matrix4& transformation, Camera3D& camera) {
|
||||||
|
_shader
|
||||||
|
.setColor(_color)
|
||||||
|
.setTransformationProjectionMatrix(camera.projectionMatrix()*transformation)
|
||||||
|
.draw(_mesh);
|
||||||
|
}
|
||||||
|
|
||||||
ViewerApp::ViewerApp(const Arguments& arguments, CliOptions options):
|
ViewerApp::ViewerApp(const Arguments& arguments, CliOptions options):
|
||||||
Platform::Application{arguments, Configuration{}.setTitle("osim-magnum-viewer").setWindowFlags(Configuration::WindowFlag::Resizable)},
|
Platform::Application{arguments, Configuration{}
|
||||||
|
.setTitle("osim-magnum-viewer")
|
||||||
|
.setSize({800, 600}, Configuration::DpiScalingPolicy::Physical)
|
||||||
|
.setWindowFlags(Configuration::WindowFlag::Resizable)},
|
||||||
_options{std::move(options)},
|
_options{std::move(options)},
|
||||||
_sceneData{loadScene(_options.modelPath, _options.motionPath, _options.geometryDirs)},
|
_sceneData{loadScene(_options.modelPath, _options.motionPath, _options.geometryDirs)},
|
||||||
_shader{Shaders::PhongGL{}},
|
_shader{Shaders::PhongGL{}},
|
||||||
|
_flatShader{Shaders::FlatGL3D{}},
|
||||||
_isPlaying{!_options.startPaused},
|
_isPlaying{!_options.startPaused},
|
||||||
_playbackSpeed{_options.initialSpeed} {
|
_playbackSpeed{_options.initialSpeed} {
|
||||||
|
|
||||||
@@ -112,14 +182,11 @@ ViewerApp::ViewerApp(const Arguments& arguments, CliOptions options):
|
|||||||
.setViewport(GL::defaultFramebuffer.viewport().size());
|
.setViewport(GL::defaultFramebuffer.viewport().size());
|
||||||
|
|
||||||
_orbit.setViewport(windowSize(), framebufferSize());
|
_orbit.setViewport(windowSize(), framebufferSize());
|
||||||
const Range3D bounds = _sceneData.initialBounds;
|
_orbit.setInvertY(!_options.reverseMouseY);
|
||||||
const Vector3 center = (bounds.min() + bounds.max())*0.5f;
|
|
||||||
const float radius = Math::max((bounds.max() - bounds.min()).max(), 0.5f);
|
|
||||||
_orbit.setSceneBounds(center, radius);
|
|
||||||
_cameraObject.setTransformation(_orbit.cameraTransform());
|
|
||||||
if(!_sceneData.times.empty()) _currentTime = _sceneData.times.front();
|
if(!_sceneData.times.empty()) _currentTime = _sceneData.times.front();
|
||||||
|
|
||||||
createSceneObjects();
|
createSceneObjects();
|
||||||
|
resetDefaultCamera();
|
||||||
updateSceneAtCurrentTime();
|
updateSceneAtCurrentTime();
|
||||||
|
|
||||||
_lastTick = std::chrono::steady_clock::now();
|
_lastTick = std::chrono::steady_clock::now();
|
||||||
@@ -145,6 +212,11 @@ void ViewerApp::createSceneObjects() {
|
|||||||
auto markerCpu = buildCpuMesh(SimTK::PolygonalMesh::createSphereMesh(1.0, 1));
|
auto markerCpu = buildCpuMesh(SimTK::PolygonalMesh::createSphereMesh(1.0, 1));
|
||||||
_markerMesh = uploadMesh(*markerCpu);
|
_markerMesh = uploadMesh(*markerCpu);
|
||||||
_gpuMeshes.push_back(_markerMesh);
|
_gpuMeshes.push_back(_markerMesh);
|
||||||
|
auto boxCpu = buildBoxMesh();
|
||||||
|
_boxMesh = uploadMesh(*boxCpu);
|
||||||
|
_gpuMeshes.push_back(_boxMesh);
|
||||||
|
_groundGridMesh = MeshTools::compile(Primitives::grid3DWireframe({20, 20}));
|
||||||
|
_originWireBoxMesh = MeshTools::compile(Primitives::cubeWireframe());
|
||||||
|
|
||||||
for(const FrameTrack& frameTrack: _sceneData.frames) {
|
for(const FrameTrack& frameTrack: _sceneData.frames) {
|
||||||
auto frameObject = std::make_unique<Object3D>(&_scene);
|
auto frameObject = std::make_unique<Object3D>(&_scene);
|
||||||
@@ -183,6 +255,52 @@ void ViewerApp::createSceneObjects() {
|
|||||||
_objectStorage.push_back(std::move(markerObject));
|
_objectStorage.push_back(std::move(markerObject));
|
||||||
_drawableStorage.push_back(std::move(drawable));
|
_drawableStorage.push_back(std::move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createReferenceObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewerApp::createReferenceObjects() {
|
||||||
|
const float radius = sceneRadius();
|
||||||
|
|
||||||
|
const float axisLength = Math::max(radius*0.6f, 0.35f);
|
||||||
|
const float axisHalfLength = axisLength*0.5f;
|
||||||
|
const float axisThickness = Math::max(radius*0.015f, 0.008f);
|
||||||
|
const float groundScale = Math::max(radius*2.5f, 1.5f);
|
||||||
|
const float originMarkerScale = Math::max(radius*0.04f, 0.03f);
|
||||||
|
|
||||||
|
const struct AxisSpec {
|
||||||
|
Vector3 translation;
|
||||||
|
Vector3 scale;
|
||||||
|
Color4 color;
|
||||||
|
} axes[] = {
|
||||||
|
{ { axisHalfLength, 0.0f, 0.0f }, { axisHalfLength, axisThickness, axisThickness }, { 0.92f, 0.28f, 0.28f, 1.0f } },
|
||||||
|
{ { 0.0f, axisHalfLength, 0.0f }, { axisThickness, axisHalfLength, axisThickness }, { 0.3f, 0.84f, 0.38f, 1.0f } },
|
||||||
|
{ { 0.0f, 0.0f, axisHalfLength }, { axisThickness, axisThickness, axisHalfLength }, { 0.36f, 0.58f, 0.92f, 1.0f } },
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const AxisSpec& axis: axes) {
|
||||||
|
auto object = std::make_unique<Object3D>(&_scene);
|
||||||
|
object->setTransformation(Matrix4::translation(axis.translation)*Matrix4::scaling(axis.scale));
|
||||||
|
auto drawable = std::make_unique<MeshDrawable>(*object, _shader, *_boxMesh, axis.color, _drawables);
|
||||||
|
_drawableStorage.push_back(std::move(drawable));
|
||||||
|
_objectStorage.push_back(std::move(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto originObject = std::make_unique<Object3D>(&_scene);
|
||||||
|
originObject->setTransformation(Matrix4::scaling(Vector3{originMarkerScale}));
|
||||||
|
auto originDrawable = std::make_unique<FlatDrawable>(
|
||||||
|
*originObject, _flatShader, _originWireBoxMesh, Color4{0.82f, 0.84f, 0.88f, 1.0f}, _drawables);
|
||||||
|
_drawableStorage.push_back(std::move(originDrawable));
|
||||||
|
_objectStorage.push_back(std::move(originObject));
|
||||||
|
|
||||||
|
auto groundObject = std::make_unique<Object3D>(&_scene);
|
||||||
|
groundObject->setTransformation(
|
||||||
|
Matrix4::rotationX(90.0_degf)*
|
||||||
|
Matrix4::scaling(Vector3{groundScale}));
|
||||||
|
auto groundDrawable = std::make_unique<FlatDrawable>(
|
||||||
|
*groundObject, _flatShader, _groundGridMesh, Color4{0.42f, 0.45f, 0.48f, 1.0f}, _drawables);
|
||||||
|
_drawableStorage.push_back(std::move(groundDrawable));
|
||||||
|
_objectStorage.push_back(std::move(groundObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerApp::updatePlayback() {
|
void ViewerApp::updatePlayback() {
|
||||||
@@ -193,12 +311,18 @@ void ViewerApp::updatePlayback() {
|
|||||||
if(!_isPlaying || _sceneData.times.empty()) return;
|
if(!_isPlaying || _sceneData.times.empty()) return;
|
||||||
|
|
||||||
_currentTime += delta*_playbackSpeed;
|
_currentTime += delta*_playbackSpeed;
|
||||||
|
const float startTime = _sceneData.times.front();
|
||||||
const float endTime = _sceneData.times.back();
|
const float endTime = _sceneData.times.back();
|
||||||
|
const float duration = endTime - startTime;
|
||||||
if(_currentTime >= endTime) {
|
if(_currentTime >= endTime) {
|
||||||
|
if(_loopPlayback && duration > 0.0f) {
|
||||||
|
_currentTime = startTime + std::fmod(_currentTime - startTime, duration);
|
||||||
|
} else {
|
||||||
_currentTime = endTime;
|
_currentTime = endTime;
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t ViewerApp::sampleIndexForTime(const float time) const {
|
std::size_t ViewerApp::sampleIndexForTime(const float time) const {
|
||||||
if(_sceneData.times.size() < 2) return 0;
|
if(_sceneData.times.size() < 2) return 0;
|
||||||
@@ -236,21 +360,84 @@ void ViewerApp::drawUi() {
|
|||||||
ImGui::TextUnformatted(_sceneData.modelName.c_str());
|
ImGui::TextUnformatted(_sceneData.modelName.c_str());
|
||||||
ImGui::TextUnformatted(_sceneData.motionName.c_str());
|
ImGui::TextUnformatted(_sceneData.motionName.c_str());
|
||||||
if(ImGui::Button(_isPlaying ? "Pause" : "Play")) {
|
if(ImGui::Button(_isPlaying ? "Pause" : "Play")) {
|
||||||
if(!_isPlaying && !_sceneData.times.empty() && _currentTime >= _sceneData.times.back()) _currentTime = 0.0f;
|
if(!_isPlaying && !_sceneData.times.empty() && _currentTime >= _sceneData.times.back()) rewindPlayback();
|
||||||
_isPlaying = !_isPlaying;
|
_isPlaying = !_isPlaying;
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if(ImGui::Button("Rewind")) {
|
if(ImGui::Button("Rewind")) {
|
||||||
_currentTime = 0.0f;
|
rewindPlayback();
|
||||||
_isPlaying = false;
|
|
||||||
}
|
}
|
||||||
ImGui::SliderFloat("Speed", &_playbackSpeed, 0.1f, 4.0f, "%.2fx");
|
ImGui::SliderFloat("Speed", &_playbackSpeed, 0.1f, 4.0f, "%.2fx");
|
||||||
|
ImGui::Checkbox("Loop", &_loopPlayback);
|
||||||
|
if(ImGui::Checkbox("Reverse mouse Y", &_options.reverseMouseY)) {
|
||||||
|
_orbit.setInvertY(!_options.reverseMouseY);
|
||||||
|
}
|
||||||
|
if(!_sceneData.times.empty()) {
|
||||||
|
float seekTime = _currentTime;
|
||||||
|
if(ImGui::SliderFloat("Seek", &seekTime, _sceneData.times.front(), _sceneData.times.back(), "%.3f",
|
||||||
|
ImGuiSliderFlags_AlwaysClamp)) {
|
||||||
|
seekPlayback(seekTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::Text("Time %.3f / %.3f", _currentTime, _sceneData.times.empty() ? 0.0f : _sceneData.times.back());
|
ImGui::Text("Time %.3f / %.3f", _currentTime, _sceneData.times.empty() ? 0.0f : _sceneData.times.back());
|
||||||
ImGui::Text("Frames %zu Markers %zu", _sceneData.frames.size(), _sceneData.markers.size());
|
ImGui::Text("Frames %zu Markers %zu", _sceneData.frames.size(), _sceneData.markers.size());
|
||||||
ImGui::TextUnformatted("LMB orbit RMB/MMB pan wheel zoom F frame");
|
ImGui::TextUnformatted("LMB orbit Shift+LMB/RMB/MMB pan wheel zoom F frame");
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float ViewerApp::sceneRadiusFromOrigin() const {
|
||||||
|
const Range3D bounds = _sceneData.initialBounds;
|
||||||
|
float radius = 0.5f;
|
||||||
|
for(const Vector3& corner: {
|
||||||
|
Vector3{bounds.min().x(), bounds.min().y(), bounds.min().z()},
|
||||||
|
Vector3{bounds.min().x(), bounds.min().y(), bounds.max().z()},
|
||||||
|
Vector3{bounds.min().x(), bounds.max().y(), bounds.min().z()},
|
||||||
|
Vector3{bounds.min().x(), bounds.max().y(), bounds.max().z()},
|
||||||
|
Vector3{bounds.max().x(), bounds.min().y(), bounds.min().z()},
|
||||||
|
Vector3{bounds.max().x(), bounds.min().y(), bounds.max().z()},
|
||||||
|
Vector3{bounds.max().x(), bounds.max().y(), bounds.min().z()},
|
||||||
|
Vector3{bounds.max().x(), bounds.max().y(), bounds.max().z()},
|
||||||
|
}) {
|
||||||
|
radius = Math::max(radius, corner.length());
|
||||||
|
}
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 ViewerApp::sceneCenter() const {
|
||||||
|
const Range3D bounds = _sceneData.initialBounds;
|
||||||
|
return (bounds.min() + bounds.max())*0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ViewerApp::sceneRadius() const {
|
||||||
|
const Range3D bounds = _sceneData.initialBounds;
|
||||||
|
return Math::max((bounds.max() - bounds.min()).length()*0.5f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewerApp::seekPlayback(float time) {
|
||||||
|
if(_sceneData.times.empty()) {
|
||||||
|
_currentTime = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentTime = Math::clamp(time, _sceneData.times.front(), _sceneData.times.back());
|
||||||
|
updateSceneAtCurrentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewerApp::resetDefaultCamera() {
|
||||||
|
_orbit.setSceneBounds({}, sceneRadiusFromOrigin());
|
||||||
|
_cameraObject.setTransformation(_orbit.cameraTransform());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewerApp::resetCameraToScene() {
|
||||||
|
_orbit.setSceneBounds(sceneCenter(), sceneRadius());
|
||||||
|
_cameraObject.setTransformation(_orbit.cameraTransform());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewerApp::rewindPlayback() {
|
||||||
|
seekPlayback(_sceneData.times.empty() ? 0.0f : _sceneData.times.front());
|
||||||
|
_isPlaying = false;
|
||||||
|
}
|
||||||
|
|
||||||
void ViewerApp::drawEvent() {
|
void ViewerApp::drawEvent() {
|
||||||
updatePlayback();
|
updatePlayback();
|
||||||
updateSceneAtCurrentTime();
|
updateSceneAtCurrentTime();
|
||||||
@@ -292,19 +479,16 @@ void ViewerApp::keyPressEvent(KeyEvent& event) {
|
|||||||
|
|
||||||
switch(event.key()) {
|
switch(event.key()) {
|
||||||
case Key::Space:
|
case Key::Space:
|
||||||
if(!_isPlaying && !_sceneData.times.empty() && _currentTime >= _sceneData.times.back()) _currentTime = 0.0f;
|
if(!_isPlaying && !_sceneData.times.empty() && _currentTime >= _sceneData.times.back()) rewindPlayback();
|
||||||
_isPlaying = !_isPlaying;
|
_isPlaying = !_isPlaying;
|
||||||
event.setAccepted();
|
event.setAccepted();
|
||||||
return;
|
return;
|
||||||
case Key::R:
|
case Key::R:
|
||||||
_currentTime = 0.0f;
|
rewindPlayback();
|
||||||
_isPlaying = false;
|
|
||||||
event.setAccepted();
|
event.setAccepted();
|
||||||
return;
|
return;
|
||||||
case Key::F: {
|
case Key::F: {
|
||||||
const Vector3 center = (_sceneData.initialBounds.min() + _sceneData.initialBounds.max())*0.5f;
|
resetCameraToScene();
|
||||||
const float radius = Math::max((_sceneData.initialBounds.max() - _sceneData.initialBounds.min()).max(), 0.5f);
|
|
||||||
_orbit.setSceneBounds(center, radius);
|
|
||||||
event.setAccepted();
|
event.setAccepted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -328,7 +512,9 @@ void ViewerApp::keyReleaseEvent(KeyEvent& event) {
|
|||||||
void ViewerApp::pointerPressEvent(PointerEvent& event) {
|
void ViewerApp::pointerPressEvent(PointerEvent& event) {
|
||||||
if(_imgui.handlePointerPressEvent(event)) return;
|
if(_imgui.handlePointerPressEvent(event)) return;
|
||||||
_lastPointerPosition = event.position();
|
_lastPointerPosition = event.position();
|
||||||
if(event.pointer() == Pointer::MouseLeft) _dragMode = DragMode::Orbit;
|
if(event.pointer() == Pointer::MouseLeft) {
|
||||||
|
_dragMode = (event.modifiers() & Modifier::Shift) ? DragMode::Pan : DragMode::Orbit;
|
||||||
|
}
|
||||||
else if(event.pointer() == Pointer::MouseRight || event.pointer() == Pointer::MouseMiddle) _dragMode = DragMode::Pan;
|
else if(event.pointer() == Pointer::MouseRight || event.pointer() == Pointer::MouseMiddle) _dragMode = DragMode::Pan;
|
||||||
event.setAccepted();
|
event.setAccepted();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <Magnum/Math/Color.h>
|
#include <Magnum/Math/Color.h>
|
||||||
#include <Magnum/Math/Matrix4.h>
|
#include <Magnum/Math/Matrix4.h>
|
||||||
#include <Magnum/Math/Time.h>
|
#include <Magnum/Math/Time.h>
|
||||||
|
#include <Magnum/Shaders/FlatGL.h>
|
||||||
#include <Magnum/SceneGraph/Camera.h>
|
#include <Magnum/SceneGraph/Camera.h>
|
||||||
#include <Magnum/SceneGraph/Drawable.h>
|
#include <Magnum/SceneGraph/Drawable.h>
|
||||||
#include <Magnum/SceneGraph/MatrixTransformation3D.h>
|
#include <Magnum/SceneGraph/MatrixTransformation3D.h>
|
||||||
@@ -38,6 +39,7 @@ struct CliOptions {
|
|||||||
std::vector<std::string> geometryDirs;
|
std::vector<std::string> geometryDirs;
|
||||||
float initialSpeed = 1.0f;
|
float initialSpeed = 1.0f;
|
||||||
bool startPaused = false;
|
bool startPaused = false;
|
||||||
|
bool reverseMouseY = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ViewerApp: public Magnum::Platform::Application {
|
class ViewerApp: public Magnum::Platform::Application {
|
||||||
@@ -99,12 +101,36 @@ private:
|
|||||||
Magnum::Color4 _color;
|
Magnum::Color4 _color;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FlatDrawable final: public Drawable3D {
|
||||||
|
public:
|
||||||
|
FlatDrawable(Object3D& object,
|
||||||
|
Magnum::Shaders::FlatGL3D& shader,
|
||||||
|
Magnum::GL::Mesh& mesh,
|
||||||
|
Magnum::Color4 color,
|
||||||
|
DrawableGroup3D& drawables);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void draw(const Magnum::Matrix4& transformation, Camera3D& camera) override;
|
||||||
|
|
||||||
|
Magnum::Shaders::FlatGL3D& _shader;
|
||||||
|
Magnum::GL::Mesh& _mesh;
|
||||||
|
Magnum::Color4 _color;
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<GpuMesh> uploadMesh(const CpuMesh& mesh) const;
|
std::shared_ptr<GpuMesh> uploadMesh(const CpuMesh& mesh) const;
|
||||||
void createSceneObjects();
|
void createSceneObjects();
|
||||||
|
void createReferenceObjects();
|
||||||
void updatePlayback();
|
void updatePlayback();
|
||||||
void updateSceneAtCurrentTime();
|
void updateSceneAtCurrentTime();
|
||||||
void drawUi();
|
void drawUi();
|
||||||
std::size_t sampleIndexForTime(float time) const;
|
std::size_t sampleIndexForTime(float time) const;
|
||||||
|
[[nodiscard]] float sceneRadiusFromOrigin() const;
|
||||||
|
[[nodiscard]] Magnum::Vector3 sceneCenter() const;
|
||||||
|
[[nodiscard]] float sceneRadius() const;
|
||||||
|
void seekPlayback(float time);
|
||||||
|
void resetDefaultCamera();
|
||||||
|
void resetCameraToScene();
|
||||||
|
void rewindPlayback();
|
||||||
|
|
||||||
CliOptions _options;
|
CliOptions _options;
|
||||||
LoadedScene _sceneData;
|
LoadedScene _sceneData;
|
||||||
@@ -118,6 +144,7 @@ private:
|
|||||||
DrawableGroup3D _drawables;
|
DrawableGroup3D _drawables;
|
||||||
|
|
||||||
Magnum::Shaders::PhongGL _shader;
|
Magnum::Shaders::PhongGL _shader;
|
||||||
|
Magnum::Shaders::FlatGL3D _flatShader;
|
||||||
std::vector<std::shared_ptr<GpuMesh>> _gpuMeshes;
|
std::vector<std::shared_ptr<GpuMesh>> _gpuMeshes;
|
||||||
std::vector<std::unique_ptr<Drawable3D>> _drawableStorage;
|
std::vector<std::unique_ptr<Drawable3D>> _drawableStorage;
|
||||||
std::vector<std::unique_ptr<Object3D>> _objectStorage;
|
std::vector<std::unique_ptr<Object3D>> _objectStorage;
|
||||||
@@ -125,8 +152,12 @@ private:
|
|||||||
std::vector<SceneMarker> _markers;
|
std::vector<SceneMarker> _markers;
|
||||||
|
|
||||||
std::shared_ptr<GpuMesh> _markerMesh;
|
std::shared_ptr<GpuMesh> _markerMesh;
|
||||||
|
std::shared_ptr<GpuMesh> _boxMesh;
|
||||||
|
Magnum::GL::Mesh _groundGridMesh;
|
||||||
|
Magnum::GL::Mesh _originWireBoxMesh;
|
||||||
|
|
||||||
bool _isPlaying = false;
|
bool _isPlaying = false;
|
||||||
|
bool _loopPlayback = true;
|
||||||
float _playbackSpeed = 1.0f;
|
float _playbackSpeed = 1.0f;
|
||||||
float _currentTime = 0.0f;
|
float _currentTime = 0.0f;
|
||||||
std::chrono::steady_clock::time_point _lastTick;
|
std::chrono::steady_clock::time_point _lastTick;
|
||||||
|
|||||||
+1
-1
@@ -13,10 +13,10 @@ int main(int argc, char** argv) {
|
|||||||
cli.add_option("--geometry-dir", options.geometryDirs, "Additional geometry search directories");
|
cli.add_option("--geometry-dir", options.geometryDirs, "Additional geometry search directories");
|
||||||
cli.add_option("--speed", options.initialSpeed, "Initial playback speed")->check(CLI::PositiveNumber);
|
cli.add_option("--speed", options.initialSpeed, "Initial playback speed")->check(CLI::PositiveNumber);
|
||||||
cli.add_flag("--start-paused", options.startPaused, "Start paused at t=0");
|
cli.add_flag("--start-paused", options.startPaused, "Start paused at t=0");
|
||||||
|
cli.add_flag("--reverse-mouse-y", options.reverseMouseY, "Reverse vertical orbit drag direction");
|
||||||
|
|
||||||
CLI11_PARSE(cli, argc, argv);
|
CLI11_PARSE(cli, argc, argv);
|
||||||
|
|
||||||
osim_viewer::ViewerApp app({argc, argv}, std::move(options));
|
osim_viewer::ViewerApp app({argc, argv}, std::move(options));
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user