Skip to content

Commit a07d7e9

Browse files
authored
More example code added to UG Simulation scenario (#649)
1 parent 4e24f11 commit a07d7e9

1 file changed

Lines changed: 305 additions & 0 deletions

File tree

user-guide/modules/ROOT/pages/task-simulation.adoc

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Creating a real-time simulation of objects or processes involves various aspects
1616
* <<Simulate Movement in 3D Space>>
1717
* <<Add Collision Detection>>
1818
* <<Deformation on Impact>>
19+
* <<Blowing in the Wind>>
1920
* <<See Also>>
2021
2122
== Libraries
@@ -570,6 +571,310 @@ Time: 8.25219 sec | Inc: 0.219674 sec | Box Position: Min(3.95642, 2.47566, -1.6
570571
571572
----
572573

574+
== Blowing in the Wind
575+
576+
Let's look at something more natural and how it might be simulated. Say you have a real-time simulation that is out in the open, and you need to realistically model the effects of wind on your objects.
577+
578+
This is one of those cases where a little mathematics goes a long way. Real wind is not random. It has structure across multiple time scales. Meteorologists often think of wind as having four components:
579+
580+
. Mean wind (the forecast strength and direction)
581+
. Long-period variation (30 to 120 second changes)
582+
. Gusts (2 to 10 second bursts)
583+
. Turbulence (small, rapid fluctuations)
584+
585+
Avoid the temptation of using a random number every frame, you can model each component separately.
586+
587+
Say the weather forecast is for a 10 mile an hour wind, from the NNW, gusting to 20 mph. Let's look at the four components.
588+
589+
. Mean wind : this should be converted to a vector (speed x direction), and direction is fixed at NNW (337.5 degrees).
590+
. Long-period variation : Real wind slowly strengthens and weakens. A sine wave works surprisingly well, something like:
591+
+
592+
[source,cpp]
593+
----
594+
double long_period(double t)
595+
{
596+
return 1.5 *
597+
sin(2.0 * pi * t / 90.0);
598+
}
599+
----
600+
601+
. Gusts : Gusting to 20 mph according to our forecast. We do not want sudden jumps, instead smoother pulses.
602+
+
603+
[source,cpp]
604+
----
605+
double gust(double t)
606+
{
607+
return exp(-x*x);
608+
}
609+
----
610+
+
611+
To be natural, each gust might last 4 to 8 seconds.
612+
613+
. Turbulence : We need to avoid obvious repetition, which we can manage with several sine waves, though the frequencies are unrelated:
614+
+
615+
[source,cpp]
616+
----
617+
double turbulence(double t)
618+
{
619+
return
620+
0.4*sin(5.3*t)
621+
+ 0.3*sin(8.9*t)
622+
+ 0.2*sin(13.7*t);
623+
}
624+
----
625+
626+
Total wind speed will now look something like this:
627+
628+
[source,cpp]
629+
----
630+
double wind_speed(double t)
631+
{
632+
return mean_wind + long_period(t) + gust_strength(t) + turbulence(t);
633+
}
634+
----
635+
636+
Wind direction is of course not _fixed_, but will wander left and right, perhaps plus or minus 5 degrees:
637+
638+
[source,cpp]
639+
----
640+
double wind_direction(double t)
641+
{
642+
return mean_direction + 4*sin(t/40) + 2*sin(t/11) + 1*sin(7*t);
643+
}
644+
----
645+
646+
Using the constants available in boost:math[], and the units from boost:units[], a first cut at a wind simulator might look like the following. Here we have three types of object - leaves, paper, tumbleweed - each with certain physical properties that determine how quickly the wind will move them.
647+
648+
[source,cpp]
649+
----
650+
#include <boost/math/constants/constants.hpp>
651+
652+
#include <boost/units/systems/si.hpp>
653+
#include <boost/units/io.hpp>
654+
655+
#include <cmath>
656+
#include <iomanip>
657+
#include <iostream>
658+
#include <vector>
659+
660+
namespace si = boost::units::si;
661+
662+
using boost::units::quantity;
663+
664+
constexpr double pi =
665+
boost::math::constants::pi<double>();
666+
//------------------------------------------------------------
667+
// Small-scale turbulence.
668+
//
669+
// Combines several sine waves of different frequencies to
670+
// produce smooth, non-repeating fluctuations.
671+
//------------------------------------------------------------
672+
673+
double turbulence(double t)
674+
{
675+
return
676+
677+
0.30 * std::sin(7.3 * t)
678+
679+
+ 0.20 * std::sin(11.7 * t + 0.8)
680+
681+
+ 0.12 * std::sin(17.9 * t + 2.1)
682+
683+
+ 0.08 * std::sin(29.4 * t);
684+
}
685+
//------------------------------------------------------------
686+
// Wind speed as a function of time.
687+
//
688+
// Returns metres per second.
689+
//------------------------------------------------------------
690+
691+
quantity<si::velocity>
692+
wind_speed(quantity<si::time> t)
693+
{
694+
// Mean wind = 4.47 m/s (≈10 mph)
695+
696+
quantity<si::velocity> mean =
697+
4.47 * si::meters_per_second;
698+
699+
// Smooth variation
700+
701+
double variation =
702+
0.9 *
703+
std::sin(
704+
2.0 * pi *
705+
t.value() / 60.0);
706+
707+
// Gaussian gust
708+
709+
double x =
710+
(t.value() - 30.0) / 5.0;
711+
712+
double gust =
713+
4.5 * std::exp(-x * x);
714+
715+
double turbulent =
716+
turbulence(t.value());
717+
718+
return
719+
mean
720+
+ variation * si::meters_per_second
721+
+ gust * si::meters_per_second
722+
+ turbulent * si::meters_per_second;
723+
}
724+
725+
struct Object
726+
{
727+
std::string name;
728+
729+
quantity<si::mass> mass;
730+
731+
double drag;
732+
733+
double friction;
734+
735+
quantity<si::length> position;
736+
737+
quantity<si::velocity> velocity;
738+
};
739+
740+
int main()
741+
{
742+
std::vector<Object> objects =
743+
{
744+
{
745+
"Leaf",
746+
747+
0.005 * si::kilograms,
748+
749+
0.90,
750+
751+
0.02,
752+
753+
0.0 * si::meters,
754+
755+
0.0 * si::meters_per_second
756+
},
757+
758+
{
759+
"Paper",
760+
761+
0.010 * si::kilograms,
762+
763+
0.80,
764+
765+
0.05,
766+
767+
0.0 * si::meters,
768+
769+
0.0 * si::meters_per_second
770+
},
771+
772+
{
773+
"Tumbleweed",
774+
775+
0.300 * si::kilograms,
776+
777+
0.60,
778+
779+
0.08,
780+
781+
0.0 * si::meters,
782+
783+
0.0 * si::meters_per_second
784+
}
785+
};
786+
787+
quantity<si::time> dt =
788+
1.0 * si::seconds;
789+
790+
std::cout
791+
<< std::fixed
792+
<< std::setprecision(2);
793+
794+
for (quantity<si::time> t = 0.0 * si::seconds;
795+
t <= 60.0 * si::seconds;
796+
t += dt)
797+
{
798+
auto wind = wind_speed(t);
799+
800+
std::cout
801+
<< "\nTime = "
802+
<< t
803+
<< " Wind = "
804+
<< wind
805+
<< "\n";
806+
807+
for (auto& o : objects)
808+
{
809+
//------------------------------------------------
810+
// Very simple physics
811+
//------------------------------------------------
812+
813+
quantity<si::force> force =
814+
wind * o.drag *
815+
o.mass /
816+
(1.0 * si::seconds);
817+
818+
// Simple damping
819+
820+
force -=
821+
o.friction *
822+
o.velocity *
823+
o.mass /
824+
(1.0 * si::seconds);
825+
826+
quantity<si::acceleration>
827+
acceleration =
828+
force / o.mass;
829+
830+
o.velocity +=
831+
acceleration * dt;
832+
833+
o.position +=
834+
o.velocity * dt;
835+
836+
std::cout
837+
<< std::setw(12)
838+
<< o.name
839+
<< " Position = "
840+
<< o.position
841+
<< " Velocity = "
842+
<< o.velocity
843+
<< '\n';
844+
}
845+
}
846+
}
847+
848+
----
849+
850+
Run this program and you will get a lot of numbers!
851+
852+
[source,text]
853+
----
854+
...
855+
856+
Time = 58.00 s Wind = 4.63 m s^-1
857+
Leaf Position = 6073.07 m Velocity = 155.59 m s^-1
858+
Paper Position = 3533.71 m Velocity = 70.33 m s^-1
859+
Tumbleweed Position = 1911.14 m Velocity = 32.32 m s^-1
860+
861+
Time = 59.00 s Wind = 4.37 m s^-1
862+
Leaf Position = 6229.48 m Velocity = 156.41 m s^-1
863+
Paper Position = 3604.02 m Velocity = 70.31 m s^-1
864+
Tumbleweed Position = 1943.50 m Velocity = 32.36 m s^-1
865+
866+
Time = 60.00 s Wind = 4.06 m s^-1
867+
Leaf Position = 6386.41 m Velocity = 156.94 m s^-1
868+
Paper Position = 3674.07 m Velocity = 70.04 m s^-1
869+
Tumbleweed Position = 1975.71 m Velocity = 32.20 m s^-1
870+
----
871+
872+
For extra realism, treat gusts like moving weather cells instead of global events. Imagine a gust as a "blob" of stronger wind drifting across the landscape. The gust has a position, radius, speed, and intensity. The result is that waves of motion travel naturally through your environment, much like you see when watching a real forest in a breeze or wind gusts crossing a lake. It looks dramatically more realistic than having every object receive the same gust at the same instant, and the underlying mathematics is still quite simple: each object samples the wind field at its own position.
873+
874+
== Next Steps
875+
876+
It is quite difficult to assess the "naturalness" of a physical process just by looking at numbers. To really assess how well our wind simulator works, we would need to have graphical output (not necessarily a full-featured 3D rendering) where we could visualize the different objects blowing across the terrain, answering the question "does it look right?".
877+
573878
It is good practice when designing a simulation of real-world activity to clearly define what is to be simulated and what is not. All simulations are simplifications to an extent, though they do tend to be large and challenging programs to write. A complex simulation might have several processes running on different threads. For a sample of multi-threading code, refer to xref:task-parallel-computation.adoc[].
574879

575880
== See Also

0 commit comments

Comments
 (0)