Samples

In this section we'll look into the premade samples (apePhysicsSimulation). We have 6 configuration, and each can give you some hints about the proper way of using the plugin.

Basic example

This is our first sample configuration, showing a simple scene with a 6x6x6 cube array. You can find something very similar to it in Bullet's sample browser, and that's not a coincidence. The sample shows us how well the gravity and collision resolution working within BulletPhysics engine. The following code shows you how the cubes were generated.

for (int i = 0; i < m_cubesArraySize[0]; i++)
{
	std::stringstream ssi;
	ssi << i;
	for (int j = 0; j < m_cubesArraySize[1]; j++)
	{
		std::stringstream ssj;
		ssj << j;
		for (int k = 0; k < m_cubesArraySize[2]; k++)
		{
			std::stringstream ssk;
			ssk << k;

			if (auto boxNode = mpSceneManager->createNode("xxboxNode" + ssi.str() + ssj.str() + ssk.str()).lock())
			{
				// material for box
				std::shared_ptr<ape::IManualMaterial> boxMaterial;
				if (boxMaterial = std::static_pointer_cast<ape::IManualMaterial>(mpSceneManager->createEntity("xxboxMaterial" + ssi.str() + ssj.str() + ssk.str(), ape::Entity::MATERIAL_MANUAL).lock()))
				{
					boxMaterial->setDiffuseColor(ape::Color(float(i + 1) / float(m_cubesArraySize[0]), float(j + 1) / float(m_cubesArraySize[1]), float(k + 1) / float(m_cubesArraySize[2])));
					boxMaterial->setSpecularColor(ape::Color(float(i) / float(m_cubesArraySize[0] - 1), float(j) / float(m_cubesArraySize[1] - 1), float(k) / float(m_cubesArraySize[2] - 1)));
					boxMaterial->setAmbientColor(ape::Color(float(i + 1) / float(m_cubesArraySize[0]), float(j + 1) / float(m_cubesArraySize[1]), float(k + 1) / float(m_cubesArraySize[2])));
				}
				
				boxNode->setPosition(m_cubesInitPos + ape::Vector3(float(i * 10 - m_cubesArraySize[0] * 10 / 2 + 5), float(j * 10), float(k * 10 - m_cubesArraySize[2] * 10 / 2 + 5)));
	
				if (auto box = std::static_pointer_cast<ape::IBoxGeometry>(mpSceneManager->createEntity("xxbox" + ssi.str() + ssj.str() + ssk.str(), ape::Entity::GEOMETRY_BOX).lock()))
				{
					box->setParameters(ape::Vector3(10, 10, 10));
					box->setParentNode(boxNode);
					box->setMaterial(boxMaterial);

					if (auto boxBody = std::static_pointer_cast<ape::IRigidBody>(mpSceneManager->createEntity("xxboxBody" + ssi.str() + ssj.str() + ssk.str(), ape::Entity::RIGIDBODY).lock()))
					{
						boxBody->setGeometry(box);
						boxBody->setParentNode(boxNode);
						boxBody->setRestitution(0.6f);
						boxBody->setDamping(0.01, 0.01);
						boxBody->setFriction(0.5,0.1,0.1);

						m_bodies.push_back(boxBody);
					}
				}
			}
		}
	}
}

This way you can get a big cube consists of several smaller cubes. When the plugin starts working it loads those cubes into the dynamics world, then applies the gravity acceleration on them in each frame. Thus you'll see cubes falling from an initial position and colliding with the ground and each other. We recommend to try out tweaking the parameters within the makeCubeArray() function (you can find it in apePhysicsSimulationiScene.cpp), so to see how can they modify the simulation, by changing the physical material of the body.

Terrain

In the next sample we have something like a terrain, what is made with GEOMETRY_INDEXEDFACESET. Since this has a static body, here we can use concave triangle mesh for the collision resulution. The initial state is almost the same as in the previus configuration, some objects are dropped out of the sky to collide with the terrain. There's also a Suzanne mesh, so you can see that colliders using CONVEX_HULL working pretty well.

Note that after you made a GEOMETRY_INDEXEDFACESET by hand there's nothing special about getting a rigid body for it, simply just use the setGeometry function as you would do it while you working with primitives. Over and above, you may not want convex hull collider for your terrain, but if you do for some reason, the code you need to use is terrain->setGeometry(terrainGeometry,ape::RigidBodyColliderType::CONVEX_HULL) . That way the plugin override the auto adjustment, and gets you a convex hull. Maybe it's worth a try to hack the sample's original code with it, and see what's happening.

Robot arm

Here we have a simulation shows a robot arm pushing away spheres and boxes. The main purpose of this sample, is to show an example where Bullet doesn't modify the body transform, but an other plugin does. Here the robot arm is just a static object in Bullet's dynamicsWorld, thus gravity and other bodies on the scene don't affect on the arm, only the arm can push the other bodies.

At the same time the arm's wrist anges are set by a httpSimulatior through the apeNodeJsPlugin in every frame, so eventough Bullet doesn't have a word about how the body moves, it's still movable, with external controlling. When a body has static type in ApertusVR, it means that the responsibility of the node's position and orientation is also at ApertusVR's side. Hence the plugin reads the node's position and orienation, and use it for positioning and rotating Bullet's internal body transform. So this is like the opposite way of how the plugin working with dynamic rigid bodies.

Water game

The water game configuration is something that you can use as an example for making a game with a user, who can move the rigid body with user input, but the physics engine also affects on the user.

For reaching this functionality, we have two node related to the user. One that store user's input data (userNode), and one that actually positioning the user (userBodyNode). In other words, the userNode is acting like an input system for the plugin, and userBodyNode is the output made by the plugin.

After userBodyNode got the position and orientation from the plugin, passes it to the headNode, since userBodyNode is the parent node of headNode, wich is responsible for the camera and the screen texts. All in all, if you want user in your ApertusVR application, you need the two node: userNode, and userBodyNode. First you can create the userNode with ape::UserInputMacro. Then Bullet plugin will get the information that there's a userNode, but you have to create userBodyNode as well. This doesn't make any difference from making a simple node, except it's name has to be "userBodyNode". Otherwise the Bullet plugin won't know wich node needs to be treated as userBodyNode. At last don't forget to make a body and attach the userBodyNode to it. This whole procedure in the water game looks something like the code below. Of course, you can find the entire code of the game in the ApertusVR/plugins/waterGame folder.

mpapeUserInputMacro = ape::UserInputMacro::getSingletonPtr();
if (auto headNode = mpapeUserInputMacro->getHeadNode().lock())
{
	if (auto userBodyNode = mpSceneManager->createNode("userBodyNode").lock())
	{
		headNode->setParentNode(userBodyNode);
		mGameManager = new WaterGame::GameManager(mpapeUserInputMacro->getUserNode(), userBodyNode);

		if (auto userGeometry = std::static_pointer_cast<ISphereGeometry>(mpSceneManager->createEntity("userGeometrySphere", ape::Entity::Type::GEOMETRY_SPHERE).lock()))
		{
			userGeometry->setParameters(20.0f, ape::Vector2(1, 1));
			if (auto userBody = std::static_pointer_cast<IRigidBody>(mpSceneManager->createEntity("userBody", ape::Entity::Type::RIGIDBODY).lock()))
			{
				userBody->setParentNode(userBodyNode);
				userBody->setToDynamic(1.0f);
				userBody->setBouyancy(true);
				userBody->setGeometry(userGeometry);
			}
		}
		mUserBodyNode = userBodyNode;
	}
}

Water1 and water2

These two samples are only made for presentating the bouyancy force generator the plugin contains. We made two configuration for simulating water. One came with a high level water material, and the other is just a simple box acting like it's the water on the scene.

In the sample there are several object loaded with Assimp, so we can take a look how does it work with physics properties.

{
  "assets": [
    {
      "file": "/plugins/assimpAssetLoader/resources/Duck.gltf",
      "mergeAndExportMeshes": false,
      "scale": [ 0.6, 0.6, 0.6 ],
      "position": [ 0, 50, 0 ],
      "orientation": [ 1, 0, 0, 0 ],
      "regenerateNormals": true,
      "rootNodeName": "duckNode",
      "physics": {
        "mass": 1.0,
        "restitution": 0.2,
        "friction": 0.5,
        "rollingFriction": 0.1,
        "spinningFriction": 0.1,
        "linearDamping": 0.01,
        "angularDamping": 0.05,
        "colliderType": "auto",
        "bouyancyEnabled": true
      }
    },
    //more assets...
  }

Inside the physics property, there are those physics parameters you must be familiar with, if you read the related page. If you accidentally skipped it, please take a look at it.

Returning to the water simulation, if you want to calculate bouyancy force for your body, here you only need to set the "bouyancyEnabled" property true. That way Bullet plugin knows your object is in liquid, and you can set the water's properties in the apeBulletPlugin.json config file. The plugin calcultes bouyancy with a brute force formula, wich is only using the body's approximated volume (volume of the bounding sphere), and the distance between center of mass and liquid surface. However you can see on the demo videos that most of time it works just fine.

Last updated