Game Development Community

Connecting Two Object Nodes

by Chris Haigler · in Torque 3D Professional · 10/04/2014 (4:53 pm) · 5 replies

Need a bit of help from someone more math savvy than me.

I'm constructing a level using modular room sections. Each mesh section has several nodes indicating doorways where other sections could connect. The level is being built procedurally so room sections will be randomly spawned and connected to other (random) room segments. The connection point between each room will be the "doorway" nodes.

I'm having trouble transforming the room segments so that their "doorway" nodes connect. The process should be something like:

1). Spawn a room piece (A).
2). Spawn another room piece (B) and position it at A's coordinates.
3). Rotate B along the z-axis so that it's rotation is the equal but opposite of A.
4). Offset B so that B's doorway node matches A's doorway node.

Steps 3 & 4 are where I'm stumbling. :]

I've whipped up a quick image to show what I'm trying to do:

i.imgur.com/m7UwA4W.jpg
Edit: I should mention, I've added a getNodeWTransform() method to SceneObject that returns a node's world transform info. It should be easy enough to add another method that returns a node's local transform.

#1
10/04/2014 (6:07 pm)
I'll try to take a stab at it, mainly since I am eternally grateful to you for helping me figure out my scoping issue earlier :) This is all pretty much pseudocode, but hopefully it can get the gears turning and set you on course. Surely I've overlooked some small problem lol, since I hadn't ran a test on any of this script. I don't expect you to be able to just plug it in and go. Anyways, see if any of this is hitting the right idea. This is based off of the diagram above but in this case we added obj2(PieceA) first and then added obj1(PieceB) which is taller. You'll see that you need to input PieceB's height as opposed to PieceA's width.

// We're dropping in PieceA first (obj2)
// Then connecting PieceB (obj1) based
// off the diagram

datablock StaticShapeData(roomPieceAData)
{
	category = "roomPieces";
};

dataBlock StaticShapeData(roomPieceBData)
{
	category = "roomPieces";
};

function spawnPieceA(%this, %pos, %rot)
{
	%obj = new StaticShape()
	{
		...
		dataBlock = "roomPieceAData";
		position = %pos;
		rotation = %rot;
		...
	};
}

// Input the %pos, %rot, and %widthA of PieceA.
// Input %heightB of PieceB (based off of your diagram,
// we are going to calculate based off of height 
// since that will end up being the width of PieceB 
// once it's rotated
function spawnPieceB(%this, %pos, %rot, %widthA, %heightB)
{
	// Go ahead and determine the difference
	// in size between PieceA and PieceB
	// and store it as %offset
	%offset = %heightB - %widthA;
	
	// Get half the width of PieceA 
	// and store it as %newWidth
	%newWidth = %widthA/2;
		
	// First set %newRot (rotation of PieceB)
	// to the %rot of PieceA.
	%newRot = %rot;
	
	// Right after that, go ahead and flip it on 'z'
	// (assuming you are connecting on the x axis)
        // Note that this angle could be a predetermined variable
        // based off PieceA
	%newRot = "0 0 " @ mDegtoRad(90) @ " 0";
	
	// Subtract half of PieceA's width as 'x' 
	// from the input %pos (if you are connecting on x axis),
	// then subtract the %offset
	// Store this as %newPos 
	%newPos = (VectorSub(%pos, %newWidth SPC "0 0")) - %offset; 

	%obj = new StaticShape()
	{
		...
		dataBlock = "roomPieceBData";
		position = %newPos;
		rotation = %newRot;
		...
	}
}

Again, I'm sorry if it's not precise. I just plugged it together in time for dinner :) Also in hindsight you could probably clean all that up using getTransform() and setTransform(). Hope it helps though, it should at least get the concept across to set you in the right direction. One thing you'll notice too, is since this is all procedural you'll need to adjust all that to suit depending on the: height/width of the pieces, rotations, and axis on which they are being connected. It's not a trivial matter to say the least. It could be beneficial to write out these functions as .cpp functions as well(Just considering performance if you're piecing together several of these). Cheers!
#2
10/04/2014 (8:09 pm)
Thanks for the reply Jesse.

I stumbled on this tutorial: gamedevelopment.tutsplus.com/tutorials/bake-your-own-3d-dungeons-with-procedural...

It does a good job explaining process (and has a much better picture of typical room segments than my img up top). Unfortunately it lacks detail on the most important part, opting instead for some Python-ish pseudo-code:

def match_exits(old_exit, new_exit):
    new_module = new_exit.parent
    forward_vector_to_match = old_exit.backward_vector
    corrective_rotation = azimuth(forward_vector_to_match) - azimuth(new_exit.forward_vector)
    rotate_around_y(new_module, new_exit.position, corrective_rotation)
    corrective_translation = old_exit.position - new_exit.position
    translate_global(new_module, corrective_translation)
 
def azimuth(vector):
    # Returns the signed angle this vector is rotated relative to global +Z axis
    forward = [0, 0, 1]
    return vector_angle(forward, vector) * math.copysign(vector.x)

The match_exits function is the important bit for me. I'm having trouble translating it into something workable in Torque.
#3
10/05/2014 (12:46 am)
Variant 1:
1. Get object A's door node and make inverse of its world transform.
2. Transform object B to A's space
3. Translate object B to zero.

Variant 2:
Work in world space only.
1. Take render transform of B's node, extract the 3x3 euler rotation and copy that to A's world matrix.
2. Translate A using vector (BnodePos - AnodePos)

Variant 3:
Rotate around an arbitrary point and translate.
#4
10/05/2014 (6:30 am)
Thanks Ivan. I think I'm on the right track, here's what I've quickly hacked together in tscript:

// move objB to world position of objA's "Ent" node
%objB.setTransform(%objA.getNodePosition("Ent")); // rotation not included

// offset objB's position so that the "Ent" nodes in both meshes overlap
%objANode = ObjA.getNodePosition("Ent");
%objBNode = ObjB.getNodePosition("Ent");
%diff = VectorSub(%objANode, %objBNode);
%objB.setTransform(VectorAdd(%objB.getPosition(), $diff));

The above results in something that looks like:
i.imgur.com/owoYZsW.jpg
I'm not sure how to proceed from here. ObjB will need to be rotated so that it matches the inverse (I think?) of ObjA's rotation. It will then need to be translated "away" from ObjA's "Ent" node until both "Ent" nodes overlap once more.
#5
10/05/2014 (7:23 am)
Oh, I see what you're up to now. Sorry, I was plugging in some script just kinda using your diagram only.

Just thinking out loud, but I wonder if you could use Torque's mount system for this. Have nodes on the entrance/exit points(which it appears you do) but set them up with "mountPoint" dummy nodes. The benefits of this would be that you could have variants for each piece depending on the axis and just build it by mounting. If this would work you probably wouldn't even need to worry with any maths or rotations manually in script.

Here's a post where Michael Reino had even modified TSStatics to be mountable:
Generic Object Mounting
He's even got links to his implementations on github. If you read his descriptions on github, you can see that he's added new console methods for mounting objects. It would appear that these methods will align the nodes with an optional offset, which would negate any need for variations of the objects.

Just a thought, good luck! I hope you get it implemented, looks like a great idea :)