Skip to content

Tutorial 1 Bouncing Cube

Jacob Austin edited this page May 16, 2020 · 2 revisions

Goals

Now that the library is installed, you can begin setting up your first project. We will be building a simple cube simulation with a global plane constraint (causing it to bounce).

CMake

The Titan library is a CMake project, and is easiest to include in your project using CMake. To include the library with CMake, create a CMakeLists.txt file in your project directory. That CMakeLists.txt file determines your project structure, handles linking and executable creation, and all other build settings. CMake is essentially a more portable and flexible version of the usual Makefiles. At a minimum, your CMakeLists.txt file should contain:

cmake_minimum_required(VERSION 3.10)
project(myproject)

set(CMAKE_CXX_STANDARD 17)

add_executable(mytarget main.cpp)
find_package(Titan CONFIG REQUIRED)
target_link_libraries(mytarget PRIVATE Titan)

where myproject and mytarget can be replaced with more descriptive names. On Linux, you may need to change the project(myproject) line to project(myproject LANGUAGES CXX CUDA) due to some issues.

Headers

All of the library headers can be found in <Titan/sim.h>, which is exported to a global location when the library is built and installed using vcpkg. You will need to include a path to the vcpkg installation in the project, as directed in the installation guide.

To start:

#include <Titan/sim.h>

and since everything in Titan is namespaced in titan, we can either refer to variables as titan::Simulation or simply include using namespace titan;.

Setting up the cube

We create the cube by adding individual masses. They are positioned at the corners of a cube spanning [0, 1] x [0, 1] x [0, 1].

using namespace titan;

int main() {
    Simulation sim; // create the basic simulation object.
    Mass * m1 = sim.createMass(Vec(0, 0, 1));
    Mass * m2 = sim.createMass(Vec(1, 0, 1));
    Mass * m3 = sim.createMass(Vec(1, 1, 1));
    Mass * m4 = sim.createMass(Vec(0, 1, 1));
    Mass * m5 = sim.createMass(Vec(0, 0, 2));
    Mass * m6 = sim.createMass(Vec(1, 0, 2));
    Mass * m7 = sim.createMass(Vec(1, 1, 2));
    Mass * m8 = sim.createMass(Vec(0, 1, 2));

Then we add springs connecting the masses at the corners.

    Spring * s1 = sim.createSpring(m1, m2);
    Spring * s2 = sim.createSpring(m2, m3);
    Spring * s3 = sim.createSpring(m3, m4);
    Spring * s4 = sim.createSpring(m4, m1);

    Spring * s5 = sim.createSpring(m1, m5);
    Spring * s6 = sim.createSpring(m5, m8);
    Spring * s7 = sim.createSpring(m8, m4);

    Spring * s8 = sim.createSpring(m8, m7);
    Spring * s9 = sim.createSpring(m5, m6);

    Spring * s10 = sim.createSpring(m2, m6);
    Spring * s11 = sim.createSpring(m6, m7);
    Spring * s12 = sim.createSpring(m3, m7);

As expected, we have added 12 springs, one for each side of the cube. The library also includes a createCube function, which more or less performs this computation for you (with a fully connected spring).

Adding a plane constraint

Next, we'd like to constraint the plane to stay above the xy-plane, which we can do with the sim.createPlane command. The function takes two arguments, a Vec object (a, b, c) which specifies the normal vector to the plane, and a double d which specifies the offset (i.e. the plane is defined as ax + by + cz = d). All masses are then constrained to lie in the positive half-space defined by the plane and its orientation.

    sim.createPlane(Vec(0, 0, 1), 0); // our plane has a unit normal in the z-direction, with 0 offset.

Starting the simulation

The simulation runs asynchronously, so the user can continue to run commands while it is running. In this case, we do not need to make modifications during the run, so we can just start the simulation and let it run until it is finished. The setBreakpoint command will stop the simulation at the specified time, but will not otherwise block the CPU.

    double runtime = 10.0;
    sim.setBreakpoint(runtime);
    sim.start();
}

The finished program:

The full program now looks like this:

#include <Titan/sim.h>

using namespace titan;

int main() {
    Simulation sim; // create the basic simulation object.
    Mass * m1 = sim.createMass(Vec(0, 0, 1));
    Mass * m2 = sim.createMass(Vec(1, 0, 1));
    Mass * m3 = sim.createMass(Vec(1, 1, 1));
    Mass * m4 = sim.createMass(Vec(0, 1, 1));
    Mass * m5 = sim.createMass(Vec(0, 0, 2));
    Mass * m6 = sim.createMass(Vec(1, 0, 2));
    Mass * m7 = sim.createMass(Vec(1, 1, 2));
    Mass * m8 = sim.createMass(Vec(0, 1, 2));

    Spring * s1 = sim.createSpring(m1, m2);
    Spring * s2 = sim.createSpring(m2, m3);
    Spring * s3 = sim.createSpring(m3, m4);
    Spring * s4 = sim.createSpring(m4, m1);

    Spring * s5 = sim.createSpring(m1, m5);
    Spring * s6 = sim.createSpring(m5, m8);
    Spring * s7 = sim.createSpring(m8, m4);

    Spring * s8 = sim.createSpring(m8, m7);
    Spring * s9 = sim.createSpring(m5, m6);

    Spring * s10 = sim.createSpring(m2, m6);
    Spring * s11 = sim.createSpring(m6, m7);
    Spring * s12 = sim.createSpring(m3, m7);

    sim.createPlane(Vec(0, 0, 1), 0); // our plane has a unit normal in the z-direction, with 0 offset.

    double runtime = 10.0;
    sim.setBreakpoint(runtime);
    sim.start();
}

The same thing can be achieved more or less with the built in sim.createCube(const Vec & center, const Vec & sides) method, i.e.

#include <Titan/sim.h>

using namespace titan;

int main() {
    Simulation sim;
    sim.createCube(Vec(0, 0, 10), 2);
    sim.createPlane(Vec(0, 0, 1), 0);
    sim.setBreakpoint(10.0);
    sim.start();
}

Result