Cupboard Configuration Tutorial

Introduction

In this tutorial you will learn how to use TurnTool to create a simple cupboard configuration tool, in a few simple steps. You will learn how to load and merge models from different files, change their material and move them using the mouse.

It’s all really easy, so let’s get started right away by looking at what we will be making.

What we'll be making

Getting started

TurnTool is controlled via JavaScript using a simple scripting system which allows for lots of different possibilities. To keep our example easy to follow, it only uses a few of the scripting commands available in TurnTool. It could easily be expanded to include more functionality. Apart from a few initialization functions most of the work is done in JavaScript using something similar to this:

doTNTCommand( 'Node(“floor”).SetWorldPosition(0,0,10)' );

Which would move the floor to position 0,0,10 in the world. Note that in TurnTool (X,Y,Z) denote the axii (right, forward, up) in that order, just like in the 3ds max. So we could say that our script above would move the floor 10 units above the origin of the world.

In addition to script commands which are run by using the doTNTCommand function, TurnTool sends events to JavaScript whenever certain events occur. For instance once our initial scene has loaded the following function will be called:

function OnSceneLoad(fileName)
{
}

So inside this function we could, and will, add some code that is triggered once the scene has loaded.

That’s a very brief introduction to how scripting is handled in TurnTool, but we’ll learn more in the next few sections.

Let’s get going!

Loading the scene

The first thing you want to do is load a 3D scene in TurnTool. If you are exporting a scene from 3ds max, you can simply set it to export a html file as well, which will automatically load the scene, and you’ve already saved some time.

But let's see what the command for loading a scene looks like:

	doTNTCommand('Viewer().LoadScene("scene.tnt")');

That should be largely self-explanatory, but it is worth noting that once the scene has loaded it will call the OnSceneLoad function, so let's take a look at that:

function OnSceneLoad(fileName)
{
	// step 1: setup mouse over events
	doTNTCommand('Node("*drawer*").SetMeshMouseOverEvent(1).SetMeshOcclusion(1)');
	doTNTCommand('Node("cupboard").SetMeshOcclusion(1)');

	// step 2: set the default design of the drawers
	MergeModels('drawer','front','files/tnt_drawer_front_b.tnt');
}

This is what OnSceneLoad looks like in our example, in the following sections you will learn what the different parts do.

What you will notice is if you remove the second step of the function, there will be no fronts on the drawers. That's because they are merged in as seperate models, and aren't part of the original scene file. Why do it like that? We'll see in the next section.

Merging other models

It’s very nice to be able to load a single scene, but often it can be a good solution to split a scene into different parts in order to maximize re-use and flexibility. So what we do is we merge in the drawer fronts and handles, this way we only need a few different files instead of having a lot of extra (hidden) models in the scene.

So what we do is merge the different models into the scene, and place them where we want them to be. We do this using the following function:

function MergeModels( parentName, modelName, fileName )
{
	
	// Remove current models
	doTNTCommand('Node("*'+modelName+'*").Destroy()');

	// Load new models if a file is defined	
	if (fileName != undefined)
	{
		// Get number of nodes in scene
		var count = parseInt(doTNTCommand('GetNodeCount()'));
		for (var i = 0; i < count; i++)
		{
			// Get the name of the node
			var nodeName = doTNTCommand('Node('+i+').GetName()');
			
			// If the node name matches parentName, we'll merge the model
			if (nodeName.indexOf(parentName) != -1)
			{
				// Create a new node with modelName
				// Set its parent
				// And merge the model
				doTNTCommand('CreateNode("'+modelName+i+'").SetParent("'+nodeName+'").SetExternalResourcePath("'+fileName+'")');
			}
		}
	}		
}

Now if you just need to merge a model in, and never change it again, it can be a lot simpler, but to allow for some flexibility we use this function. It should be generic enough to be used in many cases, so let’s go through what this function actually does:

As noted above, you may simply merge a model, if you don't need to place it in a particular spot, you can just use:

doTNTCommand('CreateNode("MergedModel").SetExternalResourcePath("myModel.tnt")');

Once a model is merged TurnTool will tell us by calling the function “OnExternalResourceMerge” so we need to add a bit of fun to that function to make sure that the correct materials are applied to our merged models. Let’s see what that looks like:

function OnExternalResourceMerge(uniqueNodeName, nodeIndex, fileName)
{
	// once the model has been downloaded, let's apply the correct materials to it
	SetDrawerMaterial( drawerMaterial );
	SetDrawerFinish( drawerFinish );
}

See the next section for what SetDrawerMaterial and SetDrawerFinish does.

Notice that if you are merging different models, you can find out which model has been merged by inspecting the parameters of the function. For instance you could do something special if the file “hello_world.tnt” had been merged, however in our example we don’t need to have special cases for different models.

Changing materials

It’s nice to be able to visualize different material finishes on our small cupboard, and the easiest way to do that is by scripting it. Of course we could merge different models with different materials instead, but that would probably be a nightmare to author, and what would happen if we suddenly needed a new material?

Scripting is better, so let’s look at two different functions we use to change the materials of the drawers:

function SetDrawerMaterial( fileName )
{
	// Apply material to front models
	doTNTCommand('Mesh("*front*").MeshFrame().FaceSegment().TextureFragment(0).Texture(0).LoadBitmap("'+fileName+'")');
	drawerMaterial = fileName;
}

Here’s what the function does:

The second function does exactly the same, except it loads a new reflection map instead of a new diffuse map. Here’s what it looks like:

function SetDrawerFinish( fileName )
{
	// Apply material to front models
	doTNTCommand('Mesh("*front*").MeshFrame().FaceSegment().TextureFragment(1).Texture(0).LoadBitmap("'+fileName+'")');
	drawerFinish = fileName;		
}

If you compare the two functions you will see that only the index of the TextureFragment being accessed is different.

In most cases you can simply use:

doTNTCommand('Mesh("*front*").LoadBitmap("'+fileName+'")');

But as noted above, this would load the new bitmap into both the diffuse map and the reflection map, which is not what we want in this case.

Setting up mouse interaction

In our cupboard example we will need to do a little scripting to let us open and close the drawers, so what we do is write the following:

function OnSceneLoad(fileName)
{
	doTNTCommand('Node("*drawer*").SetMeshMouseOverEvent(1).SetMeshOcclusion(1)');
	doTNTCommand('Node("cupboard").SetMeshOcclusion(1)');
}

Let’s break it down:

Ok, so now the nodes are ready to tell us when the mouse moves over them, but we need to handle what happens once the user clicks on them, so we’ll add two new functions:

function OnMouseEnter(nodeName, nodeIndex)
{
	mouseNode = nodeName;
}
function OnMouseExit(nodeName, nodeIndex)
{
	mouseNode = "";
}

These two functions are triggered when the mouse moves over or exits a model which has “SetMeshMouseOverEvent(1)”, and what they do is simply store the name of the model the mouse is over.

So now we know at any given time which model the mouse is over. Next step is adding a little scripting to let us drag open/close the drawers. We do that using the following code which is called every time the user presses the mouse key:

function OnMouseKeyPress(keyIndex)
{
	if (mouseNode != "")
	{
		mouseDown = true;

		editNode = mouseNode;

		// Disable camera input
		doTNTCommand('CurrentCamera().SetCameraIgnoreInput(1)');
		
		// Set mouse move mode
		doTNTCommand('Selection().SetSelectionMoveDirectionX(0)');
		doTNTCommand('Selection().SetSelectionMoveDirectionY(1)');
		doTNTCommand('Selection().SetSelectionMoveDirectionZ(0)');
		doTNTCommand('Selection().SetSelectionMouseKeyMode(1,1)');
		
		// Limit how far the model can move
		doTNTCommand('Selection().SetSelectionMinY(-0.7).SetSelectionMaxY(-0.3)');

		// Add the model to the selection
		doTNTCommand('Node("'+editNode+'").AddToSelection()');	
	}
}

Using the Selection() script commands in TurnTool it is easy to control them using the mouse. Let’s break down what happens:

Once the user releases the mouse button the following function is called:

function OnMouseKeyRelease(keyIndex)
{
	if (mouseDown)
	{
		mouseDown = false;

		// De-select model
		doTNTCommand('Selection().EmptySelection()');
		
		// Re-enable camera input
		doTNTCommand('CurrentCamera().SetCameraIgnoreInput(0)');
	}
}

Here we simply empty the selection, and re-enable camera input, so we can look around again. Very straightforward.

Conclusion

That’s the end of our brief tutorial, as you have seen using a few simple scripting commands can make your projects even better. It’s very easy to extend this tutorial to include even more options, so why not try and add functionality for different cupboard sizes, more materials, more drawer designs, and more handles?

You can download all the files used in the tutorial, including source files for 3ds max 2010, right here: Tutorial source files