Cogs.Core
OrbitingCameraController.cpp
1#include "OrbitingCameraController.h"
2
3#include "Bridge/SceneFunctions.h"
4
5#include "Context.h"
6#include "Engine.h"
7#include "Types.h"
8#include "ViewContext.h"
9
10#include "Editor/IEditor.h"
11
12#include "Components/Core/TransformComponent.h"
13#include "Components/Core/SceneComponent.h"
14#include "Components/Core/CameraComponent.h"
15
16#include "Input/KeyboardMapping.h"
17#include "Input/InputManager.h"
18
19#include "Renderer/CustomRenderer/ImguiRenderer.h"
20
21#include "Scene/RayPick.h"
22#include "Systems/Core/CameraSystem.h"
23
24#include "Utilities/CameraUtils.h"
25
26#include "Foundation/Platform/Mouse.h"
27#include "Foundation/Platform/Keyboard.h"
28
29#include <glm/vec2.hpp>
30#include <glm/vec3.hpp>
31#include <glm/vec4.hpp>
32#include <glm/mat4x4.hpp>
33#include <glm/ext/quaternion_float.hpp>
34#include <glm/gtx/compatibility.hpp>
35
36#include "imgui.h"
37#include "ImGuizmo.h"
38
39using namespace Cogs::Reflection;
40
41Cogs::Core::OrbitingCameraController::OrbitingCameraController()
42{
43 minVerticalAngle = 0.0f;
44}
45
46void Cogs::Core::OrbitingCameraController::registerType()
47{
48 Method methods[] = {
49 { "initialize", &OrbitingCameraController::initialize },
50 { "update", &OrbitingCameraController::update },
51 };
52
53 Field fields [] = {
57 .setRange(-glm::pi<float>(), glm::pi<float>()),
59 .setRange(-2.0f * glm::pi<float>(), 2.0f * glm::pi<float>()),
62 .setRange(-glm::pi<float>(), glm::pi<float>()),
64 .setRange(-glm::pi<float>(), glm::pi<float>()),
72 };
73
74 DynamicComponent::registerDerivedType<OrbitingCameraController>().setFields(fields).setMethods(methods);
75}
76
77
78void Cogs::Core::OrbitingCameraController::initialize(Context* ctx)
79{
80 context = ctx;
81 // Use the view with the same camera as this component is attached to if it can be found, otherwise use the default view.
82 if (auto camera = getComponent<CameraComponent>(); camera) {
83 for (ViewContext* v : context->getViews()) {
84 if (v->getCameraComponent() == camera) {
85 view = v;
86 return;
87 }
88 }
89 }
90 view = context->getDefaultView();
91}
92
93
94void Cogs::Core::OrbitingCameraController::handleSeek(float x, float y)
95{
96 if (auto camera = getComponent<CameraComponent>(); camera) {
97 float viewportX = x - camera->viewportOrigin.x;
98 float viewportY = context->renderer->getSize().y - (int)camera->viewportOrigin.y - (y - (int)camera->viewportOrigin.y) - 1;
99 if (viewportX >= 0 && viewportY >= 0 && viewportX < camera->viewportSize.x && viewportY < camera->viewportSize.y) {
100 std::vector<RayPicking::RayPickHit> picked;
101 context->rayPicking->pickCamera(context, *camera, glm::vec2(viewportX, viewportY), std::numeric_limits<float>::max(),
102 defaultPickingRadius, PickingFlags::None, PicksReturned::Closest, {}, picked);
103 if (!picked.empty()) {
104 cameraTarget = context->transformSystem->worldFromEngineCoords(picked.front().position);
105 }
106 }
107 }
108}
109
110void Cogs::Core::OrbitingCameraController::handleRotate(float x0, float y0, float x1, float y1, bool first)
111{
112 if (first) {
113 horizontalAngleStart = horizontalAngle;
114 verticalAngleStart = verticalAngle;
115 }
116 horizontalAngle = fmodf(horizontalAngleStart - (x1 - x0) * 0.005f, 2.0f * glm::pi<float>());
117 verticalAngle = glm::clamp(verticalAngleStart - (y1 - y0) * 0.005f, minVerticalAngle, maxVerticalAngle);
118}
119
120void Cogs::Core::OrbitingCameraController::handleTranslate(float x0, float y0, float x1, float y1, bool first)
121{
122 if (first) { cameraTargetStart = cameraTarget; }
123 const CameraData& cameraData = context->cameraSystem->getData(getComponent<CameraComponent>());
124
125 // Move camera target into view space and check if it is in front of the camera
126 glm::vec4 cameraTargetView = cameraData.viewMatrix * glm::vec4(context->transformSystem->engineFromWorldCoords(cameraTarget), 1.f);
127 if (cameraTargetView.z < 0.f) {
128
129 // Camera target in clip space
130 glm::vec4 cameraTargetClip = cameraData.projectionMatrix * cameraTargetView;
131
132 // Mouse position xy in clip space
133 glm::vec2 viewportScale = (2.f * cameraTargetClip.w) / cameraData.viewportSize;
134 glm::vec2 p0 = viewportScale * (glm::vec2(x0, y0) - cameraData.viewportOrigin) - glm::vec2(1.f);
135 glm::vec2 p1 = viewportScale * (glm::vec2(x1, y1) - cameraData.viewportOrigin) - glm::vec2(1.f);
136
137 // Create mouse positions in clip space and transform to engine coords
138 glm::vec4 ah = cameraData.inverseViewProjectionMatrix * glm::vec4(p0.x, -p0.y, cameraTargetClip.z, cameraTargetClip.w);
139 glm::vec3 a = (1.f / ah.w) * glm::vec3(ah);
140
141 glm::vec4 bh = cameraData.inverseViewProjectionMatrix * glm::vec4(p1.x, -p1.y, cameraTargetClip.z, cameraTargetClip.w);
142 glm::vec3 b = (1.f / bh.w) * glm::vec3(bh);
143
144 // Find relative movement and move target
145 glm::dvec3 diff = glm::dvec3(b) - glm::dvec3(a);
146 if (glm::all(glm::isfinite(diff)) && glm::length(diff) != 0) {
147 cameraTarget = cameraTargetStart - diff;
148 }
149 }
150}
151
152void Cogs::Core::OrbitingCameraController::handleDolly(float delta, bool first)
153{
154 if (first) { distanceStart = distance; }
155 distance = glm::clamp(distanceStart * expf(-delta), minDistance, maxDistance);
156}
157
158void Cogs::Core::OrbitingCameraController::handleZoom(float delta, bool first)
159{
160 if (CameraComponent* camera = getComponent<CameraComponent>(); camera != nullptr) {
161 if (first) { fovStart = camera->fieldOfView; }
162 camera->fieldOfView = glm::clamp(fovStart * expf(-delta), minFOV, maxFOV);
163 }
164}
165
166void Cogs::Core::OrbitingCameraController::update()
167{
168 if(!enabled || view == nullptr) return;
169 // If the OCC was marked as changed, update even when imgui has the focus.
170 if(!hasChanged() && (ImGuizmo::IsUsing() || ImguiRenderer::isUsingMouse())) return;
171
172 bool shiftIsDown = view->refKeyboard().isKeyDown(Key::Shift);
173 bool sIsDown = view->refKeyboard().isKeyDown(Key::S);
174 const std::vector<Gesture>& gestures = view->refGestures().getGestures();
175 for (const Gesture& g : gestures) {
176 bool started = g.state == Gesture::State::Started;
177 switch (g.kind) {
179 if (g.state == Gesture::State::Ended && sIsDown) { handleSeek(g.press.coord.x, g.press.coord.y); }
180 break;
182 if (sIsDown) { handleSeek(g.tap.coord.x, g.tap.coord.y); }
183 break;
185 handleSeek(g.doubleTap.coord.x, g.doubleTap.coord.y);
186 break;
188 if (shiftIsDown) handleZoom(0.12f * g.wheel.delta, true);
189 else handleDolly(0.12f * g.wheel.delta, true);
190 break;
192 switch (g.drag.button) {
193 case MouseButton::Middle: handleTranslate(g.drag.startCoord.x, g.drag.startCoord.y, g.drag.currCoord.x, g.drag.currCoord.y, started); break;
194 case MouseButton::Left: handleRotate(g.drag.startCoord.x, g.drag.startCoord.y, g.drag.currCoord.x, g.drag.currCoord.y, started); break;
195 case MouseButton::Right:
196 {
197 // Make sure to reset the drag starting position when shift is pressed/released while right mouse btn dragging,
198 // otherwise there will be a visible jump between zoom and dolly.
199 if (g.state == Gesture::State::Started) {
200 dragStartCoordY = g.drag.startCoord.y;
201 }
202 if (shiftIsDown != prevDragWithShift) {
203 prevDragWithShift = shiftIsDown;
204 dragStartCoordY = g.drag.currCoord.y;
205 started = true;
206 }
207
208 if (shiftIsDown) handleZoom(0.01f * (g.drag.currCoord.y - dragStartCoordY), started);
209 else handleDolly(0.01f * (g.drag.currCoord.y - dragStartCoordY), started);
210 break;
211 }
212 default: break;
213 }
214 break;
216 handleTranslate(g.pan.midStartCoord.x, g.pan.midStartCoord.y, g.pan.midCurrCoord.x, g.pan.midCurrCoord.y, started);
217 break;
219 handleDolly(g.pinch.scale - 1.f, started);
220 break;
224 default:
225 break;
226 }
227 }
228
229 glm::quat horizontalRotation = glm::angleAxis(horizontalAngle, glm::vec3(0, 0, 1));
230 glm::quat verticalRotation = glm::angleAxis(verticalAngle, glm::vec3(1, 0, 0));
231 glm::quat rotation = horizontalRotation * verticalRotation;
232
233 TransformComponent* transform = getComponent<TransformComponent>();
234
235 const glm::vec3 cameraDirection = rotation * glm::vec3(0, 0, 1);
236 const glm::dvec3 cameraPos = cameraTarget + glm::dvec3(distance *cameraDirection);
237 if (moveCamera && glm::all(glm::isfinite(cameraPos))) {
238
239 // Check if we are a root node or not
240 const ComponentHandle parent = getComponent<SceneComponent>()->parent;
241
242 // Camera is root node
243 if (!parent) {
244
245 // Avoid continuous updates
246 if (transform->coordinates != cameraPos || transform->position != glm::vec3(0.f)) {
247 transform->coordinates = cameraPos;
248 transform->position = glm::vec3(0.f);
249 transform->setChanged();
250 }
251 }
252
253 // Camera is not a root node, get hold of root
254 else {
255 const SceneComponent* root = parent.resolveComponent<SceneComponent>();
256 while (root && root->parent) {
257 root = root->parent.resolveComponent<SceneComponent>();
258 }
259 if (root) {
260 const TransformComponent* rootTransform = root->getComponent<TransformComponent>();
261 if (rootTransform) {
262
263 // Remove coordinates of root entity.
264 glm::vec3 cameraPosInRoot = glm::vec3(cameraPos - rootTransform->coordinates);
265
266 // FIXME: Reverse all transforms up the chain. Need a good case before tackling that.
267
268 // Avoid continuous updates
269 if (transform->position != cameraPosInRoot) {
270 transform->position = cameraPosInRoot;
271 transform->setChanged();
272 }
273 }
274 }
275 }
276
277 }
278
279 // Avoid continuous updates of camera.
280 cameraLook = -cameraDirection;
281 const glm::vec3 cameraUp = rotation * glm::vec3(0, 1, 0);
282 const glm::quat cameraRot = glm::quat_cast(glm::inverse(glm::lookAt(cameraDirection, glm::vec3(0.f), cameraUp)));
283 if (cameraRot != transform->rotation) {
284 transform->rotation = cameraRot;
285 transform->setChanged();
286 }
287}
static bool isUsingMouse()
Tests whether any ImGui control is being interacted with, or if the mouse is over an ImGui window or ...
float horizontalAngle
Current camera angle in horizontal direction [-TwoPI,+TwoPI] (glm::roll(transformComponent->rotation)...
float minDistance
Minimal distance from camera position to camera target (zoom).
float maxDistance
Maximal distance from camera position to camera target (zoom).
bool enabled
Navigation is only active when this flag is set.
float maxVerticalAngle
Maximum camera angle in vertical direction [-PI,+PI].
float distance
Distance from camera target to camera.
glm::vec3 cameraLook
camera looking direction. Updated when navigating the camers.
bool seek
Deprecated Seeking flag - not used.
glm::dvec3 cameraTarget
Camera target. Define centre of camera rotation.
bool moveCamera
Set to false to disable controller moving the camera and only apply rotate and zoom....
float minVerticalAngle
Minimum camera angle in vertical direction [-PI,+PI].
float verticalAngle
Current camera angle in vertical direction [-PI,+PI] (glm::pitch(transformComponent->rotation))
Field definition describing a single data member of a data structure.
Definition: Field.h:70
static FieldWrapper< Field, FieldType > create(const Name &name, FieldType ClassType::*field)
Creates a new field instance, returning a wrapper for type safe continuation style setup.
Definition: Field.inl:29
Simple method definition.
Definition: Method.h:72
@ Closest
Return just the closest hit.
@ None
No flags specified,.
Contains reflection support.
Definition: Component.h:11
Handle to a Component instance.
Definition: Component.h:67
ComponentType * resolveComponent() const
Definition: Component.h:90
@ Press
A long press, a press that was too long to be a tap, events on press start and end.
@ Pinch
Two-finger movement where the two fingers comes closer or farther away, see pinchThreshold,...
@ Tap
A short press, see tapMaxDuration, single-fire event.
@ Swipe
Swift pointer movement with button pressed or touch, see swipeMaxDuration, mouseVelocityThreshold and...
@ Rotate
Two-finger rotation movement where fingers are roughly equidistant, see rotateThreshold,...
@ DoubleTap
Two taps in quick succession, see doubleTapTreshold, single-fire event.
@ Wheel
Movemement of the mouse wheel, single-fire event.
@ Drag
Pointer movement with a button pressed or touch, see mouseMoveThreshold and touchMoveThreshold,...
@ Hover
Mouse pointer hovers over window without any buttons pressed, single-fire event.
@ Pan
Two-finger touch movement where fingers are roughly equidistant, see mouseMoveThreshold and touchMove...
@ Ended
Gesture ended or single-fire events, used by all gestures.
@ Started
Gesture started, used by: Press, Drag, Swipe, Pan, Rotate, Pinch.