Cogs.Core
VariableExtrusionSystem.cpp
1#include "VariableExtrusionSystem.h"
2#include "Context.h"
3#include "EntityStore.h"
4
5#include "Components/Core/TransformComponent.h"
6#include "Components/Data/TrajectoryComponent.h"
7#include "Components/Appearance/MaterialComponent.h"
8
9#include "Systems/Core/RenderSystem.h"
10
11#include "Utilities/Math.h"
12
13#include "Math/ExtrusionGenerator.h"
14#include "Math/CrossSectionGenerator.h"
15#include "Math/IndexConversion.h"
16#include "Math/NormalGenerator.h"
17
18#include "Services/Variables.h"
19#include "Services/TaskManager.h"
20
21#include "Foundation/ComponentModel/Entity.h"
22#include "Foundation/Geometry/Glm.hpp"
23#include "Foundation/Geometry/PathGenerator.hpp"
24
25using namespace Cogs::Geometry;
26
28{
29 const bool workParallel = context->engine->workParallel();
30
31 for (const auto & component : pool) {
32 if (!component.isActive()) continue;;
33 if (!component.hasChanged() && (!component.trajectory || !component.trajectory->getComponent<TrajectoryComponent>()->hasChanged())) continue;
34
35 auto & componentData = getData(&component);
36
37 if (!component.closed && !componentData.sideShape) {
38 componentData.sideShape = context->store->createChildEntity("Default", component.getContainer());
39
40 componentData.sideShape->getComponent<MaterialComponent>()->material = context->store->getEntity(component.getContainer()->getId());
41 }
42
43 auto updateComponent = [&, context]()
44 {
45 auto & data = getData(&component);
46
47 updatePath(context, component);
48
49 if (data.pathLength < 2) {
50 return;
51 }
52
53 const auto materialComponent = component.getComponent<MaterialComponent>();
54
55 auto masterMaterial = materialComponent->material.lock();
56
57 data.useTexture = HandleIsValid(materialComponent->diffuseMap) || (masterMaterial && HandleIsValid(masterMaterial->getComponent<MaterialComponent>()->diffuseMap));
58
59 data.primaryAxis = glm::vec3(0, 0, -1);
60
61 updateSegmentCount(context, component);
62 //updateWinding(context, component); // missing implementation
63 updateRotations(context, component);
64 updateCrossSections(context, component);
65 updateOffsets(context, component);
66 updateTransformation(context, component);
67 offsetCrossSections(context, component);
68 updateTextureCoordinates(context, component);
69 updateIndexes(context, component);
70 updateSideShape(context, component);
71 updateNormals(context, component);
72 };
73
74 if (workParallel) {
75 context->taskManager->enqueueChild(context->renderSystem->getTaskGroup(), updateComponent);
76 } else {
77 updateComponent();
78 }
79 }
80}
81
82void Cogs::Core::VariableExtrusionSystem::updatePath(Context * /*context*/, const VariableExtrusionComponent & component)
83{
84 auto & data = getData(&component);
85
86 data.positions.clear();
87 data.directions.clear();
88 data.pathLength = 0;
89 data.numSegments = 0;
90
91 if (!component.trajectory) return;
92
93 auto trajectoryComponent = component.trajectory->getComponent<TrajectoryComponent>();
94
95 data.positions.resize(component.depths.size());
96 data.directions.resize(component.depths.size());
97
98 if (component.useOffset) {
99 data.offsetDepths.clear();
100 data.offsetDepths.resize(component.depths.size());
101
102 for (size_t i = 0; i < component.depths.size(); ++i) {
103 data.offsetDepths[i] = component.depths[i] + component.depthOffset;
104 }
105
106 Geometry::PathGenerator::generateLinearPath(data.offsetDepths.data(),
107 data.offsetDepths.size(),
108 trajectoryComponent->indexes.data(),
109 trajectoryComponent->positions.data(),
110 static_cast<int>(trajectoryComponent->positions.size()),
111 data.positions.data(),
112 data.directions.data());
113 } else {
114 Geometry::PathGenerator::generateLinearPath(component.depths.data(),
115 component.depths.size(),
116 trajectoryComponent->indexes.data(),
117 trajectoryComponent->positions.data(),
118 static_cast<int>(trajectoryComponent->positions.size()),
119 data.positions.data(),
120 data.directions.data());
121 }
122
123 data.pathLength = data.positions.size();
124}
125
126void Cogs::Core::VariableExtrusionSystem::updateRotations(Context * /*context*/, const VariableExtrusionComponent & component)
127{
128 auto & data = getData(&component);
129
130 data.rotations.resize(data.directions.size());
131
132 glm::vec3 primaryAxis = glm::vec3(0, 0, -1);
133
134 for (size_t i = 0; i < data.directions.size(); ++i) {
135 data.rotations[i] = getRotation(primaryAxis, data.directions[i]);
136 }
137
138 if (component.useTwist && component.twist.size() == component.profile.size()) {
139 float angle = 0;
140
141 if (component.enableRotation && !component.useSimpleRotation) {
142 angle = component.angle;
143 }
144
145 for (size_t i = 0; i < data.rotations.size(); ++i) {
146 data.rotations[i] = glm::angleAxis(component.twist[i] + angle, data.directions[i]) * data.rotations[i];
147 }
148 } else {
149 for (size_t i = 0; i < data.rotations.size(); ++i) {
150 data.rotations[i] = glm::angleAxis(component.angle, data.directions[i]) * data.rotations[i];
151 }
152 }
153}
154
155void Cogs::Core::VariableExtrusionSystem::updateCrossSections(Context * context, const VariableExtrusionComponent & component)
156{
157 auto & data = getData(&component);
158 auto mesh = getMesh(context, component);
159 auto positionData = mesh->mapPositions(0, data.numSegments * data.positions.size());
160
161 if (component.useMorph && component.morph.size() == component.profile.size()) {
162 if (data.useTexture) {
163 data.crossSection.assign(component.crossSection.begin(), component.crossSection.end());
164 data.morphCrossSection.assign(component.morphCrossSection.begin(), component.morphCrossSection.end());
165
166 data.crossSection.push_back(data.crossSection[0]);
167 data.morphCrossSection.push_back(data.morphCrossSection[0]);
168
170 static_cast<int>(data.positions.size()),
171 data.crossSection.data(),
172 data.morphCrossSection.data(),
173 component.morph.data(),
174 static_cast<int>(data.numSegments),
175 data.rotations.data());
176 } else {
178 static_cast<int>(data.positions.size()),
179 component.crossSection.data(),
180 component.morphCrossSection.data(),
181 component.morph.data(),
182 static_cast<int>(data.numSegments),
183 data.rotations.data());
184 }
185 } else {
186 std::vector<glm::vec3> crossSection(data.numSegments, glm::vec3(0, 0, 0));
187 CrossSectionGenerator::generateCircularCrossection(crossSection.data(),
188 static_cast<int>(data.numSegments),
189 1.0f,
190 0,
191 component.closed ? glm::pi<float>() * 2.0f : glm::pi<float>(),
192 !component.closed || data.useTexture);
193
195 static_cast<int>(data.positions.size()),
196 crossSection.data(),
197 static_cast<int>(data.numSegments),
198 data.rotations.data());
199 }
200
201 if (component.radiusScale != 1.0f) {
202 ExtrusionGenerator::modulateSections(positionData.data,
203 static_cast<int>(data.positions.size()),
204 static_cast<int>(data.numSegments),
205 &component.radiusScale,
206 0);
207 }
208
209 ExtrusionGenerator::modulateSections(positionData.data,
210 static_cast<int>(data.positions.size()),
211 static_cast<int>(data.numSegments),
212 reinterpret_cast<const float *>(component.profile.data()),
213 2);
214}
215
216void Cogs::Core::VariableExtrusionSystem::updateOffsets(Context * context, const VariableExtrusionComponent & component)
217{
218 const auto & data = getData(&component);
219 auto mesh = getMesh(context, component);
220
221 auto positionData = mesh->mapPositions(0, data.numSegments * data.positions.size());
222
223 std::vector<glm::vec3> offsets(data.pathLength, glm::vec3(0, 0, 0));
224
225 for (size_t i = 0; i < offsets.size(); ++i) {
226 const float offset = component.profile[i][1];
227
228 if (offset != 0) {
229 // We only bother calculating the offset if it is not zero.
230 offsets[i] = data.rotations[i] * data.primaryAxis;
231
232 offsets[i] *= offset;
233 }
234 }
235
237 static_cast<int>(data.pathLength),
238 static_cast<int>(data.numSegments),
239 offsets.data());
240}
241
242void Cogs::Core::VariableExtrusionSystem::updateTransformation(Context * /*context*/, const VariableExtrusionComponent & component)
243{
244 auto & data = getData(&component);
245 auto transformComponent = component.getComponent<TransformComponent>();
246
247 if (component.enableRotation && component.useSimpleRotation) {
248 transformComponent->position = data.positions.front();
249
250 auto offset = transformComponent->position;
251
252 for (size_t i = 0; i < data.pathLength; ++i) {
253 data.positions[i] -= offset;
254 }
255 } else {
256 transformComponent->position = glm::vec3(0, 0, 0);
257 }
258
259 transformComponent->setChanged();
260}
261
262void Cogs::Core::VariableExtrusionSystem::offsetCrossSections(Context * context, const VariableExtrusionComponent & component)
263{
264 const auto & data = getData(&component);
265 auto mesh = getMesh(context, component);
266
267 auto positionData = mesh->mapPositions(0, data.numSegments * data.positions.size());
268
270 static_cast<int>(data.pathLength),
271 static_cast<int>(data.numSegments),
272 data.positions.data());
273}
274
275void Cogs::Core::VariableExtrusionSystem::updateTextureCoordinates(Context * /*context*/, const VariableExtrusionComponent & component)
276{
277 auto & data = getData(&component);
278
279 const size_t numTextureCoordinates = data.pathLength * data.numSegments;
280
281 data.textureCoordinates.resize(numTextureCoordinates);
282
283 ExtrusionGenerator::generateDepthBasedTextureCoordinates(data.pathLength,
284 data.numSegments,
285 component.depths.data(),
286 reinterpret_cast<const float *>(component.profile.data()) + 1,
287 2,
288 data.textureCoordinates.data());
289
290 const glm::vec2 & textureScale = component.textureScale;
291 const glm::vec2 & textureOffset = component.textureOffset;
292
293 if (textureOffset != glm::vec2(0, 0)) {
294 for (size_t i = 0; i < numTextureCoordinates; ++i) {
295 data.textureCoordinates[i][0] += textureOffset[0];
296 data.textureCoordinates[i][1] += textureOffset[1];
297 }
298 }
299
300 if (textureScale != glm::vec2(1, 1)) {
301 for (size_t i = 0; i < numTextureCoordinates; ++i) {
302 data.textureCoordinates[i][0] *= textureScale[0];
303 data.textureCoordinates[i][1] *= textureScale[1];
304 }
305 }
306}
307
308void Cogs::Core::VariableExtrusionSystem::updateIndexes(Context * /*context*/, const VariableExtrusionComponent & component)
309{
310 auto & data = getData(&component);
311
312 // If the shape is open or textured we need one more segment than the closed/untextured case.
313 const size_t numIndexes = (data.pathLength - 1) * (data.numSegments - ((component.closed && !data.useTexture) ? 0 : 1)) * 8;
314
315 data.indexes.resize(numIndexes);
316
317 ExtrusionGenerator::generateTriangleIndices(data.indexes.data(), static_cast<int>(data.pathLength), static_cast<int>(data.numSegments), component.closed, data.useTexture);
318}
319
320void Cogs::Core::VariableExtrusionSystem::updateSideShape(Context * context, const VariableExtrusionComponent & component)
321{
322 auto & data = getData(&component);
323
324
325
326 if (component.closed) {
327 if (data.sideShape) {
328 auto meshComponent = data.sideShape->getComponent<MeshComponent>();
329
330 if (meshComponent->meshHandle) {
331 meshComponent->getMesh(context)->clear();
332 }
333 }
334 } else {
335 auto meshComponent = data.sideShape->getComponent<MeshComponent>();
336
337 if (!HandleIsValid(meshComponent->meshHandle)) {
338 meshComponent->meshHandle = context->meshManager->create();
339 }
340
341 auto regularMesh = getMesh(context, component);
342 auto mesh = meshComponent->meshHandle.resolve();
343
344 mesh->clear();
345
346 const size_t pathLength = data.pathLength;
347 const int32_t numSegments = static_cast<int32_t>(data.numSegments);
348
349 auto positions = regularMesh->mapPositions(0, regularMesh->getCount());
350
351 std::vector<int32_t> indexes((data.pathLength + 2) * 2);
352
353 for (size_t i = 0; i < data.pathLength; ++i) {
354 indexes[i] = static_cast<int32_t>(i) * numSegments;
355 indexes[i + pathLength + 2_uz] = static_cast<int32_t>(i) * numSegments + numSegments - 1;
356 }
357
358 indexes[pathLength] = 0;
359 indexes[pathLength + 1_uz] = -1;
360
361 indexes[pathLength + pathLength + 2_uz] = numSegments - 1;
362 indexes[pathLength + pathLength + 3_uz] = -1;
363
364 std::vector<glm::vec3> newPositions;
365 std::vector<glm::vec3> normals;
366 std::vector<int32_t> newIndexes;
367 std::vector<glm::vec2> texCoords;
368
369 {
370 auto tessPositions = mesh->mapPositions(0, indexes.size() - 2);
371
372 // Copy the points needed for side polygons into the side shape mesh and
373 // simultaneously reconstruct the index array relative to this mesh.
374 size_t currentIdx = 0;
375 size_t currentIIdx = 0;
376 while (currentIIdx < indexes.size()) {
377 tessPositions[currentIdx] = positions[indexes[currentIIdx]];
378
379 indexes[currentIIdx++] = static_cast<int32_t>(currentIdx++);
380
381 if (indexes[currentIIdx] == -1) ++currentIIdx;
382 }
383
384 // Fetch updated tessellated triangle indexes.
385 auto tesselatedIndexes = tesselatePolygons(indexes, mesh);
386
387 Cogs::Geometry::generateFacetNormals(
388 tessPositions.data,
389 mesh->getCount(),
390 tesselatedIndexes.data(),
391 tesselatedIndexes.size(),
392 data.textureCoordinates.data(),
393 false,
394 true,
395 true,
396 1.0f,
397 false,
398 0,
399 newPositions,
400 normals,
401 newIndexes,
402 &texCoords);
403 }
404
405 mesh->setPositions(std::move(newPositions));
406 mesh->setNormals(std::move(normals));
407
408 if (texCoords.size()) {
409 mesh->setTexCoords(std::move(texCoords));
410 }
411
412 reindex(newIndexes, mesh, false);
413 }
414}
415
416void Cogs::Core::VariableExtrusionSystem::updateNormals(Context * context, const VariableExtrusionComponent & component)
417{
418 auto & data = getData(&component);
419 auto mesh = getMesh(context, component);
420 const size_t numElements = data.numSegments * data.positions.size();
421 auto positionData = mesh->mapPositions(0, numElements);
422
423 std::vector<glm::vec3> positions;
424 std::vector<glm::vec3> normals;
425 std::vector<int32_t> indexes;
426 std::vector<glm::vec2> texCoords;
427
428 Cogs::Geometry::generateFacetNormals(
429 positionData.data,
430 numElements,
431 data.indexes.data(),
432 data.indexes.size(),
433 data.textureCoordinates.data(),
434 false,
435 true,
436 false,
437 component.creaseAngle,
438 data.useTexture,
439 data.numSegments,
440 positions,
441 normals,
442 indexes,
443 &texCoords);
444
445 mesh->setPositions(std::move(positions));
446 mesh->setNormals(std::move(normals));
447
448 if (texCoords.size()) {
449 mesh->setTexCoords(std::move(texCoords));
450 }
451
452 reindex(indexes, mesh, false);
453}
454
455Cogs::Core::Mesh * Cogs::Core::VariableExtrusionSystem::getMesh(Context * context, const VariableExtrusionComponent & component)
456{
457 auto meshComponent = component.getComponent<MeshComponent>();
458
459 if (!HandleIsValid(meshComponent->meshHandle)) {
460 meshComponent->meshHandle = context->meshManager->create();
461 }
462
463 return meshComponent->meshHandle.resolve();
464}
465
466void Cogs::Core::VariableExtrusionSystem::updateSegmentCount(Context * /*context*/, const VariableExtrusionComponent & component)
467{
468 auto & data = getData(&component);
469
470 const bool useTexture = data.useTexture;
471
472 // If we need texturing, we must add an extra segment to the cross section to avoid the texture wrapping
473 // back to zero from the last to the first segment.
474 int textureExtra = useTexture ? 1 : 0;
475
476 if (component.useMorph && component.morph.size() == component.profile.size()) {
477 // When morphing we just use the number of elements in the given cross section.
478 data.numSegments = component.crossSection.size() + textureExtra;
479 } else {
480 data.numSegments = component.closed ? component.segmentCount : component.segmentCount / 2 + 1;
481
482 data.numSegments += textureExtra;
483 }
484}
ComponentType * getComponent() const
Definition: Component.h:159
Context * context
Pointer to the Context instance the system lives in.
void update()
Updates the system state to that of the current frame.
ComponentPool< ComponentType > pool
Pool of components managed by the system.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class EntityStore * store
Entity store.
Definition: Context.h:231
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
EntityPtr getEntity(const StringView &name, bool logIfNotFound=true) const
Retrieve a reference to the shared entity pointer to the Entity with the given name.
EntityPtr createChildEntity(const StringView &type, ComponentModel::Entity *parent, const StringView &name=StringView())
Create a new Entity, parenting it to the given parent.
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
Contains geometry calculations and generation.
Exposes material properties for legacy entities and code.
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
Data component defining a 3D trajectory, for example a Well trajectory.
float depthOffset
Offset amount applied to the given profile depths before calculating the trajectory.
std::vector< float > depths
Depth values for the profile.
std::shared_ptr< ComponentModel::Entity > trajectory
The trajectory this component is connected to.
static void offsetCrossSections(glm::vec3 *vertices, int numSections, int numSegments, const glm::vec3 *positions)
Offset vertices with per section positions.
static bool generateCrossSections(glm::vec3 *vertices, int numSections, const glm::vec3 *crossSectionElements, int numSegments, const glm::quat *rotations)
Generates cross section vertices in vertices from the cross section definition given.
static void modulateSections(glm::vec3 *vertices, const int numSections, const int numSegments, const float *values, const size_t stride=1)
Modulate extrusion vertices with per section data.
static void generateTriangleIndices(int32_t *indices, int numSections, int numSegments, bool closed, bool useTexture, int32_t offset=0)
Generate legacy triangle extrusion indices.