Cogs.Core
MultiphaseFlowComponent.cpp
1#define _USE_MATH_DEFINES
2
3#include <cmath>
4#include <sstream>
5#include "Types.h"
6#include "Context.h"
7#include "EntityStore.h"
8
9#include "Utilities/Math.h"
10#include "Systems/Core/CameraSystem.h"
11#include "Systems/Core/MeshSystem.h"
12#include "Math/CrossSectionGenerator.h"
13
14#include "Resources/Mesh.h"
15#include "Resources/MeshManager.h"
16#include "Resources/MaterialManager.h"
17#include "Resources/FontManager.h"
18#include "Resources/DefaultMaterial.h"
19
20#include "Components/Core/MeshRenderComponent.h"
21#include "Components/Core/TextComponent.h"
22#include "Components/Core/SceneComponent.h"
23#include "Components/Core/MeshRenderComponent.h"
24#include "Components/Core/TransformComponent.h"
25#include "Components/Core/SubMeshRenderComponent.h"
26#include "Components/Appearance/MaterialComponent.h"
27
28#include "MultiphaseFlowComponent.h"
29
30#include <glm/gtx/compatibility.hpp>
31
32using namespace Cogs::Geometry;
33using namespace Cogs::Reflection;
34using namespace Cogs::Core;
35
36static const int NUM_OF_SEGMENTS = 24;
37static const int NUM_OF_SEGMENTS_SHADOW = 12;
38static std::vector<glm::vec3> CROSS_SECTION;
39static std::vector<glm::vec3> CROSS_SECTION_SHADOW;
40
41static const glm::vec3 WATER_COLOR_DEFAULT = glm::vec3(54, 121, 159) / 255.f;
42static const glm::vec3 OIL_COLOR_DEFAULT = glm::vec3(235, 201, 77) / 255.f;
43static const glm::vec3 GAS_COLOR_DEFAULT = glm::vec3(195, 195, 195) / 255.f;
44static const glm::vec3 SHADOW_COLOR = glm::vec3(0.3f, 0.3f, 0.3f);
45static const glm::vec3 INDICATOR_RING_COLOR = glm::vec3(143, 14, 14) / 255.f;
46
47static const float ANNULAR_FLOW_INCLINATION = 0.6f;
48static const float GAS_STRATIFIED_TRANSPARENCY = 0.5f;
49
50static const int GAS_STRATIFIED_TRANSITION_RANGE = 20;
51
52MultiphaseFlowComponent::MultiphaseFlowComponent() :
53 waterColor(WATER_COLOR_DEFAULT),
54 oilColor(OIL_COLOR_DEFAULT),
55 gasColor(GAS_COLOR_DEFAULT),
56 indicatorRingColor(INDICATOR_RING_COLOR)
57{
58 if (CROSS_SECTION.empty())
59 {
60 CROSS_SECTION.resize(NUM_OF_SEGMENTS);
61 CrossSectionGenerator::generateCircularCrossection(CROSS_SECTION.data(), static_cast<int>(NUM_OF_SEGMENTS), 1.f, 0, glm::pi<float>() * 2.0f, false);
62 }
63
64 if (CROSS_SECTION_SHADOW.empty())
65 {
66 CROSS_SECTION_SHADOW.resize(NUM_OF_SEGMENTS_SHADOW);
67 CrossSectionGenerator::generateCircularCrossection(CROSS_SECTION_SHADOW.data(), static_cast<int>(NUM_OF_SEGMENTS_SHADOW), 1.f, 0, glm::pi<float>() * 2.0f, false);
68 }
69}
70
71void MultiphaseFlowComponent::registerType()
72{
73 Field fields[] = {
74 Field(Name("trajectoryPoints"), &MultiphaseFlowComponent::trajectoryPoints),
75 Field(Name("radius"), &MultiphaseFlowComponent::radius),
76 Field(Name("flowFraction"), &MultiphaseFlowComponent::flowFraction),
77 Field(Name("wallValues"), &MultiphaseFlowComponent::wallValues),
78 Field(Name("heightScale"), &MultiphaseFlowComponent::heightScale),
79 Field(Name("radiusScale"), &MultiphaseFlowComponent::radiusScale),
80 Field(Name("showWater"), &MultiphaseFlowComponent::showWater),
81 Field(Name("showOil"), &MultiphaseFlowComponent::showOil),
82 Field(Name("showGas"), &MultiphaseFlowComponent::showGas),
83 Field(Name("unitScale"), &MultiphaseFlowComponent::unitScale),
84 Field(Name("unitText"), &MultiphaseFlowComponent::unitText),
85 Field(Name("updateNeeded"), &MultiphaseFlowComponent::updateNeeded),
86 Field(Name("waterColor"), &MultiphaseFlowComponent::waterColor),
87 Field(Name("oilColor"), &MultiphaseFlowComponent::oilColor),
88 Field(Name("gasColor"), &MultiphaseFlowComponent::gasColor),
89 Field(Name("fontSize"), &MultiphaseFlowComponent::fontSize),
90 Field(Name("showAxialExtents"), &MultiphaseFlowComponent::showAxialExtents),
91 Field(Name("showShadow"), &MultiphaseFlowComponent::showShadow),
92 Field(Name("showGrid"), &MultiphaseFlowComponent::showGrid),
93 Field(Name("centerMesh"), &MultiphaseFlowComponent::centerMesh),
94 Field(Name("showIndicatorRing"), &MultiphaseFlowComponent::showIndicatorRing),
95 Field(Name("indicatorRingPosition"), &MultiphaseFlowComponent::indicatorRingPosition)
96 };
97
98 Method methods[] = {
99 Method(Name("initialize"), &MultiphaseFlowComponent::initialize),
100 Method(Name("update"), &MultiphaseFlowComponent::update),
101 };
102
103 DynamicComponent::registerDerivedType<MultiphaseFlowComponent>().setFields(fields).setMethods(methods);
104}
105
106bool MultiphaseFlowComponent::isStratified(MeshType liquidType)
107{
108 assert(liquidType != MeshType::Wall && liquidType != MeshType::Shadow);
109
110 return (liquidType == MeshType::StratifiedWater ||
111 liquidType == MeshType::StratifiedOil ||
112 liquidType == MeshType::StratifiedGas);
113}
114
115bool MultiphaseFlowComponent::isAnnular(MeshType liquidType)
116{
117 assert(liquidType != MeshType::Wall && liquidType != MeshType::Shadow);
118
119 return !isStratified(liquidType);
120}
121
122unsigned int MultiphaseFlowComponent::getNumOfSegmentsInCrossSection(MeshType liquidType)
123{
124 return liquidType != MeshType::Shadow ? NUM_OF_SEGMENTS : NUM_OF_SEGMENTS_SHADOW;
125}
126
127void MultiphaseFlowComponent::createMeshEntity(Cogs::Core::EntityPtr& mesh, MeshType liquidType)
128{
129 mesh = context->store->createChildEntity("MeshPart", getContainer());
130
131 MeshComponent* meshComp = mesh->getComponent<MeshComponent>();
132 if (meshComp->meshHandle == MeshHandle::NoHandle)
133 {
134 meshComp->meshHandle = context->meshManager->create();
135 }
136
137 auto materialInstance = context->materialInstanceManager->createMaterialInstance(MaterialHandle(mainMaterial));
138 auto meshRenderComp = mesh->getComponent<MeshRenderComponent>();
139 meshRenderComp->material = materialInstance;
140
141 updateMaterial(mesh, liquidType);
142}
143
144void MultiphaseFlowComponent::updateMaterial(Cogs::Core::EntityPtr mesh, MeshType liquidType)
145{
146 // Set default cull mode.
147 auto meshRenderComp = mesh->getComponent<MeshRenderComponent>();
148 auto material = meshRenderComp->material;
149 material->setOption("CullMode", "Front");
150
151 // Set material color and transparency based on the liquid type.
152 glm::vec3 diffuseColor;
153 switch (liquidType)
154 {
155 case MeshType::StratifiedWater:
156 diffuseColor = waterColor;
157 material->setOption("CullMode", "Back");
158 material->setOption("DepthBiasEnabled", "true");
159 material->setOption("DepthBiasConstant", "0.5f");
160 material->setOption("DepthBiasSlope", "0.5f");
161 break;
162 case MeshType::AnnularWater:
163 diffuseColor = waterColor;
164 material->setOption("CullMode", "Back");
165 break;
166 case MeshType::StratifiedOil:
167 diffuseColor = oilColor;
168 material->setOption("CullMode", "None");
169 break;
170 case MeshType::AnnularOil:
171 diffuseColor = oilColor;
172 material->setOption("Transparency", "On");
173 break;
174 case MeshType::StratifiedGas:
175 diffuseColor = gasColor;
176 material->setOption("Transparency", "On");
177 break;
178 case MeshType::AnnularGas:
179 diffuseColor = gasColor;
180 break;
181 case MeshType::Wall:
182 diffuseColor = glm::vec3(1.f);
183 material->setVariant("EnablePerVertexColor", true);
184 break;
185 case MeshType::Shadow:
186 diffuseColor = SHADOW_COLOR;
187 material->setVariant("EnableLighting", false);
188 material->setVariant("LightModel", "BaseColor");
189 material->setOption("CullMode", "None");
190 break;
191 default:
192 assert(false && "Unknown liquid type");
193 break;
194 }
195
196 // Set material ambient and specular color.
197 material->setVec3Property(diffuseColorMaterialPropertyKey, diffuseColor);
198 material->setVec3Property(emissiveColorMaterialPropertyKey, diffuseColor * (liquidType != MeshType::Wall ? 0.3f : 0.05f));
199 material->setVec3Property(specularColorMaterialPropertyKey, liquidType != MeshType::Shadow ? diffuseColor : glm::vec3(0, 0, 0));
200}
201
202void MultiphaseFlowComponent::initialize(Context * ctx)
203{
204 context = ctx;
205
206 // Create material.
207 auto materialHandle = context->materialManager->loadMaterial("MultiphaseFlowMaterial.material");
208 materialHandle->setMaterialFlag(MaterialFlags::Default);
209 context->materialManager->processLoading();
210 materialHandle->setChanged();
211 mainMaterial = materialHandle.resolve();
212
213 diffuseColorMaterialPropertyKey = materialHandle->getVec3Key("diffuseColor");
214 emissiveColorMaterialPropertyKey = materialHandle->getVec3Key("emissiveColor");
215 specularColorMaterialPropertyKey = materialHandle->getVec3Key("specularColor");
216
217 // Initialize meshes for all liquids.
218 for (int i = MeshType::First; i <= MeshType::Last; i++)
219 {
220 auto liquidType = MeshType(i);
221 createMeshEntity(meshEntity[i], liquidType);
222 }
223
224 // Create ground mesh entity.
225 createGridEntity();
226
227 // Create indicator ring.
228 createIndicatorRing();
229
230 // Create text entity.
231 createTextEntity();
232}
233
234static void SetLineMaterial(Context* context, EntityPtr entity, const glm::vec3& color, float lineWidth = 1.f)
235{
236 auto material = context->materialInstanceManager->createMaterialInstance(context->materialManager->getDefaultMaterial());
237 material->setPermutation("Line");
238 material->setVariant("EnableLighting", false);
239 material->setBoolProperty(DefaultMaterial::EnableLighting, false);
240 material->setFloatProperty(DefaultMaterial::LineWidth, lineWidth);
241 material->setVec4Property(DefaultMaterial::DiffuseColor, glm::vec4(color, 1));
242
243 auto meshRenderComp = entity->getComponent<MeshRenderComponent>();
244 meshRenderComp->material = material;
245}
246
247// Create ground mesh entity.
248void MultiphaseFlowComponent::createGridEntity()
249{
250 groundGridMeshEntity = context->store->createChildEntity("MeshPart", getContainer());
251 auto* meshComp = groundGridMeshEntity->getComponent<MeshComponent>();
252 if (meshComp->meshHandle == MeshHandle::NoHandle)
253 {
254 meshComp->meshHandle = context->meshManager->create();
255 }
256 SetLineMaterial(context, groundGridMeshEntity, glm::vec3(0.6f, 0.6f, 0.6f));
257
258 annotationAxisMeshEntity = context->store->createChildEntity("MeshPart", getContainer());
259 auto* annotationMeshComp = annotationAxisMeshEntity->getComponent<MeshComponent>();
260 if (annotationMeshComp->meshHandle == MeshHandle::NoHandle)
261 {
262 annotationMeshComp->meshHandle = context->meshManager->create();
263 }
264 SetLineMaterial(context, annotationAxisMeshEntity, glm::vec3(0.2f, 0.2f, 0.2f));
265}
266
267void MultiphaseFlowComponent::createIndicatorRing()
268{
269 indicatorRingEntity = context->store->createChildEntity("MeshPart", getContainer());
270 auto* meshComp = indicatorRingEntity->getComponent<MeshComponent>();
271 if (meshComp->meshHandle == MeshHandle::NoHandle)
272 {
273 meshComp->meshHandle = context->meshManager->create();
274 }
275
276 SetLineMaterial(context, indicatorRingEntity, indicatorRingColor, 5.f);
277}
278
279// Create text entity.
280void MultiphaseFlowComponent::createTextEntity()
281{
282 for (int i = AnnotationAxis::AxisFirst; i <= AnnotationAxis::AxisLast; i++)
283 {
284 textEntity[i] = context->store->createChildEntity("Text", getContainer());
285 auto* textComp = textEntity[i]->getComponent<TextComponent>();
287 textComp->horizontalJustification = HorizontalJustification::Left;
288 textComp->verticalAlignment = i == AnnotationAxis::X ? VerticalAlignment::Top : VerticalAlignment::Center;
289 }
290
291 assert(currentFontSize != fontSize);
292 updateFontSize();
293}
294
295void MultiphaseFlowComponent::updateFontSize()
296{
297 if (currentFontSize != fontSize)
298 {
299 currentFontSize = fontSize;
300
301 for (int i = AnnotationAxis::AxisFirst; i <= AnnotationAxis::AxisLast; i++)
302 {
303 auto* textComp = textEntity[i]->getComponent<TextComponent>();
304 textComp->fontHandle = context->fontManager->loadFont("Fonts/SourceSansPro-Regular.ttf", currentFontSize, NoResourceId);
305 }
306 }
307}
308
309static float sCurve(float a_fValue, float a_fPower = 1.5f)
310{
311 if (a_fValue < 0.5f)
312 {
313 return (glm::pow(glm::clamp(a_fValue, 0.f, 1.f) * 2, a_fPower)) * 0.5f;
314 }
315 else
316 {
317 return -glm::pow(-(glm::clamp(a_fValue, 0.f, 1.f) - 1) * 2, a_fPower) * 0.5f + 1;
318 }
319}
320
321static glm::vec3 wavelengthToColor(float lambda)
322{
323 // Based on: http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm
324 // The foregoing is based on: http://www.midnightkite.com/color.html
325 struct Color
326 {
327 float c[3];
328 glm::vec3 toColor(float factor) const
329 {
330 float const gamma = 0.8f;
331 float ci[3];
332 for (int i = 0; i < 3; ++i)
333 {
334 ci[i] = c[i] == 0 ? 0 : glm::round(255 * pow(c[i] * factor, gamma));
335 }
336 return glm::vec3(ci[0] / 255.f, ci[1] / 255.f, ci[2] / 255.f);
337 }
338 } color;
339
340 float factor = 0.f;
341
342 color.c[0] = 0.f;
343 color.c[1] = 0.f;
344 color.c[2] = 0.f;
345
346 static float thresholds[] = { 380, 440, 490, 510, 580, 645, 780 };
347
348 for (unsigned int i = 0; i < sizeof(thresholds) / sizeof(thresholds[0]); ++i)
349 {
350 float t1 = thresholds[i], t2 = thresholds[i + 1];
351 if (lambda < t1 || lambda >= t2)
352 {
353 continue;
354 }
355 if (i % 2)
356 {
357 std::swap(t1, t2);
358 }
359 color.c[i % 3] = (i < 5) ? (lambda - t2) / (t1 - t2) : 0.f;
360 color.c[2 - i / 2] = 1.f;
361 factor = 1.f;
362 break;
363 }
364
365 // Let the intensity fall off near the vision limits
366 if (lambda >= 380 && lambda < 420)
367 {
368 factor = 0.3f + 0.7f * (lambda - 380) / (420 - 380);
369 }
370 else if (lambda >= 700 && lambda < 780)
371 {
372 factor = 0.3f + 0.7f * (780 - lambda) / (780 - 700);
373 }
374 return color.toColor(factor);
375}
376
377glm::vec3 MultiphaseFlowComponent::wallValueToColor(const ControlPoint& controlPoint)
378{
379 static const float f1 = 1.f / 435.f;
380 static const float f2 = 1.f / 650.f;
381 float lambda = 1.f / (f1 - controlPoint.wall * (f1 - f2));
382 return wavelengthToColor(lambda);
383}
384
385void MultiphaseFlowComponent::transformCrossection(float annularRatio, float alpha, std::vector<Vertex>& vertexBuffer, const ControlPoint& controlPoint, MeshType liquidType, bool isClosingCap /*= false*/)
386{
387 // Modify radius.
388 float radius = controlPoint.radius;
389 float annularOilTransparency = 1.f;
390 if (liquidType == MeshType::AnnularOil && !isClosingCap)
391 {
392 float radiusAnnular = glm::sqrt(controlPoint.flow.oil + controlPoint.flow.gas);
393 radius *= glm::lerp(1.0f, radiusAnnular, annularRatio);
394
395 annularOilTransparency = glm::lerp(1.f, controlPoint.flow.oil, annularRatio);
396 }
397 else
398 if (liquidType == MeshType::AnnularGas)
399 {
400 float radiusAnnular = glm::sqrt(controlPoint.flow.gas);
401 radius *= glm::lerp(1.0f, radiusAnnular, annularRatio);
402 }
403
404 // Reduce multiphase flow mesh radius when the wall is visible.
405 bool wallVisible = (wallValues.size() == trajectoryPoints.size());
406 if (wallVisible && liquidType != MeshType::Wall && liquidType != MeshType::Shadow)
407 {
408 radius *= 0.8f;
409 }
410
411 // Process all crosssection points
412 const auto& crossSection = (liquidType != MeshType::Shadow ? CROSS_SECTION : CROSS_SECTION_SHADOW);
413 for (size_t j = 0; j < crossSection.size(); ++j)
414 {
415 glm::vec3 positionN = controlPoint.rotation * crossSection[j];
416 glm::vec3 crossectionPointAnnular = crossSection[j];
417
418 // Modify position based on the liquid fraction for stratified flow.
419 glm::vec3 crossectionPointStratified = crossSection[j];
420 glm::vec3 crossectionPointStratifiedInverted = crossSection[j];
421 switch (liquidType)
422 {
423 case MeshType::AnnularWater:
424 break;
425
426 case MeshType::StratifiedWater:
427
428 if (crossectionPointStratified.x < controlPoint.flow.heightWaterOil)
429 {
430 crossectionPointStratified.x = controlPoint.flow.heightWaterOil;
431 crossectionPointStratified.y = crossectionPointStratified.y > 0.f ? controlPoint.flow.widthWaterOil : -controlPoint.flow.widthWaterOil;
432 }
433
434 // Inverted.
435 if (crossectionPointStratifiedInverted.x > -controlPoint.flow.heightWaterOil)
436 {
437 crossectionPointStratifiedInverted.x = -controlPoint.flow.heightWaterOil;
438 crossectionPointStratifiedInverted.y = crossectionPointStratifiedInverted.y > 0.f ? controlPoint.flow.widthWaterOil : -controlPoint.flow.widthWaterOil;
439 }
440 break;
441
442 case MeshType::StratifiedOil:
443 case MeshType::AnnularOil:
444 if (crossectionPointStratified.x < controlPoint.flow.heightOilGas)
445 {
446 crossectionPointStratified.x = controlPoint.flow.heightOilGas;
447 crossectionPointStratified.y = crossectionPointStratified.y > 0.f ? controlPoint.flow.widthOilGas : -controlPoint.flow.widthOilGas;
448 }
449 else
450 if (crossectionPointStratified.x > controlPoint.flow.heightWaterOil)
451 {
452 crossectionPointStratified.x = controlPoint.flow.heightWaterOil;
453 crossectionPointStratified.y = crossectionPointStratified.y > 0.f ? controlPoint.flow.widthWaterOil : -controlPoint.flow.widthWaterOil;
454 }
455
456 // Inverted.
457 if (crossectionPointStratifiedInverted.x > -controlPoint.flow.heightOilGas)
458 {
459 crossectionPointStratifiedInverted.x = -controlPoint.flow.heightOilGas;
460 crossectionPointStratifiedInverted.y = crossectionPointStratifiedInverted.y > 0.f ? controlPoint.flow.widthOilGas : -controlPoint.flow.widthOilGas;
461 }
462 else
463 if (crossectionPointStratifiedInverted.x < -controlPoint.flow.heightWaterOil)
464 {
465 crossectionPointStratifiedInverted.x = -controlPoint.flow.heightWaterOil;
466 crossectionPointStratifiedInverted.y = crossectionPointStratifiedInverted.y > 0.f ? controlPoint.flow.widthWaterOil : -controlPoint.flow.widthWaterOil;
467 }
468 break;
469
470 case MeshType::StratifiedGas:
471 case MeshType::AnnularGas:
472 if (crossectionPointStratified.x > controlPoint.flow.heightOilGas)
473 {
474 crossectionPointStratified.x = controlPoint.flow.heightOilGas;
475 crossectionPointStratified.y = crossectionPointStratified.y > 0.f ? controlPoint.flow.widthOilGas : -controlPoint.flow.widthOilGas;
476 }
477
478 // Inverted.
479 if (crossectionPointStratifiedInverted.x < -controlPoint.flow.heightOilGas)
480 {
481 crossectionPointStratifiedInverted.x = -controlPoint.flow.heightOilGas;
482 crossectionPointStratifiedInverted.y = crossectionPointStratifiedInverted.y > 0.f ? controlPoint.flow.widthOilGas : -controlPoint.flow.widthOilGas;
483 }
484 break;
485
486 case MeshType::Shadow:
487 case MeshType::Wall:
488 break;
489
490 default:
491 break;
492 }
493
494 // Interpolate between inverted and non-inverted cross sections (oil always on the top).
495 if (liquidType == MeshType::StratifiedWater)
496 {
497 // Special treatment for stratified oil - binary interpolation.
498 crossectionPointStratified = (controlPoint.invertedFlowRatio < 0.5f ? crossectionPointStratified : crossectionPointStratifiedInverted);
499 }
500 else
501 if (liquidType != MeshType::Shadow && liquidType != MeshType::Wall)
502 {
503 crossectionPointStratified = glm::lerp(crossectionPointStratified, crossectionPointStratifiedInverted, controlPoint.invertedFlowRatio);
504 }
505
506 // Interpolate between stratified and annular flow.
507 glm::vec3 crossectionPoint = glm::lerp(crossectionPointStratified, crossectionPointAnnular, annularRatio);
508
509 // Calculate rotated position and normal vector.
510 glm::vec3 position = controlPoint.rotation * crossectionPoint;
511
512 // Set radius and apply offset.
513 position *= radius;
514 position += controlPoint.position;
515
516 // Create vertex color.
517 auto transparency = 1.f;
518 switch (liquidType)
519 {
520 case MeshType::StratifiedWater:
521 break;
522 case MeshType::AnnularWater:
523 break;
524 case MeshType::StratifiedOil:
525 break;
526 case MeshType::AnnularOil:
527 transparency = annularOilTransparency;
528 break;
529 case MeshType::StratifiedGas:
530 transparency = alpha;
531 break;
532 case MeshType::AnnularGas:
533 break;
534 case MeshType::Wall:
535 break;
536 case MeshType::Shadow:
537 break;
538 default:
539 assert(false && "Unknown liquid type");
540 break;
541 }
542
543 // Get normal.
544 glm::vec3 normal;
545 if (!isClosingCap)
546 {
547 normal = glm::normalize(positionN);
548 }
549 else
550 {
551 // Normal vector for closing caps based on the the trajectory direction.
552 normal = controlPoint.direction;
553 }
554
555 // Construct final vertex.
556 vertexBuffer.push_back({ position, glm::vec4(-normal, transparency) });
557 }
558}
559
560void MultiphaseFlowComponent::generateExtrusionVertices(std::vector<Vertex>& vertexBuffer, std::vector<unsigned int>& indexBuffer, const std::vector<ControlPoint>& controlPoints, MeshType liquidType)
561{
562 assert(controlPoints.size() >= 2);
563
564 bool isAnnular = MultiphaseFlowComponent::isAnnular(liquidType);
565
566 // Reserve memory for the vertex buffer.
567 assert(vertexBuffer.size() == 0);
568 vertexBuffer.reserve(NUM_OF_SEGMENTS * controlPoints.size() + NUM_OF_SEGMENTS * 4); // Allocate additional memory for closing caps.
569
570 // Create flow ranges.
571 std::vector<std::pair<int, int>> flowRanges;
572 int startRange = -1;
573 for (size_t i = 0; i < controlPoints.size(); i++)
574 {
575 bool isPointAnnularFlow = (controlPoints[i].annularFlowRatio > 0.f);
576 if (isPointAnnularFlow == isAnnular)
577 {
578 if (startRange == -1)
579 {
580 startRange = (int)i;
581 }
582 }
583 else
584 {
585 if (startRange >= 0)
586 {
587 assert(i > 0);
588 flowRanges.push_back(std::pair<int, int>(startRange, (int)i));
589 startRange = -1;
590 }
591 }
592 }
593
594 if (startRange >= 0)
595 {
596 flowRanges.push_back(std::pair<int, int>(startRange, (int)controlPoints.size() - 1));
597 }
598
599 // Generate geometry for all ranges.
600 unsigned int segmentStart = 0;
601 for (const auto& range : flowRanges)
602 {
603 int segmentRange = range.second - range.first;
604 if (segmentRange <= 0)
605 {
606 continue;
607 }
608
609 // Fill up vertex buffer with transformed vertices.
610 for (int i = range.first; i <= range.second; i++)
611 {
612 const auto& controlPoint = controlPoints[i];
613 float annularRatio = controlPoint.annularFlowRatio;
614 float alpha = 1.f;
615
616 if ((i == range.first || i == range.second) && i > 0 && i < static_cast<int>(controlPoints.size()) - 1)
617 {
618 annularRatio = 0.f;
619 }
620
621 // Special transition for stratified gas.
622 if (liquidType == MeshType::StratifiedGas)
623 {
624 float ratioNext = 0;
625 for (int j = 0; j <= GAS_STRATIFIED_TRANSITION_RANGE; j++)
626 {
627 int nextIndex = i + j;
628 if (nextIndex >= static_cast<int>(controlPoints.size()))
629 {
630 break;
631 }
632 const auto& nextCtrlPoint = controlPoints[nextIndex];
633
634 if (nextCtrlPoint.annularFlowRatio > 0.f)
635 {
636 ratioNext = 1.f - static_cast<float>(j) / static_cast<float>(GAS_STRATIFIED_TRANSITION_RANGE);
637 break;
638 }
639 }
640
641 float ratioPrev = 0;
642 for (int j = 0; j <= GAS_STRATIFIED_TRANSITION_RANGE; j++)
643 {
644 int prevIndex = i - j;
645 if (prevIndex < 0)
646 {
647 break;
648 }
649 const auto& nextCtrlPoint = controlPoints[prevIndex];
650
651 if (nextCtrlPoint.annularFlowRatio > 0.f)
652 {
653 ratioPrev = 1.f - static_cast<float>(j) / static_cast<float>(GAS_STRATIFIED_TRANSITION_RANGE);
654 break;
655 }
656 }
657
658 alpha = std::max(ratioNext, ratioPrev);
659 alpha = GAS_STRATIFIED_TRANSPARENCY + alpha * (1.f - GAS_STRATIFIED_TRANSPARENCY);
660 }
661
662 transformCrossection(annularRatio, alpha, vertexBuffer, controlPoint, liquidType);
663 }
664
665 assert(segmentRange > 0);
666 unsigned int segmentEnd = segmentStart + segmentRange;
667 assert(segmentStart < segmentEnd && segmentEnd);
668
669 // Generate index buffer.
670 for (unsigned int offset = segmentStart; offset <= segmentEnd - 1; offset++)
671 {
672 const auto numOfSegments = getNumOfSegmentsInCrossSection(liquidType);
673 for (unsigned int j = 0; j < numOfSegments - 1; j++)
674 {
675 indexBuffer.push_back((offset * numOfSegments) + j);
676 indexBuffer.push_back((offset * numOfSegments) + j + 1);
677 indexBuffer.push_back(((offset + 1) * numOfSegments) + j + 1);
678
679 indexBuffer.push_back((offset * numOfSegments) + j);
680 indexBuffer.push_back(((offset + 1) * numOfSegments) + j + 1);
681 indexBuffer.push_back(((offset + 1) * numOfSegments) + j);
682 }
683
684 indexBuffer.push_back((offset * numOfSegments) + numOfSegments - 1);
685 indexBuffer.push_back((offset * numOfSegments));
686 indexBuffer.push_back(((offset + 1) * numOfSegments));
687
688 indexBuffer.push_back((offset * numOfSegments) + numOfSegments - 1);
689 indexBuffer.push_back(((offset + 1) * numOfSegments));
690 indexBuffer.push_back(((offset + 1) * numOfSegments) + numOfSegments - 1);
691 }
692
693 segmentStart = segmentEnd + 1;
694 }
695
696 // Closing caps for solid meshes (stratified oil and annular gas).
697 if (liquidType == MeshType::StratifiedOil ||
698 liquidType == MeshType::AnnularGas)
699 {
700 // For annular gas flow crate closing caps at bot ends of every range.
701 for (const auto& range : flowRanges)
702 {
703 int segmentRange = range.second - range.first;
704 if (segmentRange <= 0)
705 {
706 continue;
707 }
708
709 if (range.first > 0)
710 {
711 generateClosingCaps(vertexBuffer, indexBuffer, controlPoints[range.first], liquidType);
712 }
713
714 if (range.second < static_cast<int>(controlPoints.size()) - 1)
715 {
716 generateClosingCaps(vertexBuffer, indexBuffer, controlPoints[range.second], liquidType);
717 }
718 }
719 }
720
721 // Create closing caps at the both ends of the flow.
722 if (!flowRanges.empty() && liquidType != MeshType::Wall && liquidType != MeshType::Shadow)
723 {
724 if (flowRanges[0].first == 0)
725 {
726 generateClosingCaps(vertexBuffer, indexBuffer, controlPoints[0], liquidType);
727 }
728
729 if (flowRanges[flowRanges.size() - 1].second == static_cast<int>(controlPoints.size()) - 1)
730 {
731 generateClosingCaps(vertexBuffer, indexBuffer, controlPoints[controlPoints.size() - 1], liquidType);
732 }
733 }
734}
735
736void MultiphaseFlowComponent::generateClosingCaps(std::vector<Vertex>& vertexBuffer, std::vector<unsigned int>& indexBuffer, const ControlPoint& controlPoint, MeshType liquidType)
737{
738 float ratio = (controlPoint.annularFlowRatio < 0.5f ? 0.f : 1.f);
739 float alpha = 1.f;
740
741 if (liquidType == MeshType::StratifiedGas)
742 {
743 alpha = GAS_STRATIFIED_TRANSPARENCY;
744 }
745
746 if (liquidType == MeshType::Wall || isStratified(liquidType) || liquidType == MeshType::AnnularGas)
747 {
748 for (int i = 0; i < 2; i++)
749 {
750 ControlPoint ctrlPoint = controlPoint;
751
752 if (i == 0)
753 {
754 ctrlPoint.direction *= -1;
755 }
756
757 const auto vertexBufferOffset = static_cast<unsigned int>(vertexBuffer.size());
758
759 // Generate vertex buffer for closing caps.
760 transformCrossection(ratio, alpha, vertexBuffer, ctrlPoint, liquidType, true);
761
762 const auto numOfSegments = getNumOfSegmentsInCrossSection(liquidType);
763 for (unsigned int j = 0; j < numOfSegments - 2; ++j)
764 {
765 indexBuffer.push_back(vertexBufferOffset);
766 if (i == 1)
767 {
768 indexBuffer.push_back(vertexBufferOffset + j + 2);
769 indexBuffer.push_back(vertexBufferOffset + j + 1);
770 }
771 else
772 {
773 indexBuffer.push_back(vertexBufferOffset + j + 1);
774 indexBuffer.push_back(vertexBufferOffset + j + 2);
775 }
776 }
777 }
778 }
779 else
780 if (liquidType == MeshType::AnnularWater || liquidType == MeshType::AnnularOil)
781 {
782 assert(isAnnular(liquidType));
783
784 const unsigned int offset = static_cast<unsigned int>(vertexBuffer.size());
785
786 ControlPoint ctrlPoint1 = controlPoint;
787 ControlPoint ctrlPoint2 = controlPoint;
788
789 if (liquidType == MeshType::AnnularWater)
790 {
791 ctrlPoint2.radius *= glm::sqrt(ctrlPoint2.flow.oil + ctrlPoint2.flow.gas);
792
793 ctrlPoint1.direction *= -1;
794 ctrlPoint2.direction *= -1;
795 }
796 else
797 {
798 ctrlPoint1.radius *= glm::sqrt(ctrlPoint1.flow.oil + ctrlPoint1.flow.gas);
799 ctrlPoint2.radius *= glm::sqrt(ctrlPoint2.flow.gas);
800 }
801
802 // Generate vertex buffer for closing caps.
803 transformCrossection(ratio, alpha, vertexBuffer, ctrlPoint1, liquidType, true);
804 transformCrossection(ratio, alpha, vertexBuffer, ctrlPoint2, liquidType, true);
805
806 const auto numOfSegments = getNumOfSegmentsInCrossSection(liquidType);
807 for (unsigned int i = 0; i < 2; i++)
808 {
809 for (unsigned int j = 0; j < numOfSegments; ++j)
810 {
811 indexBuffer.push_back(offset + j);
812 if (i == 0)
813 {
814 indexBuffer.push_back(offset + j + numOfSegments);
815 indexBuffer.push_back(j < numOfSegments - 1 ? offset + j + 1 : offset);
816 }
817 else
818 {
819 indexBuffer.push_back(j < numOfSegments - 1 ? offset + j + 1 : offset);
820 indexBuffer.push_back(offset + j + numOfSegments);
821 }
822
823 indexBuffer.push_back(offset + j + numOfSegments);
824 if (i == 0)
825 {
826 indexBuffer.push_back(j < numOfSegments - 1 ? offset + j + numOfSegments + 1 : offset + numOfSegments);
827 indexBuffer.push_back(j < numOfSegments - 1 ? offset + j + 1 : offset);
828 }
829 else
830 {
831 indexBuffer.push_back(j < numOfSegments - 1 ? offset + j + 1 : offset);
832 indexBuffer.push_back(j < numOfSegments - 1 ? offset + j + numOfSegments + 1 : offset + numOfSegments);
833 }
834 }
835 }
836 }
837 else
838 {
839 assert(!"Unknown flow type");
840 }
841}
842
843void MultiphaseFlowComponent::generateExtrusionVerticesWall(std::vector<WallVertex>& vertexBuffer, std::vector<unsigned int>& indexBuffer, const std::vector<ControlPoint>& controlPoints, MeshType liquidType)
844{
845 for (const auto& controlPoint : controlPoints)
846 {
847 std::vector<Vertex> temp;
848 transformCrossection(1.f, 1.f, temp, controlPoint, liquidType);
849
850 // Add colors.
851 for (const auto& v : temp)
852 {
853 vertexBuffer.push_back({ v.position, v.normalAndOpacity, wallValueToColor(controlPoint) });
854 }
855 }
856
857 // Generate index buffer.
858 const auto numOfSegments = getNumOfSegmentsInCrossSection(liquidType);
859 for (unsigned int offset = 0; offset < static_cast<unsigned int>(controlPoints.size()) - 1; offset++)
860 {
861 for (unsigned int j = 0; j < numOfSegments - 1; j++)
862 {
863 indexBuffer.push_back((offset * numOfSegments) + j);
864 indexBuffer.push_back((offset * numOfSegments) + j + 1);
865 indexBuffer.push_back(((offset + 1) * numOfSegments) + j + 1);
866
867 indexBuffer.push_back((offset * numOfSegments) + j);
868 indexBuffer.push_back(((offset + 1) * numOfSegments) + j + 1);
869 indexBuffer.push_back(((offset + 1) * numOfSegments) + j);
870 }
871
872 indexBuffer.push_back((offset * numOfSegments) + numOfSegments - 1);
873 indexBuffer.push_back((offset * numOfSegments));
874 indexBuffer.push_back(((offset + 1) * numOfSegments));
875
876 indexBuffer.push_back((offset * numOfSegments) + numOfSegments - 1);
877 indexBuffer.push_back(((offset + 1) * numOfSegments));
878 indexBuffer.push_back(((offset + 1) * numOfSegments) + numOfSegments - 1);
879 }
880
881 // Generate closing caps if multiphase flow mesh is not visible.
882 if (liquidType == MeshType::Wall && flowFraction.size() != trajectoryPoints.size())
883 {
884 std::vector<Vertex> tempFirst;
885 std::vector<Vertex> tempLast;
886 std::vector<unsigned int> indexBufferFirst;
887 std::vector<unsigned int> indexBufferLast;
888 generateClosingCaps(tempFirst, indexBufferFirst, controlPoints[0], liquidType);
889 generateClosingCaps(tempLast, indexBufferLast, controlPoints[controlPoints.size() - 1], liquidType);
890
891 // Add colors.
892 unsigned int offsetFirst = static_cast<unsigned int>(vertexBuffer.size());
893 vertexBuffer.reserve(vertexBuffer.size() + tempFirst.size() + tempLast.size());
894 for (const auto& v : tempFirst)
895 {
896 vertexBuffer.push_back({ v.position, v.normalAndOpacity, wallValueToColor(controlPoints[0]) });
897 }
898 for (const auto& i : indexBufferFirst)
899 {
900 indexBuffer.push_back(i + offsetFirst);
901 }
902
903 unsigned int offsetLast = static_cast<unsigned int>(vertexBuffer.size());
904 for (const auto& v : tempLast)
905 {
906 vertexBuffer.push_back({ v.position, v.normalAndOpacity, wallValueToColor(controlPoints[controlPoints.size() - 1]) });
907 }
908 for (const auto& i : indexBufferLast)
909 {
910 indexBuffer.push_back(i + offsetLast);
911 }
912 }
913}
914
915static glm::mat4x4 shadowMatrix(glm::vec4 groundplane, glm::vec4 lightpos)
916{
917 // Find dot product between light position vector and ground plane normal.
918 float dot = glm::dot(groundplane, lightpos);
919
920 glm::mat4x4 shadowMat;
921 shadowMat[0][0] = dot - lightpos.x * groundplane.x;
922 shadowMat[1][0] = 0.f - lightpos.x * groundplane.y;
923 shadowMat[2][0] = 0.f - lightpos.x * groundplane.z;
924 shadowMat[3][0] = 0.f - lightpos.x * groundplane.w;
925
926 shadowMat[0][1] = 0.f - lightpos.y * groundplane.x;
927 shadowMat[1][1] = dot - lightpos.y * groundplane.y;
928 shadowMat[2][1] = 0.f - lightpos.y * groundplane.z;
929 shadowMat[3][1] = 0.f - lightpos.y * groundplane.w;
930
931 shadowMat[0][2] = 0.f - lightpos.z * groundplane.x;
932 shadowMat[1][2] = 0.f - lightpos.z * groundplane.y;
933 shadowMat[2][2] = dot - lightpos.z * groundplane.z;
934 shadowMat[3][2] = 0.f - lightpos.z * groundplane.w;
935
936 shadowMat[0][3] = 0.f - lightpos.w * groundplane.x;
937 shadowMat[1][3] = 0.f - lightpos.w * groundplane.y;
938 shadowMat[2][3] = 0.f - lightpos.w * groundplane.z;
939 shadowMat[3][3] = dot - lightpos.w * groundplane.w;
940
941 return shadowMat;
942}
943
944void MultiphaseFlowComponent::generateMultiphaseFlowMesh(Mesh* mesh, const std::vector<ControlPoint>& controlPoints, MeshType liquidType)
945{
946 // Generate vertex and buffers for the main mesh.
947 if (liquidType != MeshType::Wall && liquidType != MeshType::Shadow)
948 {
949 std::vector<Vertex> vertexBuffer;
950 std::vector<unsigned int> indexBuffer;
951 generateExtrusionVertices(vertexBuffer, indexBuffer, controlPoints, liquidType);
952
953 // Set vertex and index buffers in the mesh object.
954 if (!vertexBuffer.empty())
955 {
956 if (liquidType == MeshType::StratifiedOil)
957 {
958 calculateNormals(vertexBuffer, indexBuffer);
959 }
960
961 mesh->setVertexData(vertexBuffer.data(), vertexBuffer.size());
962 mesh->setIndexes(std::move(indexBuffer));
963 }
964 else
965 {
966 mesh->clear();
967 }
968
969 // Update bounding box.
970 for (auto& p : vertexBuffer)
971 {
972 geometryMin = glm::min(geometryMin, p.position);
973 geometryMax = glm::max(geometryMax, p.position);
974 }
975 }
976 else
977 {
978 std::vector<WallVertex> vertexBufferWall;
979 std::vector<unsigned int> indexBuffer;
980 generateExtrusionVerticesWall(vertexBufferWall, indexBuffer, controlPoints, liquidType);
981
982 if (liquidType == MeshType::Shadow)
983 {
984 // Project shadow mesh on a plane.
985 float l = glm::max(glm::max(glm::max(geometryMax.x, geometryMax.y), geometryMax.z), 1.f);
986 glm::mat4x4 shadowMat = shadowMatrix(glm::vec4(0, 0, -1, geometryMin.z), glm::vec4(l* 1.5f, -l * 1.5f, l * 4.f, 1.f));
987
988 for (auto& v : vertexBufferWall)
989 {
990 auto transformed = shadowMat * glm::vec4(v.position.x, v.position.y, v.position.z, 1);
991 v.position = glm::vec3(transformed.x / transformed.w, transformed.y / transformed.w, transformed.z / transformed.w);
992 }
993 }
994
995 // Set vertex and index buffers in the mesh object.
996 if (!vertexBufferWall.empty())
997 {
998 mesh->setVertexData(vertexBufferWall.data(), vertexBufferWall.size());
999 mesh->setIndexes(std::move(indexBuffer));
1000 }
1001 else
1002 {
1003 mesh->clear();
1004 }
1005
1006
1007 // Update bounding box.
1008 if (liquidType == MeshType::Wall)
1009 {
1010 for (auto& p : vertexBufferWall)
1011 {
1012 geometryMin = glm::min(geometryMin, p.position);
1013 geometryMax = glm::max(geometryMax, p.position);
1014 }
1015 }
1016 }
1017}
1018
1019// Stretches the trajectory points to avoid overlapping of the mesh in the bends.
1020static void stretch(std::vector<MultiphaseFlowComponent::TrajectoryPoint>& points)
1021{
1022 assert(points.size() >= 2);
1023
1024 glm::vec3 direction = points[1].pos - points[0].pos;
1025 glm::vec3 dirPrev = glm::normalize(direction);
1026 glm::vec3 prevPoint;
1027 glm::vec3 trajectoryOffset(0, 0, 0);
1028
1029 for (size_t i = 0; i < points.size(); ++i)
1030 {
1031 // Calculate direction vector.
1032 if (i > 0)
1033 {
1034 if (i <= points.size() - 2)
1035 {
1036 direction = points[i + 1].pos - prevPoint;
1037 }
1038 else
1039 {
1040 direction = points[i].pos - prevPoint;
1041 }
1042 }
1043
1044 float dirLength = glm::length(direction);
1045 if (dirLength > 0)
1046 {
1047 // Normalize direction.
1048 direction.x /= dirLength;
1049 direction.y /= dirLength;
1050 direction.z /= dirLength;
1051
1052 // Save previous point.
1053 prevPoint = points[i].pos;
1054
1055 // Get radius.
1056 const float r = points[i].radius;
1057
1058 // Fix trajectory with sharp angles.
1059 // This will modify trajectory points and stretch the geometry at bends.
1060 auto dot = glm::dot(direction, dirPrev);
1061 dot = glm::clamp(glm::abs(dot), 0.f, 1.f);
1062 float angle = acos(dot);
1063 float ratio = angle / glm::half_pi<float>() * r * 0.65f;
1064
1065 trajectoryOffset += (dirPrev + direction) * ratio;
1066 points[i].pos += trajectoryOffset;
1067
1068 dirPrev = direction;
1069 }
1070 }
1071}
1072
1073template <class T> class CatmullRomSpline
1074{
1075public:
1076 CatmullRomSpline(T x0, T x1, T x2, T x3, float t01, float t12, float t23, bool x0HasValue, bool x3HasValue)
1077 {
1078 T x0Scaled;
1079 if (x0HasValue)
1080 {
1081 auto delta = x1 - x0;
1082 delta /= t01;
1083 x0Scaled = x1 - delta * t12;
1084 }
1085 else
1086 {
1087 auto delta = x2 - x1;
1088 x0Scaled = x1 - delta;
1089 }
1090
1091 T x3Scaled;
1092 if (x3HasValue)
1093 {
1094 auto delta = x2 - x3;
1095 delta /= t23;
1096 x3Scaled = x2 - delta * t12;
1097 }
1098 else
1099 {
1100 auto delta = x1 - x2;
1101 x3Scaled = x2 - delta;
1102 }
1103
1104 // Compute control data
1105 _c0 = x1;
1106 _c1 = -x0Scaled * 0.5f + x2 * 0.5f;
1107 _c2 = x0Scaled - x1 * 2.5f + x2 * 2.0f - x3Scaled * 0.5f;
1108 _c3 = -x0Scaled * 0.5f + x1 * 1.5f - x2 * 1.5f + x3Scaled * 0.5f;
1109 }
1110
1111 T Interpolate(float t)
1112 {
1113 return (((_c3 * t + _c2) * t + _c1) * t + _c0);
1114 }
1115
1116private:
1117 T _c0;
1118 T _c1;
1119 T _c2;
1120 T _c3;
1121};
1122
1123static void interpolate(std::vector<MultiphaseFlowComponent::TrajectoryPoint>& points)
1124{
1125 assert(points.size() >= 2);
1126
1127 const float MAX_STEPS = glm::min(static_cast<float>(50000 / points.size()), 20.f);
1128 const float MIN_STEPS = glm::max(glm::floor(MAX_STEPS / 4), 1.f);
1129
1130 if (MAX_STEPS <= 1)
1131 {
1132 return;
1133 }
1134
1135 std::vector<MultiphaseFlowComponent::TrajectoryPoint> newPoints;
1136 size_t reservedSize = points.size() * static_cast<size_t>((MAX_STEPS + MIN_STEPS) / 2.f);
1137 newPoints.reserve(reservedSize);
1138
1139 glm::vec3 direction = points[1].pos - points[0].pos;
1140 glm::vec3 dirPrev = glm::normalize(direction);
1141
1142 for (size_t i = 0; i < points.size() - 1; i++)
1143 {
1144 if (i > 0)
1145 {
1146 if (i <= points.size() - 2)
1147 {
1148 direction = points[i + 1].pos - points[i - 1].pos;
1149 }
1150 else
1151 {
1152 direction = points[i].pos - points[i - 1].pos;
1153 }
1154 }
1155 direction = glm::normalize(direction);
1156
1157 bool hasFirstPoint = (i > 0);
1158 bool hasLastPoint = (i < points.size() - 2);
1159
1160 glm::vec3 p0 = hasFirstPoint ? points[i - 1].pos : glm::vec3();
1161 glm::vec3 f0 = hasFirstPoint ? points[i - 1].flowFraction : glm::vec3();
1162
1163 const auto& p1 = points[i].pos;
1164 const auto& f1 = points[i].flowFraction;
1165
1166 glm::vec3 p2 = points[i + 1].pos;
1167 glm::vec3 f2 = points[i + 1].flowFraction;
1168
1169 glm::vec3 p3 = hasLastPoint ? points[i + 2].pos : glm::vec3();
1170 glm::vec3 f3 = hasLastPoint ? points[i + 2].flowFraction : glm::vec3();
1171
1172 float t01 = hasFirstPoint ? glm::distance(p0, p1) : 0;
1173 float t12 = glm::distance(p1, p2);
1174 float t23 = hasLastPoint ? glm::distance(p2, p3) : 0;
1175
1176 // Calculate number of interpolation steps based on the trajectory.
1177 auto dot = glm::dot(direction, dirPrev);
1178 auto angle = glm::acos(glm::clamp(glm::abs(dot), 0.f, 1.f));
1179 auto ratio = glm::clamp(angle / glm::half_pi<float>(), 0.f, 1.f);
1180
1181 int steps = static_cast<int>(glm::lerp(MIN_STEPS, MAX_STEPS, ratio));
1182 assert(steps > 0);
1183
1184 // Create spline objects.
1185 CatmullRomSpline<glm::vec3> splineTrajectory(p0, p1, p2, p3, t01, t12, t23, hasFirstPoint, hasLastPoint);
1186 CatmullRomSpline<glm::vec3> splineFlowFraction(f0, f1, f2, f3, t01, t12, t23, hasFirstPoint, hasLastPoint);
1187
1188 // Interpolate.
1189 for (int j = 0; j < steps; j++)
1190 {
1191 float ratio_ = static_cast<float>(j) / static_cast<float>(steps);
1192
1194 newPoint.pos = splineTrajectory.Interpolate(ratio_);
1195 newPoint.radius = glm::lerp(points[i].radius, points[i + 1].radius, ratio_);
1196 newPoint.flowFraction = splineFlowFraction.Interpolate(ratio_);
1197 newPoint.wallValue = glm::lerp(points[i].wallValue, points[i + 1].wallValue, ratio_);
1198 newPoint.md = glm::lerp(points[i].md, points[i + 1].md, ratio_);
1199
1200 newPoints.push_back(newPoint);
1201 }
1202
1203 dirPrev = direction;
1204 }
1205
1206 // Add last point.
1207 newPoints.push_back(points[points.size() - 1]);
1208
1209 // Swap data.
1210 std::swap(points, newPoints);
1211}
1212
1213void MultiphaseFlowComponent::updateGroundGrid()
1214{
1215 // Set visibility.
1216 if (groundGridMeshEntity)
1217 {
1218 auto meshSceneCompoment = groundGridMeshEntity->getComponent<MeshRenderComponent>();
1219 meshSceneCompoment->setVisible(showGrid);
1220 }
1221
1222 // Exit if grid is not visible.
1223 if (!showGrid)
1224 {
1225 return;
1226 }
1227
1228 glm::vec3 center = (geometryMax + geometryMin) * 0.5f;
1229 float edgeLength = glm::max(geometryMax.x - geometryMin.x, geometryMax.y - geometryMin.y) * 1.5f; // Make the grid 150% bigger than the actual mesh.
1230 glm::vec3 corner = center - edgeLength * 0.5f;
1231
1232 // Create grid
1233 std::vector<PositionVertex> vertexBufferGrid;
1234 std::vector<unsigned int> indexBufferGrid;
1235 static const int NUM_OF_SECTORS = 10;
1236 for (int j = 0; j < 2; j++)
1237 {
1238 for (int i = 0; i <= NUM_OF_SECTORS; i++)
1239 {
1240 PositionVertex v0;
1241 PositionVertex v1;
1242
1243 if (j == 0)
1244 {
1245 v0.position = glm::vec3(corner.x + edgeLength / static_cast<float>(NUM_OF_SECTORS)* static_cast<float>(i), corner.y, geometryMin.z * 1.01f);
1246 v1.position = glm::vec3(corner.x + edgeLength / static_cast<float>(NUM_OF_SECTORS)* static_cast<float>(i), corner.y + edgeLength, geometryMin.z * 1.01f);
1247 }
1248 else
1249 {
1250 v0.position = glm::vec3(corner.x, corner.y + edgeLength / static_cast<float>(NUM_OF_SECTORS)* static_cast<float>(i), geometryMin.z * 1.01f);
1251 v1.position = glm::vec3(corner.x + edgeLength, corner.y + edgeLength / static_cast<float>(NUM_OF_SECTORS)* static_cast<float>(i), geometryMin.z * 1.01f);
1252 }
1253
1254 vertexBufferGrid.push_back(v0);
1255 vertexBufferGrid.push_back(v1);
1256
1257 indexBufferGrid.push_back(static_cast<unsigned int>(vertexBufferGrid.size()) - 2);
1258 indexBufferGrid.push_back(static_cast<unsigned int>(vertexBufferGrid.size()) - 1);
1259 }
1260 }
1261
1262 // Update mesh with the new vertex and index buffers.
1263 Mesh* meshGrid = context->meshManager->get(groundGridMeshEntity->getComponent<MeshComponent>()->meshHandle);
1264 meshGrid->setVertexData(vertexBufferGrid.data(), vertexBufferGrid.size());
1265 meshGrid->setIndexes(std::move(indexBufferGrid));
1266 meshGrid->primitiveType = Cogs::PrimitiveType::LineList;
1267}
1268
1269void MultiphaseFlowComponent::updateIndicatorRing(const std::vector<TrajectoryPoint>& trajectory)
1270{
1271 // Set visibility.
1272 if (indicatorRingEntity)
1273 {
1274 auto meshSceneCompoment = indicatorRingEntity->getComponent<MeshRenderComponent>();
1275 meshSceneCompoment->setVisible(showIndicatorRing);
1276 }
1277
1278 // Exit if the ring is not visible.
1279 if (!showIndicatorRing)
1280 {
1281 return;
1282 }
1283
1284 // Find position.
1285 assert(trajectory.size() > 1);
1286 glm::vec3 positionPoint;
1287 float radius = 0.f;
1288 glm::quat rotation;
1289 for (size_t i = 1; i < trajectory.size(); i++)
1290 {
1291 if (indicatorRingPosition < trajectory[i].md || i+1 == trajectory.size())
1292 {
1293 auto ratio = (trajectory[i].md - indicatorRingPosition) / (trajectory[i].md - trajectory[i - 1].md);
1294 ratio = glm::clamp(ratio, 0.f, 1.f);
1295
1296 positionPoint = glm::lerp(trajectory[i].pos, trajectory[i - 1].pos, ratio);
1297 radius = glm::lerp(trajectory[i].radius, trajectory[i - 1].radius, ratio) * 1.3f;
1298 rotation = glm::slerp(trajectory[i].rotation, trajectory[i - 1].rotation, ratio);
1299
1300 break;
1301 }
1302 }
1303
1304 std::vector<PositionVertex> vertexBuffer;
1305 std::vector<unsigned int> indexBuffer;
1306 static const int NUM_OF_STEPS = 32;
1307 auto angle = 0.0;
1308 auto angleStep = glm::two_pi<double>() / static_cast<double>(NUM_OF_STEPS);
1309
1310 for (auto i = 0; i < NUM_OF_STEPS; i++)
1311 {
1312 auto s = static_cast<float>(sin(angle)) * radius;
1313 auto c = static_cast<float>(cos(angle)) * radius;
1314 angle += angleStep;
1315
1317 v.position = rotation * glm::vec3(s, c, 0);
1318 v.position += positionPoint;
1319
1320 vertexBuffer.push_back(v);
1321
1322 if (i == NUM_OF_STEPS - 1)
1323 {
1324 indexBuffer.push_back(i);
1325 indexBuffer.push_back(0);
1326 }
1327 else
1328 {
1329 indexBuffer.push_back(i);
1330 indexBuffer.push_back(i + 1);
1331 }
1332 }
1333
1334 // Update mesh with the new vertex and index buffers.
1335 Mesh* meshGrid = context->meshManager->get(indicatorRingEntity->getComponent<MeshComponent>()->meshHandle);
1336 meshGrid->setVertexData(vertexBuffer.data(), vertexBuffer.size());
1337 meshGrid->setIndexes(std::move(indexBuffer));
1338 meshGrid->primitiveType = Cogs::PrimitiveType::LineList;
1339
1340 // Update material color.
1341 auto meshRenderComp = indicatorRingEntity->getComponent<MeshRenderComponent>();
1342 meshRenderComp->material->setVec4Property(DefaultMaterial::DiffuseColor, glm::vec4(indicatorRingColor, 1));
1343}
1344
1345void MultiphaseFlowComponent::updateAxisAnnotations()
1346{
1347 Mesh* mesh = context->meshManager->get(annotationAxisMeshEntity->getComponent<MeshComponent>()->meshHandle);
1348
1349 // If axial extents are hidden.
1350 if (!showAxialExtents)
1351 {
1352 // Hide axial extents meshes.
1353 mesh->clear();
1354
1355 // Hide text.
1356 for (int i = AnnotationAxis::AxisFirst; i <= AnnotationAxis::AxisLast; i++)
1357 {
1358 auto textComp = textEntity[i]->getComponent<TextComponent>();
1359 textComp->texts.clear();
1360 }
1361
1362 // Exit.
1363 return;
1364 }
1365
1366 glm::vec3 center = (geometryMax + geometryMin) * 0.5f;
1367 float edgeLength = glm::max(geometryMax.x - geometryMin.x, geometryMax.y - geometryMin.y) * 1.5f; // Make the grid 150% bigger than the actual mesh.
1368
1369 std::vector<PositionVertex> vertexBuffer;
1370 std::vector<unsigned int> indexBuffer;
1371
1372 // Calculate trajectory bounding box.
1373 glm::vec3 trajectoryMin = trajectoryPoints[0];
1374 glm::vec3 trajectoryMax = trajectoryPoints[0];
1375 for (const auto& p : trajectoryPoints)
1376 {
1377 trajectoryMin = glm::min(trajectoryMin, p);
1378 trajectoryMax = glm::max(trajectoryMax, p);
1379 }
1380
1381 // Show annotations based on the trajectory bounding box.
1382 bool xShowAnnotation = glm::abs(trajectoryMax.x - trajectoryMin.x) > 0.1f;
1383 bool yShowAnnotation = glm::abs(trajectoryMax.y - trajectoryMin.y) > 0.1f;
1384 bool zShowAnnotation = glm::abs(trajectoryMax.z - trajectoryMin.z) > 0.1f;
1385
1386 // X line.
1387 float xLinePosZ = geometryMax.z + edgeLength * 0.01f;
1388 if (xShowAnnotation)
1389 {
1390 vertexBuffer.push_back({ glm::vec3(scaledTrajectoryMin.x, center.y, xLinePosZ) });
1391 vertexBuffer.push_back({ glm::vec3(scaledTrajectoryMax.x, center.y, xLinePosZ) });
1392 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1393 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1394 vertexBuffer.push_back({ glm::vec3(scaledTrajectoryMin.x, center.y, xLinePosZ - edgeLength * 0.005f) });
1395 vertexBuffer.push_back({ glm::vec3(scaledTrajectoryMin.x, center.y, xLinePosZ + edgeLength * 0.005f) });
1396 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1397 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1398 vertexBuffer.push_back({ glm::vec3(scaledTrajectoryMax.x, center.y, xLinePosZ - edgeLength * 0.005f) });
1399 vertexBuffer.push_back({ glm::vec3(scaledTrajectoryMax.x, center.y, xLinePosZ + edgeLength * 0.005f) });
1400 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1401 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1402 }
1403
1404 // Y line.
1405 float yLinePosX = geometryMin.x - edgeLength * 0.01f;
1406 if (yShowAnnotation)
1407 {
1408 vertexBuffer.push_back({ glm::vec3(yLinePosX, scaledTrajectoryMin.y, center.z) });
1409 vertexBuffer.push_back({ glm::vec3(yLinePosX, scaledTrajectoryMax.y, center.z) });
1410 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1411 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1412 vertexBuffer.push_back({ glm::vec3(yLinePosX - edgeLength * 0.005f, scaledTrajectoryMin.y, center.z) });
1413 vertexBuffer.push_back({ glm::vec3(yLinePosX + edgeLength * 0.005f, scaledTrajectoryMin.y, center.z) });
1414 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1415 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1416 vertexBuffer.push_back({ glm::vec3(yLinePosX - edgeLength * 0.005f, scaledTrajectoryMax.y, center.z) });
1417 vertexBuffer.push_back({ glm::vec3(yLinePosX + edgeLength * 0.005f, scaledTrajectoryMax.y, center.z) });
1418 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1419 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1420 }
1421
1422 // Z line.
1423 float zLinePosX = geometryMax.x + edgeLength * 0.01f;
1424 if (zShowAnnotation)
1425 {
1426 vertexBuffer.push_back({ glm::vec3(zLinePosX, center.y, scaledTrajectoryMin.z) });
1427 vertexBuffer.push_back({ glm::vec3(zLinePosX, center.y, scaledTrajectoryMax.z) });
1428 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1429 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1430 vertexBuffer.push_back({ glm::vec3(zLinePosX - edgeLength * 0.005f, center.y, scaledTrajectoryMin.z) });
1431 vertexBuffer.push_back({ glm::vec3(zLinePosX + edgeLength * 0.005f, center.y, scaledTrajectoryMin.z) });
1432 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1433 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1434 vertexBuffer.push_back({ glm::vec3(zLinePosX - edgeLength * 0.005f, center.y, scaledTrajectoryMax.z) });
1435 vertexBuffer.push_back({ glm::vec3(zLinePosX + edgeLength * 0.005f, center.y, scaledTrajectoryMax.z) });
1436 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 2);
1437 indexBuffer.push_back(static_cast<unsigned int>(vertexBuffer.size()) - 1);
1438 }
1439
1440 // Update mesh with the new vertex and index buffers.
1441 mesh->setVertexData(vertexBuffer.data(), vertexBuffer.size());
1442 mesh->setIndexes(std::move(indexBuffer));
1443 mesh->primitiveType = Cogs::PrimitiveType::LineList;
1444
1445 // Clear text.
1446 TextComponent* textComp[AnnotationAxis::AxisLast + 1] = { nullptr, nullptr, nullptr };
1447 for (int i = AnnotationAxis::AxisFirst; i <= AnnotationAxis::AxisLast; i++)
1448 {
1449 textComp[i] = textEntity[i]->getComponent<TextComponent>();
1450 textComp[i]->color = glm::vec4(0.f, 0.f, 0.f, 1.f);
1451 textComp[i]->texts.clear();
1452 textComp[i]->positions.clear();
1453 textComp[i]->positionMode = PositionMode::World;
1454 textComp[i]->setChanged();
1455 textComp[i]->getComponent<SceneComponent>()->visible = true;
1456 }
1457
1458 // X axis annotation.
1459 if (xShowAnnotation)
1460 {
1461 std::stringstream xMinText;
1462 xMinText << static_cast<int>(floor(trajectoryMin.x * unitScale)) << " " << unitText;
1463 textComp[AnnotationAxis::X]->texts.push_back(xMinText.str());
1464 textComp[AnnotationAxis::X]->positions.push_back(glm::vec3(scaledTrajectoryMin.x, center.y, xLinePosZ + edgeLength * 0.01f));
1465
1466 std::stringstream xMaxText;
1467 xMaxText << static_cast<int>(floor(trajectoryMax.x * unitScale)) << " " << unitText;
1468 textComp[AnnotationAxis::X]->texts.push_back(xMaxText.str());
1469 textComp[AnnotationAxis::X]->positions.push_back(glm::vec3(scaledTrajectoryMax.x, center.y, xLinePosZ + edgeLength * 0.01f));
1470
1471 textComp[AnnotationAxis::X]->texts.push_back("X");
1472 textComp[AnnotationAxis::X]->positions.push_back(glm::vec3((scaledTrajectoryMax.x + scaledTrajectoryMin.x) / 2, center.y, xLinePosZ + edgeLength * 0.005f));
1473 }
1474
1475 // Y axis annotation.
1476 if (yShowAnnotation)
1477 {
1478 std::stringstream yMinText;
1479 yMinText << static_cast<int>(floor(trajectoryMin.y * unitScale)) << " " << unitText;
1480 textComp[AnnotationAxis::YMin]->texts.push_back(yMinText.str());
1481 textComp[AnnotationAxis::YMin]->positions.push_back(glm::vec3(yLinePosX - edgeLength * 0.01f, scaledTrajectoryMin.y, center.z));
1482
1483 std::stringstream yMaxText;
1484 yMaxText << static_cast<int>(floor(trajectoryMax.y * unitScale)) << " " << unitText;
1485 textComp[AnnotationAxis::YMax]->texts.push_back(yMaxText.str());
1486 textComp[AnnotationAxis::YMax]->positions.push_back(glm::vec3(yLinePosX - edgeLength * 0.01f, scaledTrajectoryMax.y, center.z));
1487
1488 textComp[AnnotationAxis::Y]->texts.push_back("Y");
1489 textComp[AnnotationAxis::Y]->positions.push_back(glm::vec3(yLinePosX + edgeLength * 0.005f, (scaledTrajectoryMin.y + scaledTrajectoryMax.y) / 2, center.z));
1490 }
1491
1492 // Z axis annotation.
1493 if (zShowAnnotation)
1494 {
1495 std::stringstream zMaxText;
1496 zMaxText << " " << static_cast<int>(ceil(trajectoryMax.z * unitScale)) << " " << unitText << " ";
1497 textComp[AnnotationAxis::Z]->texts.push_back(zMaxText.str());
1498 textComp[AnnotationAxis::Z]->positions.push_back(glm::vec3(zLinePosX + edgeLength * 0.005f, center.y, scaledTrajectoryMax.z));
1499
1500 std::stringstream zMinText;
1501 zMinText << " " << static_cast<int>(floor(trajectoryMin.z * unitScale)) << " " << unitText << " ";
1502 textComp[AnnotationAxis::Z]->texts.push_back(zMinText.str());
1503 textComp[AnnotationAxis::Z]->positions.push_back(glm::vec3(zLinePosX + edgeLength * 0.005f, center.y, scaledTrajectoryMin.z));
1504
1505 textComp[AnnotationAxis::Z]->texts.push_back("Z");
1506 textComp[AnnotationAxis::Z]->positions.push_back(glm::vec3(zLinePosX + edgeLength * 0.005f, center.y, (scaledTrajectoryMin.z + scaledTrajectoryMax.z) / 2));
1507 }
1508
1509 // Text has been changed.
1510 for (int i = AnnotationAxis::AxisFirst; i <= AnnotationAxis::AxisLast; i++)
1511 {
1512 textComp[i]->setChanged();
1513 }
1514}
1515
1516void MultiphaseFlowComponent::removeDuplicatedPoints(std::vector<TrajectoryPoint>& points)
1517{
1518 assert(!points.empty());
1519 std::vector<TrajectoryPoint> newPoints;
1520 newPoints.reserve(points.size());
1521 newPoints.push_back(points[0]);
1522
1523 for (size_t i = 1; i < points.size(); i++)
1524 {
1525 if (points[i].pos != newPoints[newPoints.size() - 1].pos)
1526 {
1527 newPoints.push_back(points[i]);
1528 }
1529 }
1530
1531 points.swap(newPoints);
1532}
1533
1534void MultiphaseFlowComponent::applyScaling(std::vector<TrajectoryPoint>& points)
1535{
1536 assert(!points.empty());
1537
1538 float bottomHeight = points[0].pos.z;
1539 for (auto& p : points)
1540 {
1541 bottomHeight = glm::min(bottomHeight, p.pos.z);
1542 }
1543
1544 for (auto& p : points)
1545 {
1546 p.pos.z = (p.pos.z - bottomHeight) * heightScale;
1547 }
1548
1549 for (auto& p : points)
1550 {
1551 p.radius *= radiusScale;
1552 }
1553}
1554
1555void MultiphaseFlowComponent::center(std::vector<TrajectoryPoint>& points)
1556{
1557 assert(!points.empty());
1558
1559 glm::vec3 min = points[0].pos;
1560 glm::vec3 max = points[0].pos;
1561 for (auto& p : points)
1562 {
1563 min = glm::min(min, p.pos);
1564 max = glm::max(max, p.pos);
1565 }
1566
1567 glm::vec3 center;
1568 center.x = (min.x + max.x) / 2;
1569 center.y = (min.y + max.y) / 2;
1570 center.z = (min.z + max.z) / 2;
1571
1572 for (auto& p : points)
1573 {
1574 p.pos -= center;
1575 }
1576}
1577
1578static float crosssectionAreaToHeight(float alpha)
1579{
1580 static const float fac = glm::pow(1.5f * glm::pi<float>(), 1.f / 3.f);
1581 const float beta = glm::pi<float>() * alpha + fac * (1 - 2 * alpha + glm::pow(alpha, 1.f / 3.f) - glm::pow(1 - alpha, 1.f / 3.f));
1582 float res = 0.5f - 0.5f * glm::cos(beta);
1583 return res;
1584}
1585
1586static glm::quat myRotation(glm::vec3 dir, glm::vec3 up = glm::vec3(0, 1, 0))
1587{
1588 glm::vec3 x = glm::cross(up, dir);
1589 x = glm::normalize(x);
1590
1591 glm::vec3 y = glm::cross(dir, x);
1592 y = glm::normalize(y);
1593
1594 glm::mat3x3 cMatrix(x, y, dir);
1595
1596 return glm::quat(cMatrix);
1597}
1598
1599void MultiphaseFlowComponent::finalizeControlPoints(std::vector<ControlPoint>& controlPoints)
1600{
1601 static const int DISTRIBUTION_SIZE = 5;
1602 static float distributionTable[DISTRIBUTION_SIZE + 1];
1603 static bool distributionTableInitialized = false;
1604 if (!distributionTableInitialized)
1605 {
1606 for (int x = 0; x <= DISTRIBUTION_SIZE; x++)
1607 {
1608 float y = expf(-(x * x * 0.1f));
1609 distributionTable[x] = y;
1610 }
1611 distributionTableInitialized = true;
1612 }
1613
1614 for (int i = 0; i < static_cast<int>(controlPoints.size()); i++)
1615 {
1616 auto& controlPoint = controlPoints[i];
1617
1618 // Interpolate annular flow.
1619 controlPoint.annularFlowRatio = 0.f;
1620
1621 float sum = 0.f;
1622 for (int j = -DISTRIBUTION_SIZE; j <= DISTRIBUTION_SIZE; j++)
1623 {
1624 int k = i + j;
1625 if (k < 0) k = 0;
1626 if (k > static_cast<int>(controlPoints.size() - 1)) k = static_cast<int>(controlPoints.size() - 1);
1627
1628 auto& sibling = controlPoints[k];
1629
1630 float ratio = distributionTable[j < 0 ? -j : j];
1631 controlPoint.annularFlowRatio += sibling.annularFlow ? ratio : 0.f;
1632 sum += ratio;
1633 }
1634
1635 assert(sum > 0);
1636 controlPoint.annularFlowRatio /= sum;
1637 controlPoint.annularFlowRatio = glm::clamp(controlPoint.annularFlowRatio, 0.f, 1.f);
1638
1639 // Set binary inverted flow.
1640 controlPoint.invertedFlowRatio = controlPoint.invertedFlow ? 1.f : 0.f;
1641 }
1642
1643 // Get ranges for annular flow.
1644 std::vector<std::pair<int, int>> annularRanges;
1645 int start = -1;
1646 for (int i = 0; i < static_cast<int>(controlPoints.size()); i++)
1647 {
1648 auto& controlPoint = controlPoints[i];
1649 if (start == -1 && controlPoint.annularFlowRatio > 0.f)
1650 {
1651 start = glm::max(i - 1, 0);
1652 }
1653 else
1654 if (start >= 0 && controlPoint.annularFlowRatio == 0.f)
1655 {
1656 assert(i > 0);
1657 annularRanges.push_back(std::pair<int, int>(start, i));
1658 start = -1;
1659 }
1660 }
1661 if (start >= 0)
1662 {
1663 annularRanges.push_back(std::pair<int, int>(start, static_cast<int>(controlPoints.size() - 1)));
1664 }
1665
1666 // Interpolate rotations and inverted flow.
1667 for (int i = 0; i < (int)annularRanges.size(); i++)
1668 {
1669 auto& p0 = controlPoints[annularRanges[i].first];
1670 auto& p1 = controlPoints[annularRanges[i].second];
1671
1672 float p0InvertedFlow = p0.invertedFlow ? 1.f : 0.f;
1673 float p1InvertedFlow = p1.invertedFlow ? 1.f : 0.f;
1674
1675 int dist = annularRanges[i].second - annularRanges[i].first;
1676 if (dist > 1)
1677 {
1678 for (int j = 0; j <= dist; j++)
1679 {
1680 float ratio = static_cast<float>(j) / static_cast<float>(dist);
1681 ratio = sCurve(ratio);
1682 ratio = glm::clamp(ratio, 0.f, 1.f);
1683
1684 auto up = glm::lerp(p0.rotationUpVector, p1.rotationUpVector, ratio);
1685
1686 int k = annularRanges[i].first + j;
1687 controlPoints[k].rotation = myRotation(controlPoints[k].direction, up);
1688
1689 // Interpolate inverted flow.
1690 controlPoints[k].invertedFlowRatio = glm::lerp(p0InvertedFlow, p1InvertedFlow, ratio);
1691 }
1692 }
1693 }
1694}
1695
1696void MultiphaseFlowComponent::updateShadowVisibility()
1697{
1698 auto* cam = context->cameraSystem->getMainCamera();
1699 auto transform = static_cast<TransformComponent*>(cam->getComponent<TransformComponent>());
1700
1701 bool show = (transform->position.z > geometryMin.z) && (trajectoryPoints.size() == radius.size()) && showShadow;
1702
1703 if (meshEntity[MeshType::Shadow])
1704 {
1705 auto meshSceneCompoment = meshEntity[MeshType::Shadow]->getComponent<MeshRenderComponent>();
1706 if (meshSceneCompoment)
1707 {
1708 meshSceneCompoment->setVisible(show);
1709 }
1710 }
1711}
1712
1713void MultiphaseFlowComponent::updateMeshes()
1714{
1715 assert(updateNeeded);
1716
1717 updateNeeded = false;
1718
1719 assert(flowFraction.empty() || flowFraction.size() == trajectoryPoints.size());
1720 assert(wallValues.empty() || wallValues.size() == trajectoryPoints.size());
1721
1722 std::vector<TrajectoryPoint> trajectory(trajectoryPoints.size());
1723 float md = 0.0f;
1724 for (size_t i = 0; i < trajectoryPoints.size(); i++)
1725 {
1726 auto& trajPoint = trajectory[i];
1727 trajPoint.pos = trajectoryPoints[i];
1728 trajPoint.radius = radius[i];
1729 trajPoint.flowFraction = !flowFraction.empty() ? flowFraction[i] : glm::vec3();
1730 trajPoint.wallValue = !wallValues.empty() ? wallValues[i] : 0.0f;
1731
1732 if (i > 0)
1733 {
1734 md += glm::distance(trajectory[i - 1].pos, trajectory[i].pos);
1735 }
1736
1737 trajPoint.md = md;
1738 }
1739
1740 removeDuplicatedPoints(trajectory);
1741
1742 if (centerMesh)
1743 {
1744 center(trajectory);
1745 }
1746
1747 applyScaling(trajectory);
1748
1749 stretch(trajectory);
1750 interpolate(trajectory);
1751 stretch(trajectory);
1752
1753 if (centerMesh)
1754 {
1755 center(trajectory);
1756 }
1757 else
1758 {
1759 auto posOffset = trajectoryPoints[0] - trajectory[0].pos;
1760 for (auto &v : trajectory)
1761 {
1762 v.pos += posOffset;
1763 }
1764 }
1765
1766 std::vector<ControlPoint> controlPoints;
1767 controlPoints.resize(trajectory.size());
1768 bool visualizeMultiphaseFlow = !flowFraction.empty();
1769 scaledTrajectoryMin = trajectory[0].pos;
1770 scaledTrajectoryMax = trajectory[0].pos;
1771 bool waterInMultiphaseFlow = false;
1772 bool oilInMultiphaseFlow = false;
1773 bool gasInMultiphaseFlow = false;
1774 for (size_t i = 0; i < trajectory.size(); i++)
1775 {
1776 auto& controlPoint = controlPoints[i];
1777 controlPoint.position = trajectory[i].pos;
1778 controlPoint.radius = trajectory[i].radius;
1779 controlPoint.wall = glm::clamp(!wallValues.empty() ? trajectory[i].wallValue : 0.f, 0.f, 1.f);
1780
1781 // Update trajectory AABB
1782 scaledTrajectoryMin = glm::min(scaledTrajectoryMin, trajectory[i].pos);
1783 scaledTrajectoryMax = glm::max(scaledTrajectoryMax, trajectory[i].pos);
1784
1785 // Calculate direction vector.
1786 controlPoint.direction = trajectory[i == 0 ? 0 : i - 1].pos - trajectory[i < trajectory.size() - 1 ? i + 1 : i].pos;
1787 controlPoint.direction = glm::normalize(controlPoint.direction);
1788
1789 // Segment rotation
1790 auto d = glm::dot(controlPoint.direction, glm::vec3(0, 1, 0));
1791 auto right = glm::abs(d) > 0.95f ? glm::vec3(1, 0, 0) : glm::vec3(0, 1, 0);
1792 if (i == 0)
1793 {
1794 controlPoint.rotationUpVector = right;
1795 }
1796 else
1797 {
1798 auto rotPos = myRotation(controlPoint.direction, right);
1799 auto rotNeg = myRotation(controlPoint.direction, -right);
1800
1801 auto upPos = rotPos * glm::vec3(1, 0, 0);
1802 auto upNeg = rotNeg * glm::vec3(1, 0, 0);
1803
1804 auto upPrev = controlPoints[i - 1].rotation * glm::vec3(1, 0, 0);
1805
1806 auto dotPos = glm::dot(upPos, upPrev);
1807 auto dotNeg = glm::dot(upNeg, upPrev);
1808
1809 controlPoint.rotationUpVector = right * (dotPos > dotNeg ? 1.0f : -1.0f);
1810 }
1811 controlPoint.rotation = myRotation(controlPoint.direction, controlPoint.rotationUpVector);
1812 trajectory[i].rotation = controlPoint.rotation;
1813
1814 // Pre-compute data used to show liquid fraction.
1815 auto& flowData = controlPoint.flow;
1816 flowData.water = visualizeMultiphaseFlow ? glm::max(trajectory[i].flowFraction.x, 0.f) : 0.f;
1817 flowData.oil = visualizeMultiphaseFlow ? glm::max(trajectory[i].flowFraction.y, 0.f) : 0.f;
1818 flowData.gas = visualizeMultiphaseFlow ? glm::max(trajectory[i].flowFraction.z, 0.f) : 0.f;
1819
1820 // Re-normalize amount of liquids and gas.
1821 float total = flowData.water + flowData.oil + flowData.gas;
1822 if (total > 0.f)
1823 {
1824 flowData.water /= total;
1825 flowData.oil /= total;
1826 flowData.gas /= total;
1827 }
1828
1829 // Check visibility.
1830 if (flowData.water > 0.f) { waterInMultiphaseFlow = true; }
1831 if (flowData.oil > 0.f) { oilInMultiphaseFlow = true; }
1832 if (flowData.gas > 0.f) { gasInMultiphaseFlow = true; }
1833
1834 // Calculate liquid height in the pipe based on the percentage of the pipe area.
1835 float h1 = crosssectionAreaToHeight(flowData.water);
1836 float h2 = crosssectionAreaToHeight(flowData.water + flowData.oil);
1837
1838 flowData.heightWaterOil = -1 * (h1 - 0.5f) * 2.f;
1839 flowData.heightOilGas = -1 * (h2 - 0.5f) * 2.f;
1840
1841 flowData.widthWaterOil = glm::sqrt(1 - (1 - h1 * 2) * (1 - h1 * 2));
1842 flowData.widthOilGas = glm::sqrt(1 - (1 - h2 * 2) * (1 - h2 * 2));
1843
1844 // Check for inverted crosssection (water always at the bottom).
1845 glm::vec3 testVec = controlPoint.rotation * glm::vec3(1, 0, 0);
1846 controlPoint.invertedFlow = (testVec.z > 0.f);
1847
1848 // Check for annular flow.
1849 bool isAnnularFlow = (std::fabs(controlPoint.direction.z) >= ANNULAR_FLOW_INCLINATION);
1850 controlPoint.annularFlow = isAnnularFlow;
1851 }
1852
1853 // Interpolate control data.
1854 finalizeControlPoints(controlPoints);
1855
1856 // Reset geometry AABB.
1857 geometryMin = glm::vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
1858 geometryMax = glm::vec3(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
1859
1860 // Update all meshes.
1861 for (int i = MeshType::First; i <= MeshType::Last; i++)
1862 {
1863 auto liquidType = MeshType(i);
1864
1865 // Get mesh.
1866 Mesh* mesh = context->meshManager->get(meshEntity[i]->getComponent<MeshComponent>()->meshHandle);
1867
1868 // Update material.
1869 updateMaterial(meshEntity[i], liquidType);
1870
1871 bool visible = true;
1872 switch (liquidType)
1873 {
1874 case MeshType::StratifiedWater:
1875 case MeshType::AnnularWater:
1876 visible = showWater && waterInMultiphaseFlow && visualizeMultiphaseFlow;
1877 break;
1878 case MeshType::StratifiedOil:
1879 case MeshType::AnnularOil:
1880 visible = showOil && oilInMultiphaseFlow && visualizeMultiphaseFlow;
1881 break;
1882 case MeshType::StratifiedGas:
1883 case MeshType::AnnularGas:
1884 visible = showGas && gasInMultiphaseFlow && visualizeMultiphaseFlow;
1885 break;
1886 case MeshType::Wall:
1887 visible = (wallValues.size() == trajectoryPoints.size() || !visualizeMultiphaseFlow);
1888 if (visible)
1889 {
1890 // Modify material based on the visibility of multiphase flow mesh.
1891 auto meshRenderComp = meshEntity[i]->getComponent<MeshRenderComponent>();
1892 auto material = meshRenderComp->material;
1893 material->setOption("CullMode", visualizeMultiphaseFlow ? "Back" : "Front");
1894
1895 material->setVariant("EnableLighting", !visualizeMultiphaseFlow);
1896 material->setVariant("LightModel", visualizeMultiphaseFlow ? "BaseColor" : "Phong");
1897 }
1898 break;
1899 case MeshType::Shadow:
1900 {
1901 visible = (trajectoryPoints.size() == radius.size());
1902 }
1903 break;
1904 default:
1905 assert(false && "Unknown liquid type");
1906 break;
1907 }
1908
1909 if (visible)
1910 {
1911 generateMultiphaseFlowMesh(mesh, controlPoints, liquidType);
1912 }
1913 else
1914 {
1915 mesh->clear();
1916 }
1917 }
1918
1919 // Update grid, indicator ring and text.
1920 updateGroundGrid();
1921 updateAxisAnnotations();
1922 updateIndicatorRing(trajectory);
1923}
1924
1925void MultiphaseFlowComponent::update()
1926{
1927 if (trajectoryPoints.size() < 2 || trajectoryPoints.size() != radius.size())
1928 {
1929 return;
1930 }
1931
1932 if (showAxialExtents != currentShowAxialExtents ||
1933 showShadow != currentShowGrid ||
1934 showGrid != currentShowGrid ||
1935 centerMesh != currentCenterMesh ||
1936 showIndicatorRing != currentShowIndicatorRing)
1937 {
1938 currentShowAxialExtents = showAxialExtents;
1939 currentShowGrid = showShadow;
1940 currentShowGrid = showGrid;
1941 currentCenterMesh = centerMesh;
1942 currentShowIndicatorRing = showIndicatorRing;
1943 updateNeeded = true;
1944 }
1945
1946 if (updateNeeded)
1947 {
1948 updateFontSize();
1949 updateMeshes();
1950 }
1951
1952 updateShadowVisibility();
1953
1954 // Set text horizontal alignment for Y an Z axis based on camera position.
1955 // Avoid text being displayed under the 3D model of a pipe.
1956 auto* cam = context->cameraSystem->getMainCamera();
1957 auto transform = static_cast<TransformComponent*>(cam->getComponent<TransformComponent>());
1958 auto* zTextComp = textEntity[AnnotationAxis::Z]->getComponent<TextComponent>();
1959 zTextComp->horizontalJustification = (transform->position.y <= 0 ? HorizontalJustification::Left : HorizontalJustification::Right);
1960
1961 auto* yMinTextComp = textEntity[AnnotationAxis::YMin]->getComponent<TextComponent>();
1962 auto* yMaxTextComp = textEntity[AnnotationAxis::YMax]->getComponent<TextComponent>();
1963 auto* yTextComp = textEntity[AnnotationAxis::Y]->getComponent<TextComponent>();
1964
1965 if (yMinTextComp != nullptr && yMaxTextComp != nullptr && yTextComp != nullptr)
1966 {
1967 yMinTextComp->horizontalJustification = (transform->position.y > 0 ? HorizontalJustification::Left : HorizontalJustification::Right);
1968 yMaxTextComp->horizontalJustification = (transform->position.y > 0 ? HorizontalJustification::Left : HorizontalJustification::Right);
1969 yTextComp->horizontalJustification = (transform->position.y <= 0 ? HorizontalJustification::Left : HorizontalJustification::Right);
1970 }
1971}
1972
1973void MultiphaseFlowComponent::calculateNormals(std::vector<Vertex>& vertexBuffer, std::vector<unsigned int>& indexBuffer)
1974{
1975 for (size_t index = 0; index < indexBuffer.size() / 3; index++)
1976 {
1977 auto i0 = indexBuffer[index * 3 + 0];
1978 auto i1 = indexBuffer[index * 3 + 1];
1979 auto i2 = indexBuffer[index * 3 + 2];
1980
1981 auto& p0 = vertexBuffer[i0];
1982 auto& p1 = vertexBuffer[i1];
1983 auto& p2 = vertexBuffer[i2];
1984
1985 auto p = p1.position - p0.position;
1986 auto q = p2.position - p0.position;
1987 auto s = p2.position - p1.position;
1988
1989 if (glm::length(p) > 0.0001f &&
1990 glm::length(q) > 0.0001f &&
1991 glm::length(s) > 0.0001f)
1992 {
1993 auto n = glm::cross(p, q);
1994 n = glm::normalize(n);
1995
1996 p0.normalAndOpacity.x = n.x;
1997 p0.normalAndOpacity.y = n.y;
1998 p0.normalAndOpacity.z = n.z;
1999
2000 p1.normalAndOpacity.x = n.x;
2001 p1.normalAndOpacity.y = n.y;
2002 p1.normalAndOpacity.z = n.z;
2003
2004 p2.normalAndOpacity.x = n.x;
2005 p2.normalAndOpacity.y = n.y;
2006 p2.normalAndOpacity.z = n.z;
2007 }
2008 }
2009}
void setChanged()
Sets the component to the ComponentFlags::Changed state with carry.
Definition: Component.h:202
ComponentType * getComponent() const
Definition: Component.h:159
class Entity * getContainer() const
Get the container currently owning this component instance.
Definition: Component.h:151
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
EntityPtr createChildEntity(const StringView &type, ComponentModel::Entity *parent, const StringView &name=StringView())
Create a new Entity, parenting it to the given parent.
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
MeshHandle meshHandle
Handle to a Mesh resource to use when rendering.
Definition: MeshComponent.h:29
Renders the contents of a MeshComponent using the given materials.
MaterialInstanceHandle material
Material used to render the mesh.
constexpr void setVisible(bool visible)
Set the specific visibility.
Contains information on how the entity behaves in the scene.
Renders the given text(s) by generating sprites.
Definition: TextComponent.h:77
std::vector< glm::vec3 > positions
Offset positions for each of the strings in texts.
HorizontalJustification horizontalJustification
Horizontal justification of the text.
std::vector< std::string > texts
A set of text strings to render.
Definition: TextComponent.h:91
PositionMode positionMode
Positioning mode of the text.
glm::vec4 color
Single color value to apply to all text strings.
FontHandle fontHandle
Handle to the font resource to use when rendering text.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Field definition describing a single data member of a data structure.
Definition: Field.h:68
Simple method definition.
Definition: Method.h:72
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ World
Position given in world space coordinates.
std::shared_ptr< ComponentModel::Entity > EntityPtr
Smart pointer for Entity access.
Definition: EntityPtr.h:12
@ Right
The rightmost character is placed to the left of the screen position.
@ Left
The leftmost character is placed to the right of the screen position.
@ Center
Center the text on the screen position.
@ Top
Align the text towards the top, making the position the baseline of the text.
@ Color
Use color property from point set if present, fallback to basecolor.
Contains geometry calculations and generation.
Contains reflection support.
Definition: Component.h:11
@ Default
Default material.
Definition: Material.h:45
void setVec4Property(const VariableKey key, glm::vec4 value)
Set the vec4 property with the given key to value.
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
void setIndexes(std::span< const uint32_t > collection)
Set the index data to the collection given.
Definition: Mesh.h:596
void clear()
Clear all data from the Mesh, returning it to its initial non-indexed state with no streams and no su...
Definition: Mesh.cpp:27
void setVertexData(Element *elements, size_t count)
Set vertex data.
Definition: Mesh.h:754
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
@ LineList
List of lines.
Definition: Common.h:120
Represents an unique name.
Definition: Name.h:70