Cogs.Core
TrajectorySystem.cpp
1#include "TrajectorySystem.h"
2
3#include "Context.h"
4
5#include "Components/Core/LodComponent.h"
6#include "Components/Data/TrajectoryComponent.h"
7
8#include "Systems/Core/CameraSystem.h"
9#include "Systems/Core/TransformSystem.h"
10#include "Systems/Core/LodSystem.h"
11
12#include "Utilities/Math.h"
13#include "Utilities/TransformVertices.h"
14
15#include "Foundation/ComponentModel/Entity.h"
16#include "Foundation/Logging/Logger.h"
17
18#define _USE_MATH_DEFINES
19#include <cmath>
20
21using namespace Cogs::Core;
22
23namespace {
24 Cogs::Logging::Log logger = Cogs::Logging::getLogger("TrajectorySystem");
25
26 glm::vec3 removeLinearDepenence(const glm::vec3& vec, const glm::vec3& normalizedRef)
27 {
28 return vec - glm::dot(vec, normalizedRef)*normalizedRef;
29 }
30
31 glm::vec3 pickAnyDirectionBut(const glm::vec3& but)
32 {
33 glm::vec3 t = glm::abs(but);
34 glm::vec3 r;
35 if ((t.x > t.y) && (t.x > t.z)) {
36 r = glm::vec3(0.f, 0.f, 1.f); // x is smallest largest, choose z
37 }
38 else if (t.x > t.y) {
39 r = glm::vec3(0.f, 1.f, 0.f); // t.z is largest component, choose y
40 }
41 else {
42 r = glm::vec3(1.f, 0.f, 0.f); // otherwise choose x
43 }
44 return glm::normalize(removeLinearDepenence(r, but));
45 }
46}
47
48namespace {
49 template<GeometricErrorKind K> struct GeometricErrorKindTraits;
50
51 template<> struct GeometricErrorKindTraits < GeometricErrorKind::DistanceBased >
52 {
53 inline static float bakedTolerance(float sourceTolerance) { return sourceTolerance; }
54 inline static float error(const glm::vec2& /*ab*/, const glm::vec2&ac, const glm::vec2&n)
55 { return glm::abs(glm::dot(ac, n));}
56 };
57
58 template<> struct GeometricErrorKindTraits < GeometricErrorKind::AreaBased >
59 {
60 inline static float bakedTolerance(float sourceTolerance) { return sourceTolerance*sourceTolerance; }
61 inline static float error(const glm::vec2&ab, const glm::vec2&ac, const glm::vec2& /*n*/)
62 { return 0.5f*glm::abs(ab.x*ac.y - ac.x*ab.y); }
63 };
64}
65
66template<GeometricErrorKind K>
67void selectLodSubset(std::vector<bool>& currInclude,
68 const std::vector<bool>& prevInclude,
69 const std::vector<glm::vec4> positions,
70 const glm::vec2& viewportSize,
71 const float distanceTolerance,
72 const float previousFrameStickyness)
73{
74 float tolerance = GeometricErrorKindTraits<K>::bakedTolerance(distanceTolerance);
75 float unstickyness = glm::clamp(1.f - previousFrameStickyness, 0.001f, 0.999f);
76
77 const size_t N = positions.size();
78 std::vector<glm::vec2> screenPosition(N);
79 std::vector<std::pair<size_t,size_t>> ranges;
80
81 currInclude.clear();
82 currInclude.resize(N);
83
84 bool prevInsideState = false;
85 size_t rangeStart = 0;
86 for(size_t i=0; i<=N; i++) {
87 // Check if we're behind or in front of the near-plane. We do an
88 // extra iteration which fails the inside-test at the end to
89 // purge any ongoing intervals.
90 bool currInsideState = i < N ? positions[i].z >= -positions[i].w : false;
91
92 if (prevInsideState != currInsideState) {
93 prevInsideState = currInsideState;
94 currInclude[i == 0 ? 0 : i - 1] = true; // Make sure that samples on both sides
95 currInclude[i < N ? i : N - 1] = true; // of the transition zone are included
96
97 if (currInsideState) { // we're entering the frustum
98 rangeStart = i;
99 }
100 else { // we're exiting the frustum
101 ranges.emplace_back(rangeStart, i - 1);
102 }
103 }
104
105 // And we find the screen-space position for all points behind near.
106 if(currInsideState) {
107 const glm::vec4& p = positions[i];
108 screenPosition[i] = (0.5f / p.w)* viewportSize * glm::vec2(p.x, p.y);
109 }
110 }
111 // Now, all the indices being part of a line segment intersecting the near-
112 // plane are tagged for inclusion, as well as all intervals of line segments
113 // behind the near plane is stored in in ranges.
114
115 // Next step is to subdivide ranges and tag split points until the criteria
116 // is met. We find the most offending index in the range, insert this point
117 // and refine, until we are happy.
118 while (!ranges.empty()) {
119
120 const std::pair<size_t, size_t> r = ranges.back();
121 ranges.pop_back();
122
123 // We run through the interval searching for the most offending index,
124 // and if that index is not offending enough, we're done with that
125 // interval.
126 if (r.first + 1 < r.second) {
127 size_t prevSplit = 0;
128 size_t currSplit = 0;
129 float prevError = -std::numeric_limits<float>::max();
130 float currError = -std::numeric_limits<float>::max();
131
132 glm::vec2 ab = screenPosition[r.second] - screenPosition[r.first];
133 glm::vec2 n = glm::normalize(glm::vec2(-ab.y, ab.x));
134 for (size_t i = r.first + 1; i < r.second; i++) {
135 glm::vec2 ac = screenPosition[i] - screenPosition[r.first];
136
137 float error = GeometricErrorKindTraits<K>::error(ab, ac, n);
138
139 if (currError < error) { // Update overall most offending
140 currSplit = i; // index in interval
141 currError = error;
142 }
143
144 if ((prevError < error) && prevInclude[i]) { // Update most offending
145 prevSplit = i; // index that was included
146 prevError = error; // in the previous frame.
147 }
148 }
149
150 if (tolerance < currError) { // here we assume that tolerance
151 // is a non-negative value
152
153 // Now, if the error is almost the same by using an indexed that was
154 // included the previous frame, we do that, and reduce popping artifacts.
155 if (unstickyness*currError < prevError) {
156 currSplit = prevSplit;
157 }
158
159 // And split. Only add ranges if they have interior indices.
160 currInclude[currSplit] = true;
161 if (r.first + 1 < currSplit) {
162 ranges.emplace_back(r.first, currSplit);
163 }
164 if (currSplit + 1 < r.second) {
165 ranges.emplace_back(currSplit, r.second);
166 }
167
168 }
169 }
170 }
171}
172
173void Cogs::Core::TrajectorySystem::generateAnchoredFramesOfReference(std::vector<glm::quat>& orientations,
174 const std::vector<float>& indices,
175 const std::vector<glm::vec3>& positions,
176 const glm::vec3& startAnchor,
177 const glm::vec3& stopAnchor,
178 const float anchorStickyness)
179{
180 const size_t N = positions.size();
181 orientations.resize(N);
182 if (N < 2) {
183 return;
184 }
185
186 // calculate directions for each position
187 std::vector<glm::vec3> directions(N);
188 for (size_t i = 0; i < N - 1; i++) { // calculate tangents
189 directions[i] = positions[i + 1] - positions[i];
190 }
191 directions[N - 1] = directions[N - 2]; // duplicate at end
192 for (size_t i = N - 2; i > 0; i--) { // average at inner indices backwards
193 directions[i] = glm::normalize(directions[i] + directions[i - 1]);
194 }
195 directions[0] = glm::normalize(directions[0]); // normalize end points
196 directions[N - 1] = glm::normalize(directions[N - 1]);
197
198 std::vector<float> debug(N, -43.f);
199 std::vector<glm::vec3> anchors(N);
200 anchors[0] = glm::normalize(removeLinearDepenence(startAnchor, directions[0]));
201 anchors[N-1] = glm::normalize(removeLinearDepenence(stopAnchor, directions[N - 1]));
202
203 float to = indices[0];
204 float ts = 1.f / (indices[N - 1] - indices[0]);
205 float linearDepenenceTolerance = glm::clamp(1.f - anchorStickyness, 0.01f, 1.f);
206
207 size_t prevIndependent = 0;
208 for (size_t i = 1; i < N; ) {
209 glm::vec3 interpolatedAnchor = glm::mix(startAnchor, stopAnchor, ts*(indices[i] - to));
210 glm::vec3 orthogonalAnchor = removeLinearDepenence(interpolatedAnchor, directions[i]);
211
212 if (linearDepenenceTolerance < glm::length(orthogonalAnchor)) {
213 // All well with the interpolated anchor
214 anchors[i] = glm::normalize(orthogonalAnchor);
215 prevIndependent = i;
216 //debug[i] = -42.f;
217 // And skip forward
218 i++;
219 }
220 else {
221 // Anchor is or is close linear dependent on tangent, search forward
222 // until we get something decent.
223 size_t nextIndependent = N - 1;
224 for (size_t k = i + 1; k < N - 1; k++) {
225 glm::vec3 interpolatedAnchor_ = glm::mix(startAnchor, stopAnchor, ts*(indices[k] - to));
226 glm::vec3 orthogonalAnchor_ = removeLinearDepenence(interpolatedAnchor_, directions[k]);
227 if (linearDepenenceTolerance < glm::length(orthogonalAnchor_)) {
228 anchors[k] = glm::normalize(orthogonalAnchor_);
229 //debug[i] = -44.f;
230 nextIndependent = k;
231 break;
232 }
233 }
234
235
236 // Try to create a plane in which the curve lies. We use the normal (or the flip
237 // of the normal) of this plane to define and small detour for the anchor.
238 size_t halfWay = (prevIndependent + nextIndependent) / 2;
239 glm::vec3 va = glm::normalize(positions[nextIndependent] - positions[prevIndependent]);
240 glm::vec3 detour = glm::cross(positions[halfWay] - positions[prevIndependent], va);
241 if (glm::length(detour) < std::numeric_limits<float>::epsilon()) {
242 // Failed to create a plane, we settle for anything orthogonal to the midpoint tangent.
243 detour = pickAnyDirectionBut(directions[halfWay]);
244 }
245 else {
246 // We do have a candidate...!
247 detour = glm::normalize(detour);
248
249 // Then we check if the twist (if any) of the unconstrained rotation
250 // aligns better with n or -n.
251 glm::quat unconstrainedRotation = Cogs::Core::getRotation(directions[prevIndependent], directions[halfWay]);
252 if (glm::dot(unconstrainedRotation * anchors[prevIndependent], detour) < 0.f) {
253 detour = -detour;
254 }
255 }
256
257 // And do a quadratic interpolation through the detour
258 for (size_t k = prevIndependent+1; k < nextIndependent; k++) {
259 float t_k = ts*(indices[k] - indices[prevIndependent]) / (indices[nextIndependent] - indices[prevIndependent]);
260 glm::vec3 twistAnchor = ((1.f - t_k)*(1.f - t_k)*anchors[prevIndependent] +
261 2.f*(1.f - t_k)*t_k*detour +
262 t_k*t_k*anchors[nextIndependent]);
263 anchors[k] = glm::normalize(removeLinearDepenence(twistAnchor, directions[k]));
264 debug[k] = t_k;
265 }
266
267 // and finally skip forward
268 prevIndependent = nextIndependent;
269 i = nextIndependent+1;
270 }
271 }
272
273 // Create rotations
274 for (size_t i = 0; i < N; i++) {
275
276 // Rotation for aligning local Z with tangent
277 glm::quat alignZ;
278 if (i == 0) {
279 alignZ = Cogs::Core::getRotation(glm::vec3(0.f, 0.f, 1.f), directions[0]);
280 }
281 else {
282 alignZ = Cogs::Core::getRotation(directions[i - 1], directions[i])*orientations[i - 1];
283 }
284
285 // Roll-correction to align local Y with predefined anchor
286 glm::quat alignY = Cogs::Core::getRotation(alignZ * glm::vec3(0.f, 1.f, 0.f), anchors[i]);
287
288 orientations[i] = alignY*alignZ;
289 }
290}
291
292
293void Cogs::Core::TrajectorySystem::generateFramesOfReference(std::vector<glm::quat>& orientations,
294 const std::vector<glm::vec3>& positions)
295{
296 const size_t N = positions.size();
297 orientations.resize(N);
298 if (N < 2) {
299 return;
300 }
301
302
303 // calculate directions for each position
304 std::vector<glm::vec3> directions(N);
305 for (size_t i = 0; i < N - 1; i++) { // calcualte tangents
306 directions[i] = positions[i + 1] - positions[i];
307 }
308 directions[N - 1] = directions[N - 2]; // duplicate at end
309 for (size_t i = N - 2; i > 0; i--) { // average at inner indices backwards
310 directions[i] = glm::normalize(directions[i] + directions[i - 1]);
311 }
312 directions[0] = glm::normalize(directions[0]); // normalize end points
313 directions[N - 1] = glm::normalize(directions[N - 1]);
314
315 // incrementally create a sequence of orientations.
316 orientations[0] = Cogs::Core::getRotation(glm::vec3(0.f, 0.f, 1.f), directions[0]);
317 for (size_t i = 1; i < N; i++) {
318 orientations[i] = Cogs::Core::getRotation(directions[i - 1], directions[i])*orientations[i - 1];
319 }
320}
321
323{
324 const auto & cameraComponent = context->cameraSystem->getMainCamera();
325
326 for (TrajectoryComponent& trajComp : pool) {
327 TrajectoryData& trajData = getData(&trajComp);
328
329 if (trajComp.hasChanged()) {
330
331 float l2a = glm::dot(trajComp.startRadialAnchor, trajComp.startRadialAnchor);
332 float l2b = glm::dot(trajComp.startRadialAnchor, trajComp.stopRadialAnchor);
333 if (std::isnormal(l2a) && std::isnormal(l2b)) {
334 generateAnchoredFramesOfReference(trajData.frames, trajComp.indexes, trajComp.positions,
335 trajComp.startRadialAnchor, trajComp.stopRadialAnchor,
336 trajComp.anchorStickyness);
337 }
338 else {
339 generateFramesOfReference(trajData.frames, trajComp.positions);
340 }
341 }
342
343
344 LodComponent* lodComp = trajComp.getComponent<LodComponent>();
345
346 if (lodComp) {
347 if (lodComp->policy == LodPolicy::GeometricTolerance) {
348 const LodData & lodData = context->lodSystem->getData<LodData>(lodComp);
349
350 // Transform to clip space
351 trajData.clipSpacePos.resize(trajComp.positions.size());
352 Cogs::Core::transformVertices(context,
353 trajData.clipSpacePos,
354 lodData.localToClip,
355 trajComp.positions);
356
357 bool hasChanged = false;
358 std::vector<bool> lodSelection(trajComp.positions.size(), false);
359 if (trajComp.hasChanged() || (trajData.lodSelection.size() != trajComp.positions.size())) {
360 trajData.lodSelection.clear();
361 trajData.lodSelection.resize(trajComp.positions.size());
362 hasChanged = true;
363 }
364
365 if (true) { // if anything that concerns the projection has changed or lod'ing
366
367 switch (context->lodSystem->geometricErrorKind) {
368 case GeometricErrorKind::AreaBased:
369 selectLodSubset<GeometricErrorKind::AreaBased>(lodSelection, trajData.lodSelection, trajData.clipSpacePos,
370 cameraComponent->viewportSize, lodComp->geometricTolerance,
371 context->lodSystem->previousFrameStickyness);
372 break;
373 case GeometricErrorKind::DistanceBased:
374 selectLodSubset<GeometricErrorKind::DistanceBased>(lodSelection, trajData.lodSelection, trajData.clipSpacePos,
375 cameraComponent->viewportSize, lodComp->geometricTolerance,
376 context->lodSystem->previousFrameStickyness);
377 break;
378 }
379
380 if (!hasChanged) {
381 hasChanged = !std::equal(lodSelection.begin(), lodSelection.end(),
382 trajData.lodSelection.begin());
383 }
384
385 if (hasChanged) {
386 trajData.lodSelection.swap(lodSelection);
387 trajComp.setChangedTransient(); // notify rest of systems down the chain, but don't ping-pong
388 }
389 }
390 }
391 else {
392 if (lodComp->hasChanged() || (trajData.lodSelection.size() != trajData.frames.size())) {
393 trajData.lodSelection.clear();
394 trajData.lodSelection.resize(trajData.frames.size(), true);
395 trajComp.setChangedTransient();
396 }
397 }
398 }
399 }
400}
ComponentType * getComponent() const
Definition: Component.h:159
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Contains data describing level of detail behavior for the entity the component belongs to.
Definition: LodComponent.h:43
LodPolicy policy
The policy used for determining the level of detail for this entity.
Definition: LodComponent.h:51
float geometricTolerance
Geometric tolerance value applied when the lodPolicy field is set to LodPolicy::GeometricTolerance.
Definition: LodComponent.h:68
static void generateFramesOfReference(std::vector< glm::quat > &orientations, const std::vector< glm::vec3 > &positions)
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
GeometricErrorKind
Defines kinds of geometric error calculations.
Definition: LodComponent.h:27
@ GeometricTolerance
Use a geometric error bound to determine how refined geometry needs to be at its current position.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Defines level of detail data calculated by the LodSystem.
Definition: LodSystem.h:15
Data component defining a 3D trajectory, for example a Well trajectory.
std::vector< glm::quat > frames
Frames of reference along trajectory with minimal torsion.
std::vector< glm::vec4 > clipSpacePos
Clip space position of trajectory stations.
std::vector< bool > lodSelection
Set of trajectory stations that should be included in current lod-situation.