Snap To objects functionality in Mission Editor
by Davis Ray Sickmon, Jr · 06/18/2004 (2:14 pm) · 26 comments
So, Eric Forhan sends me the new content pack he's been working on (which also happens to be related to one of the contract projects I'm doing) which happens to include roads. I start putting parts together just to play with it some more (he included a small city for me to work with) and I was having a really hard time making the roads line up properly. Sure, there's the grid, but that doesn't always do the job. Time for some hackin'...
Here's how to add this to your project - open up common/editor/editorgui.cs. Find:
"But wait - how do I use it?", you ask. Well, pretty simple really:
Click on the "target" object - this is the object that you'd like to have another object align with ("SnapTo")
Hit Shift, and click on the next object - that will leave both objects highlighted.
Click on "SnapTo", and the axis and location you'd like it to snap to - play with this a bit to get used to it. It's hard for me to describe how it works, but, it's basing the "SnapTo" on the bounding boxes and centers for both objects. Once you get used to it, it's pretty intuitive.
Now for it's limitations: It's based on the center and the bounding box for the objects that are being snapped. This means, you need to have your object at 0, 90, 180, 260 degrees, not 42 degrees when you snap 'em together. So if you need three objects, all rotated at 41 degrees but snapped together like this... do SnapTo on all of them first, THEN rotate the entire group at once using Shift-click then rotate.
The second limitation is that you can't do like a dozen objects at once - only one target, and only one object to be aligned. You can SELECT as many as you want - just the first object is going to be used as the target, and the last object will be the one to be aligned.
That's about it for limitations.
Feel free to comment, etc. about it - however, if you want to make fun of my 'leet scriping... improve it! :-)
And since someone will probably ask: Eric is working on a content pack for City environments. Medium poly with LOD, etc. - if you need a big honkin' city that runs at a reasonable framerate, this will be the pack to get :-)
Here's how to add this to your project - open up common/editor/editorgui.cs. Find:
EditorMenuBar.addMenuItem("World", "Drop at Centroid", 5, "", 1);
EditorMenuBar.addMenuItem("World", "Drop to Ground", 6, "", 1);
EditorMenuBar.addMenu("Action", 3);
EditorMenuBar.addMenuItem("Action", "Select", 1, "", 1);And insert this before EditorMenuBar.addMenu("Action", 3);// DRSJR: Added for SnapTo Menu
EditorMenuBar.addMenu("SnapTo", 8);
EditorMenuBar.addMenuItem("SnapTo", "X", 1);
EditorMenuBar.addMenuItem("SnapTo", "X+", 2);
EditorMenuBar.addMenuItem("SnapTo", "X-", 3);
EditorMenuBar.addMenuItem("SnapTo", "Y", 4);
EditorMenuBar.addMenuItem("SnapTo", "Y+", 5);
EditorMenuBar.addMenuItem("SnapTo", "Y-", 6);
EditorMenuBar.addMenuItem("SnapTo", "Z", 7);
EditorMenuBar.addMenuItem("SnapTo", "Z+", 8);
EditorMenuBar.addMenuItem("SnapTo", "Z-", 9);
// DRSJR: End addition for SnapTo MenuThen, scroll down a bit to:case "Action":
%this.onActionMenuItemSelect(%itemId, %item);
case "Brush":
%this.onBrushMenuItemSelect(%itemId, %item);
case "Camera":
%this.onCameraMenuItemSelect(%itemId, %item);And add:// DRSJR - added for SnapTo menu
case "SnapTo":
%this.OnSnapToMenuItemSelect(%itemId, %item);
// DRSJR - end addition for SnapTo menuMove down to:function EditorMenuBar::onEditMenuItemSelect(%this, %itemId, %item)
{And just before that line, add:// DRSJR: Added for SnapTo menu handling.
function EditorMenuBar::onSnapToMenuItemSelect(%this, %itemId, %item)
{
EWorldEditor.SnapTo(%item);
}
// DRSJR: End addition for SnapTo menu handlingFinally, scroll down to:function WorldEditor::dropCameraToSelection(%this)
{And just before that, paste in the following:// DRSJR: Begin snap-to functionality
// SnapTo works ONLY off of the two selected objects - select as many
// as you want, but this sucker only works on two of them - the last
// one is the object to be snapped, and the first one is the target.
function WorldEditor::snapTo(%this, %snapType)
{
if(%this.getSelectionSize() > 2)
{
error("Please select two objects before selecting a Snap To funciton.");
return;
}
%objTarget = %this.getSelectedObject(0);
%objToSnap = %this.getSelectedObject(%this.getSelectionSize()-1);
switch$(%snapType)
{
case "X":
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 0, getWord(%objTarget.getTransform(), 0)));
case "X-":
%objTargetXEdge = getWord(%objTarget.getTransform(), 0) + getWord(%objTarget.getObjectBox(), 0);
%objToSnapXEdge = %objTargetXEdge - getWord(%objToSnap.getObjectBox(), 3);
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 0, %objToSnapXEdge));
case "X+":
%objTargetXEdge = getWord(%objTarget.getTransform(), 0) + getWord(%objTarget.getObjectBox(), 3);
%objToSnapXEdge = %objTargetXEdge - getWord(%objToSnap.getObjectBox(), 0);
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 0, %objToSnapXEdge));
case "Y":
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 1, getWord(%objTarget.getTransform(), 1)));
case "Y-":
%objTargetXEdge = getWord(%objTarget.getTransform(), 1) + getWord(%objTarget.getObjectBox(), 1);
%objToSnapXEdge = %objTargetXEdge - getWord(%objToSnap.getObjectBox(), 4);
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 1, %objToSnapXEdge));
case "Y+":
%objTargetXEdge = getWord(%objTarget.getTransform(), 1) + getWord(%objTarget.getObjectBox(), 4);
%objToSnapXEdge = %objTargetXEdge - getWord(%objToSnap.getObjectBox(), 1);
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 1, %objToSnapXEdge));
case "Z":
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 2, getWord(%objTarget.getTransform(), 2)));
case "Z-":
%objTargetXEdge = getWord(%objTarget.getTransform(), 2) + getWord(%objTarget.getObjectBox(), 2);
%objToSnapXEdge = %objTargetXEdge - getWord(%objToSnap.getObjectBox(), 5);
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 2, %objToSnapXEdge));
case "Z+":
%objTargetXEdge = getWord(%objTarget.getTransform(), 2) + getWord(%objTarget.getObjectBox(), 5);
%objToSnapXEdge = %objTargetXEdge - getWord(%objToSnap.getObjectBox(), 2);
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 2, %objToSnapXEdge));
}
}Save and run. It should work right off the bat :-)"But wait - how do I use it?", you ask. Well, pretty simple really:
Click on the "target" object - this is the object that you'd like to have another object align with ("SnapTo")
Hit Shift, and click on the next object - that will leave both objects highlighted.
Click on "SnapTo", and the axis and location you'd like it to snap to - play with this a bit to get used to it. It's hard for me to describe how it works, but, it's basing the "SnapTo" on the bounding boxes and centers for both objects. Once you get used to it, it's pretty intuitive.
Now for it's limitations: It's based on the center and the bounding box for the objects that are being snapped. This means, you need to have your object at 0, 90, 180, 260 degrees, not 42 degrees when you snap 'em together. So if you need three objects, all rotated at 41 degrees but snapped together like this... do SnapTo on all of them first, THEN rotate the entire group at once using Shift-click then rotate.
The second limitation is that you can't do like a dozen objects at once - only one target, and only one object to be aligned. You can SELECT as many as you want - just the first object is going to be used as the target, and the last object will be the one to be aligned.
That's about it for limitations.
Feel free to comment, etc. about it - however, if you want to make fun of my 'leet scriping... improve it! :-)
And since someone will probably ask: Eric is working on a content pack for City environments. Medium poly with LOD, etc. - if you need a big honkin' city that runs at a reasonable framerate, this will be the pack to get :-)
#2
06/18/2004 (8:41 pm)
After Eric and I played with it a while, we noticed something... getObjectBox() always returns the un-rotated box for the object. Oops. Hm... anyone got a good thought on how to fix that? :-)
#3
06/19/2004 (4:20 am)
getWorldBox maybe?
#4
getWorldBox() doesnt work, too...
I have the same problem of not getting the "real" objectBox, but its AABB it seems...
tork.beffy.de/uploads/pics/torqueai/objbox_problem02a.jpg
tork.beffy.de/uploads/pics/torqueai/objbox_problem02b.jpg
06/20/2004 (10:21 am)
Davis, if you ever figure this out, let me know :)getWorldBox() doesnt work, too...
I have the same problem of not getting the "real" objectBox, but its AABB it seems...
tork.beffy.de/uploads/pics/torqueai/objbox_problem02a.jpg
tork.beffy.de/uploads/pics/torqueai/objbox_problem02b.jpg
#5
tork.beffy.de/uploads/pics/torqueai/objbox_problem02c.jpg
What I'm doing now is instead of "transforming" the objBox into the "correct" transform I'd need (which doesn't work since it will always return the AABB for performance reasons), I now instead transform my node points into the local space of the objBox and test there if they are contained inside the box :)
06/22/2004 (6:43 am)
Nm, solved my issues (mattf had the right idea, thx again! :))tork.beffy.de/uploads/pics/torqueai/objbox_problem02c.jpg
What I'm doing now is instead of "transforming" the objBox into the "correct" transform I'd need (which doesn't work since it will always return the AABB for performance reasons), I now instead transform my node points into the local space of the objBox and test there if they are contained inside the box :)
Point3F localNodePos = fromNode->position;
mNavMeshModifiers[i]->getWorldTransform().mulP(localNodePos);
localNodePos.convolveInverse(mNavMeshModifiers[i]->getScale());
if(modifierBox.isContained(localNodePos))
{
#6
07/03/2004 (5:00 am)
Anyone have any luck on fixing this resource? It sure comes in handy.
#7
I factored out a lot of the original SnapTo stuff, so I might as well post the whole new change instead of the differences. There's a lot of lines, but it is only four chunks.
The new functionality includes multi-axes alignment in one click, and auto-clone and align in one keypress (select one *or more* objects and select a clone option from the CloneTo menu; each object will be copy/pasted and then aligned to the chosen axes.) Further, I added shortcut keys:
On the Numpad (hold down Alt to clone *and* align):
Numpad 4: align to Neg X, Y and Z
Numpad 6: align to Pos X, Y and Z
Numpad 2: align to X, Neg Y and Z
Numpad 8: align to X, Pos Y and Z
Numpad 3: align to X, Y and Neg Z
Numpad 9: align to X, Y and Pos Z
For the individual axis, use the appropriate letter. E.g., 'X' aligns to X center. Shift-X aligns to Pos X and Alt-X aligns to Neg X. Same things with 'Y' and 'Z' keys.
Step 1: Find The following
Step 2: Find The following
Step 3: Find The following
Step 4: Find The following
That's it. Hope someone finds it useful. In fact, I hope it drives someone to investigate and fix the getObjectBox problem, because then this would be INCREDIBLY useful!
07/08/2004 (9:22 pm)
I've also been making roads recently and this snap-to resource was unbelievably helpful. I ended up making a bunch of tweaks to speed things up even more (sadly, not the getObjBox fix. I wish!). I thought I'd share my changes here in case someone might find them useful.I factored out a lot of the original SnapTo stuff, so I might as well post the whole new change instead of the differences. There's a lot of lines, but it is only four chunks.
The new functionality includes multi-axes alignment in one click, and auto-clone and align in one keypress (select one *or more* objects and select a clone option from the CloneTo menu; each object will be copy/pasted and then aligned to the chosen axes.) Further, I added shortcut keys:
On the Numpad (hold down Alt to clone *and* align):
Numpad 4: align to Neg X, Y and Z
Numpad 6: align to Pos X, Y and Z
Numpad 2: align to X, Neg Y and Z
Numpad 8: align to X, Pos Y and Z
Numpad 3: align to X, Y and Neg Z
Numpad 9: align to X, Y and Pos Z
For the individual axis, use the appropriate letter. E.g., 'X' aligns to X center. Shift-X aligns to Pos X and Alt-X aligns to Neg X. Same things with 'Y' and 'Z' keys.
Step 1: Find The following
EditorMenuBar.addMenuItem("World", "Drop to Ground", 6, "", 1);and just after it add:// DRSJR: Added for SnapTo Menu
EditorMenuBar.addMenu("SnapTo", 8);
EditorMenuBar.addMenuItem("SnapTo", "X", 1, "X");
EditorMenuBar.addMenuItem("SnapTo", "X+", 2, "Shift X");
EditorMenuBar.addMenuItem("SnapTo", "X-", 3, "Alt X");
EditorMenuBar.addMenuItem("SnapTo", "Y", 4, "Y");
EditorMenuBar.addMenuItem("SnapTo", "Y+", 5, "Shift Y");
EditorMenuBar.addMenuItem("SnapTo", "Y-", 6, "Alt Y");
EditorMenuBar.addMenuItem("SnapTo", "Z", 7, "Z");
EditorMenuBar.addMenuItem("SnapTo", "Z+", 8, "Shift Z");
EditorMenuBar.addMenuItem("SnapTo", "Z-", 9, "Alt Z");
EditorMenuBar.addMenuItem("SnapTo", "X+YZ", 10, "numpad6");
EditorMenuBar.addMenuItem("SnapTo", "X-YZ", 11, "numpad4");
EditorMenuBar.addMenuItem("SnapTo", "XY+Z", 12, "numpad8");
EditorMenuBar.addMenuItem("SnapTo", "XY-Z", 13, "numpad2");
EditorMenuBar.addMenuItem("SnapTo", "XYZ+", 12, "numpad9");
EditorMenuBar.addMenuItem("SnapTo", "XYZ-", 13, "numpad3");
// DRSJR: End addition for SnapTo Menu
// LMD: Added for CloneTo Menu
EditorMenuBar.addMenu("CloneTo", 8);
EditorMenuBar.addMenuItem("CloneTo", "X+YZ", 1, "alt numpad6");
EditorMenuBar.addMenuItem("CloneTo", "X-YZ", 2, "alt numpad4");
EditorMenuBar.addMenuItem("CloneTo", "XY+Z", 3, "alt numpad8");
EditorMenuBar.addMenuItem("CloneTo", "XY-Z", 4, "alt numpad2");
EditorMenuBar.addMenuItem("CloneTo", "XYZ+", 5, "alt numpad9");
EditorMenuBar.addMenuItem("CloneTo", "XYZ-", 6, "alt numpad3");
// LMD: End addition for CloneTo MenuStep 2: Find The following
case "Camera":
%this.onCameraMenuItemSelect(%itemId, %item);and just after it add:// DRSJR - added for SnapTo menu case "SnapTo": %this.OnSnapToMenuItemSelect(%itemId, %item); // DRSJR - end addition for SnapTo menu // LMD - added for CloneTo menu case "CloneTo": %this.OnCloneToMenuItemSelect(%itemId, %item); // LMD - end addition for CloneTo menu
Step 3: Find The following
case "Drop at Centroid":
EWorldEditor.dropType = "atCentroid";
}
}
}and just after it add:// DRSJR: Added for SnapTo menu handling.
function EditorMenuBar::onSnapToMenuItemSelect(%this, %itemId, %item )
{
EWorldEditor.SnapTo(%item);
}
// DRSJR: End addition for SnapTo menu handling
// LMD: Added for CloneTo menu handling.
function EditorMenuBar::onCloneToMenuItemSelect(%this, %itemId, %item )
{
EWorldEditor.CloneTo(%item);
}
// LMD: End addition for CloneTo menu handlingStep 4: Find The following
function WorldEditor::getSelectionHiddenCount(%this)
{
%ret = 0;
for(%i = 0; %i < %this.getSelectionSize(); %i++)
{
%obj = %this.getSelectedObject(%i);
if(%obj.hidden $= "true")
%ret++;
}
return %ret;
}and just after it add:// DRSJR: Begin snap-to functionality
// SnapTo works ONLY off of the two selected objects - select as many
// as you want, but this sucker only works on two of them - the last
// one is the object to be snapped, and the first one is the target.
function WorldEditor::snapTo(%this, %snapType, %objTarget, %objToSnap)
{
if (%objTarget $= "")
{
%objTarget = %this.getSelectedObject(0);
}
if (%objToSnap $= "")
{
%objToSnap = %this.getSelectedObject(%this.getSelectionSize()-1);
}
if(%objTarget $= "" || %objToSnap $= "")
{
error("Please select two objects before selecting a Snap To funciton.");
return;
}
switch$(%snapType)
{
case "X":
%this.snapToX(%objTarget, %objToSnap);
case "X-":
%this.snapToXNeg(%objTarget, %objToSnap);
case "X+":
%this.snapToXPos(%objTarget, %objToSnap);
case "Y":
%this.snapToY(%objTarget, %objToSnap);
case "Y-":
%this.snapToYNeg(%objTarget, %objToSnap);
case "Y+":
%this.snapToYPos(%objTarget, %objToSnap);
case "Z":
%this.snapToZ(%objTarget, %objToSnap);
case "Z-":
%this.snapToZNeg(%objTarget, %objToSnap);
case "Z+":
%this.snapToZPos(%objTarget, %objToSnap);
case "X+YZ":
%this.snapToXPosYZ(%objTarget, %objToSnap);
case "X-YZ":
%this.snapToXNegYZ(%objTarget, %objToSnap);
case "XY+Z":
%this.snapToXYPosZ(%objTarget, %objToSnap);
case "XY-Z":
%this.snapToXYNegZ(%objTarget, %objToSnap);
case "XYZ+":
%this.snapToXYZPos(%objTarget, %objToSnap);
case "XYZ-":
%this.snapToXYZNeg(%objTarget, %objToSnap);
}
}
function WorldEditor::snapToX(%this, %objTarget, %objToSnap)
{
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 0, (getWord(%objTarget.getTransform(), 0) + EWorldEditor.snapGapX)));
}
function WorldEditor::snapToXPos(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getWorldBox(), 0) - getWord(%objToSnap.getTransform(), 0));
%transformWithOffset = getWord(%objTarget.getWorldBox(), 3) + %edgeOffset;
%objToSnap.setTransform( setWord(%objToSnap.getTransform(), 0, %transformWithOffset) );
}
function WorldEditor::snapToXNeg(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getWorldBox(), 3) - getWord(%objToSnap.getTransform(), 0));
%transformWithOffset = getWord(%objTarget.getWorldBox(), 0) - %edgeOffset;
%objToSnap.setTransform( setWord(%objToSnap.getTransform(), 0, %transformWithOffset) );
}
function WorldEditor::snapToY(%this, %objTarget, %objToSnap)
{
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 1, (getWord(%objTarget.getTransform(), 1) + EWorldEditor.snapGapY)));
}
function WorldEditor::snapToYPos(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getWorldBox(), 1) - getWord(%objToSnap.getTransform(), 1));
%transformWithOffset = getWord(%objTarget.getWorldBox(), 4) + %edgeOffset;
%objToSnap.setTransform( setWord(%objToSnap.getTransform(), 1, %transformWithOffset) );
}
function WorldEditor::snapToYNeg(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getWorldBox(), 4) - getWord(%objToSnap.getTransform(), 1));
%transformWithOffset = getWord(%objTarget.getWorldBox(), 1) - %edgeOffset;
%objToSnap.setTransform( setWord(%objToSnap.getTransform(), 1, %transformWithOffset) );
}
function WorldEditor::snapToZ(%this, %objTarget, %objToSnap)
{
%objToSnap.setTransform(setWord(%objToSnap.getTransform(), 2, (getWord(%objTarget.getTransform(), 2) + EWorldEditor.snapGapZ)));
}
function WorldEditor::snapToZPos(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getWorldBox(), 2) - getWord(%objToSnap.getTransform(), 2));
%transformWithOffset = getWord(%objTarget.getWorldBox(), 5) + %edgeOffset;
%objToSnap.setTransform( setWord(%objToSnap.getTransform(), 2, %transformWithOffset) );
}
function WorldEditor::snapToZNeg(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getWorldBox(), 5) - getWord(%objToSnap.getTransform(), 2));
%transformWithOffset = getWord(%objTarget.getWorldBox(), 2) - %edgeOffset;
%objToSnap.setTransform( setWord(%objToSnap.getTransform(), 2, %transformWithOffset) );
}
function WorldEditor::snapToXPosYZ(%this, %objTarget, %objToSnap)
{
%this.snapToXPos(%objTarget, %objToSnap);
%this.snapToY(%objTarget, %objToSnap);
%this.snapToZ(%objTarget, %objToSnap);
}
function WorldEditor::snapToXNegYZ(%this, %objTarget, %objToSnap)
{
%this.snapToXNeg(%objTarget, %objToSnap);
%this.snapToY(%objTarget, %objToSnap);
%this.snapToZ(%objTarget, %objToSnap);
}
function WorldEditor::snapToXYPosZ(%this, %objTarget, %objToSnap)
{
%this.snapToX(%objTarget, %objToSnap);
%this.snapToYPos(%objTarget, %objToSnap);
%this.snapToZ(%objTarget, %objToSnap);
}
function WorldEditor::snapToXYNegZ(%this, %objTarget, %objToSnap)
{
%this.snapToX(%objTarget, %objToSnap);
%this.snapToYNeg(%objTarget, %objToSnap);
%this.snapToZ(%objTarget, %objToSnap);
}
function WorldEditor::snapToXYZPos(%this, %objTarget, %objToSnap)
{
%this.snapToX(%objTarget, %objToSnap);
%this.snapToY(%objTarget, %objToSnap);
%this.snapToZPos(%objTarget, %objToSnap);
}
function WorldEditor::snapToXYZNeg(%this, %objTarget, %objToSnap)
{
%this.snapToX(%objTarget, %objToSnap);
%this.snapToY(%objTarget, %objToSnap);
%this.snapToZNeg(%objTarget, %objToSnap);
}
function WorldEditor::cloneTo(%this, %snapType)
{
%selSize = %this.getSelectionSize();
for(%i = 0; %i < %selSize; %i++)
{
%origObjects[%i] = %this.getSelectedObject(%i);
}
for(%i = 0; %i < %selSize; %i++)
{
%this.clearSelection();
%objTarget = %origObjects[%i];
%this.selectObject(%objTarget);
%this.copySelection();
%this.pasteSelection();
%objToSnap = %this.getSelectedObject(0);
%newObjects[%i] = %objToSnap;
%this.snapTo(%snapType, %objTarget, %objToSnap);
}
%this.clearSelection();
for(%i = 0; %i < %selSize; %i++)
{
%this.selectObject(%newObjects[%i]);
}
}That's it. Hope someone finds it useful. In fact, I hope it drives someone to investigate and fix the getObjectBox problem, because then this would be INCREDIBLY useful!
#8
First, I've added the following function:
I dropped this in just before the snapTo function. I then changed:
so it calls multiSnapTo instead:
You may notice that I've flipped the order of who snaps to what in that multiSnapTo routine. I also changed it in snapTo, from:
I did this because if you cut/paste or create a new object, the new object is already selected by default, and you always want to move(align) that one. This way it is create-click-align and flows a bit better. Also with the multiSnapTo routine above, I like doing a mass-select of items I want to snap, and end by selecting the reference 'rooted' object last.
What was taking me hours to painfully align by hand I can do in a matter of a few minutes! I avoided the AABB issue by either having symmetrical road pieces that mostly don't need rotating, or I created pre-rotated versions for each angle I'll need. Lame, but it works. Hope it helps others too.
07/08/2004 (10:27 pm)
As I'm using this more to do real work, I'm finding more little tweaks to improve my efficiency.First, I've added the following function:
// Iterates through a whole bunch of items and snaps them all to the last.
function WorldEditor::multiSnapTo(%this, %snapType)
{
%selSize = %this.getSelectionSize();
%objTarget = %this.getSelectedObject(%this.getSelectionSize()-1);
for(%i = 0; %i < %selSize - 1; %i++)
{
%objToSnap = %this.getSelectedObject(%i);
%this.snapTo(%snapType, %objTarget, %objToSnap);
}
}I dropped this in just before the snapTo function. I then changed:
// DRSJR: Added for SnapTo menu handling.
function EditorMenuBar::onSnapToMenuItemSelect(%this, %itemId, %item )
{
EWorldEditor.snapTo(%item);
}
// DRSJR: End addition for SnapTo menu handlingso it calls multiSnapTo instead:
EWorldEditor.multiSnapTo(%item);
You may notice that I've flipped the order of who snaps to what in that multiSnapTo routine. I also changed it in snapTo, from:
if (%objTarget $= "")
{
%objTarget = %this.getSelectedObject(0);
}
if (%objToSnap $= "")
{
%objToSnap = %this.getSelectedObject(%this.getSelectionSize()-1);
}toif (%objTarget $= "")
{
%objTarget = %this.getSelectedObject(%this.getSelectionSize()-1);
}
if (%objToSnap $= "")
{
%objToSnap = %this.getSelectedObject(0);
}I did this because if you cut/paste or create a new object, the new object is already selected by default, and you always want to move(align) that one. This way it is create-click-align and flows a bit better. Also with the multiSnapTo routine above, I like doing a mass-select of items I want to snap, and end by selecting the reference 'rooted' object last.
What was taking me hours to painfully align by hand I can do in a matter of a few minutes! I avoided the AABB issue by either having symmetrical road pieces that mostly don't need rotating, or I created pre-rotated versions for each angle I'll need. Lame, but it works. Hope it helps others too.
#9
I'm not going to document it here right now unless someone specifically wants it (because it's a bunch of changes scattered around), but I added three mission variables to EWorldEditor called 'snapGapX', 'snapGapY' and 'snapGapZ' and exposed them in WorldEditorSettingsDlg.gui. They basically get rolled into the calc when the alignment happens for each axis. With this gap, I can clone an item or items with extra space in one or more directions. Neat for laying down spaced-out trees or bushes or what have you, or whipping up some stairs, etc.
Ok I promise I'm done for the night now. :)
07/08/2004 (11:12 pm)
Ok I can't leave well enough alone.I'm not going to document it here right now unless someone specifically wants it (because it's a bunch of changes scattered around), but I added three mission variables to EWorldEditor called 'snapGapX', 'snapGapY' and 'snapGapZ' and exposed them in WorldEditorSettingsDlg.gui. They basically get rolled into the calc when the alignment happens for each axis. With this gap, I can clone an item or items with extra space in one or more directions. Neat for laying down spaced-out trees or bushes or what have you, or whipping up some stairs, etc.
Ok I promise I'm done for the night now. :)
#10
Well, if you'd expose getWorldTransform() to the console a fix for the rotation problem would be easy... dunno if you want to / are able to change the engine... but if you want, I could do it and post that :)
07/09/2004 (9:11 am)
Nice :)Well, if you'd expose getWorldTransform() to the console a fix for the rotation problem would be easy... dunno if you want to / are able to change the engine... but if you want, I could do it and post that :)
#11
If you give me a nudge on what to do once getWorldTransform is exposed, maybe that'll rattle the cobwebs from the corners of my brain. :)
07/09/2004 (12:02 pm)
Sure, I have no qualms about exposing getWorldTransform. Before playing with the script itself, I was half way through pouring over the engine code to try to fix this, but I got kinda lost in the math.\If you give me a nudge on what to do once getWorldTransform is exposed, maybe that'll rattle the cobwebs from the corners of my brain. :)
#12
To do this, find:
I've had tiny sparks of realization on how the world transform here could be used to properly do this alignment, but I can't bring it through to conclusion. I realize that the local coordinate system's axis for the object needs to be used for the offset transform but I still don't know how to apply it in the script. For example:
This does all the proper distance calculations, but the final transform application has to happen taking into account the rotated local axis, not the world axis. How do I manipulate these within the domain of the local axes instead of the world axes? Or am I off in the weeds?
07/10/2004 (2:20 pm)
Ok, I've exposed the getWorldTransform as a console method.To do this, find:
ConsoleMethod( SceneObject, getTransform, const char*, 2, 2, "Get transform of object.")
{
char *returnBuffer = Con::getReturnBuffer(256);
const MatrixF& mat = object->getTransform();
Point3F pos;
mat.getColumn(3,&pos);
AngAxisF aa(mat);
dSprintf(returnBuffer,256,"%g %g %g %g %g %g %g",
pos.x,pos.y,pos.z,aa.axis.x,aa.axis.y,aa.axis.z,aa.angle);
return returnBuffer;
}and below it add:ConsoleMethod( SceneObject, getWorldTransform, const char*, 2, 2, "Get transform which allows world space to be converted to object space.")
{
char *returnBuffer = Con::getReturnBuffer(256);
const MatrixF& mat = object->getWorldTransform();
Point3F pos;
mat.getColumn(3,&pos);
AngAxisF aa(mat);
dSprintf(returnBuffer,256,"%g %g %g %g %g %g %g",
pos.x,pos.y,pos.z,aa.axis.x,aa.axis.y,aa.axis.z,aa.angle);
return returnBuffer;
}I've had tiny sparks of realization on how the world transform here could be used to properly do this alignment, but I can't bring it through to conclusion. I realize that the local coordinate system's axis for the object needs to be used for the offset transform but I still don't know how to apply it in the script. For example:
%edgeOffset = mAbs(getWord(%objToSnap.getObjectBox(), 0)); %transformWithOffset = getWord(%objTarget.getTransform(), 0) + getWord(%objTarget.getObjectBox(), 3) + %edgeOffset; %objToSnap.setTransform( setWord(%objToSnap.getTransform(), 0, %transformWithOffset) );
This does all the proper distance calculations, but the final transform application has to happen taking into account the rotated local axis, not the world axis. How do I manipulate these within the domain of the local axes instead of the world axes? Or am I off in the weeds?
#13
I realize now that the getWorldTransform IS the representation of the local space, so in theory I could getWorldTransform, apply my calculated offset and have a new world transform where the proper location should be.
Now all I'm stuck on is how to take the modified world transform and convert it back to something I can use with setTransform...
07/10/2004 (2:36 pm)
There goes another one of those sparks...I realize now that the getWorldTransform IS the representation of the local space, so in theory I could getWorldTransform, apply my calculated offset and have a new world transform where the proper location should be.
Now all I'm stuck on is how to take the modified world transform and convert it back to something I can use with setTransform...
#14
I've changed all the original snap functions (from 'snapToX' through 'snapToZNeg') to use the world box. This does nothing for non-rotated items, but for rotated items it has a bit more of a useful result; it snaps using the world bounding boxes instead of the AABB of the object. I made the script changes in my original comment. Just remove your existing snapTo functions (from snapToX through snapToZNeg) and use the ones above instead.
Second, I've implemented something that approaches the real desired snap capability. For these changes, you'll need to implement the getWorldTransform addition in engine code first (two comments back.)
Then (below the existing snapTo functions) you can add:
These are rotational-happy snaps for a given side. They're a work in progress as I chew through the math, so not the prettiest code. I'm sure some of the matrix math could be cleaned up.
These aren't single axis alignments either, they snap the other two axes to center, which works well for my cloning stuff. These could be broken out into individual axis snaps, but I consider those a lower priority and I need time for my brain to stop smoking. :)
Anyway, change the following code to access these new functions from the editor. If you don't have the cloneTo function implemented, skip that change.
In the cloneTo function find:
Then in the SnapTo menu definition, just below this line:
Finally, in the real snapTo function (the big switch), below:
That's all there is, cloning now defaults to use the object-space snap and shift-numpad keys perform the new object-snap instead of the worldbox snap (which are the numberpad keys alone).
Two objects that are rotated differently will probably not snap very well because I don't account for the two separate object spaces. For best results, make sure the two objects have the exact same rotation.
Luke
07/10/2004 (7:19 pm)
Two updates:I've changed all the original snap functions (from 'snapToX' through 'snapToZNeg') to use the world box. This does nothing for non-rotated items, but for rotated items it has a bit more of a useful result; it snaps using the world bounding boxes instead of the AABB of the object. I made the script changes in my original comment. Just remove your existing snapTo functions (from snapToX through snapToZNeg) and use the ones above instead.
Second, I've implemented something that approaches the real desired snap capability. For these changes, you'll need to implement the getWorldTransform addition in engine code first (two comments back.)
Then (below the existing snapTo functions) you can add:
function WorldEditor::snapToObjXPosYZ(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getObjectBox(), 3)) + mAbs(getWord(%objTarget.getObjectBox(), 0));
%worldTransform = %objTarget.getWorldTransform();
%worldTransform = setWord(%worldTransform, 0, getWord(%worldTransform, 0) + %edgeOffset);
%offsetMatrix = MatrixMultiply(%objTarget.getTransform(), %worldTransform);
%newTransform = %objToSnap.getTransform();
%newTransform = setWord(%newTransform, 0, getWord(%objTarget.getTransform(), 0) + getWord(%offsetMatrix, 0) );
%newTransform = setWord(%newTransform, 1, getWord(%objTarget.getTransform(), 1) + getWord(%offsetMatrix, 1) );
%newTransform = setWord(%newTransform, 2, getWord(%objTarget.getTransform(), 2) + getWord(%offsetMatrix, 2) );
%objToSnap.setTransform( %newTransform );
}
function WorldEditor::snapToObjXNegYZ(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getObjectBox(), 0)) + mAbs(getWord(%objTarget.getObjectBox(), 3));
%worldTransform = %objTarget.getWorldTransform();
%worldTransform = setWord(%worldTransform, 0, getWord(%worldTransform, 0) - %edgeOffset);
%offsetMatrix = MatrixMultiply(%objTarget.getTransform(), %worldTransform);
%newTransform = %objToSnap.getTransform();
%newTransform = setWord(%newTransform, 0, getWord(%objTarget.getTransform(), 0) + getWord(%offsetMatrix, 0) );
%newTransform = setWord(%newTransform, 1, getWord(%objTarget.getTransform(), 1) + getWord(%offsetMatrix, 1) );
%newTransform = setWord(%newTransform, 2, getWord(%objTarget.getTransform(), 2) + getWord(%offsetMatrix, 2) );
%objToSnap.setTransform( %newTransform );
}
function WorldEditor::snapToObjXYPosZ(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getObjectBox(), 4)) + mAbs(getWord(%objTarget.getObjectBox(), 1));
%worldTransform = %objTarget.getWorldTransform();
%worldTransform = setWord(%worldTransform, 1, getWord(%worldTransform, 1) + %edgeOffset);
%offsetMatrix = MatrixMultiply(%objTarget.getTransform(), %worldTransform);
%newTransform = %objToSnap.getTransform();
%newTransform = setWord(%newTransform, 0, getWord(%objTarget.getTransform(), 0) + getWord(%offsetMatrix, 0) );
%newTransform = setWord(%newTransform, 1, getWord(%objTarget.getTransform(), 1) + getWord(%offsetMatrix, 1) );
%newTransform = setWord(%newTransform, 2, getWord(%objTarget.getTransform(), 2) + getWord(%offsetMatrix, 2) );
%objToSnap.setTransform( %newTransform );
}
function WorldEditor::snapToObjXYNegZ(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getObjectBox(), 1)) + mAbs(getWord(%objTarget.getObjectBox(), 4));
%worldTransform = %objTarget.getWorldTransform();
%worldTransform = setWord(%worldTransform, 1, getWord(%worldTransform, 1) - %edgeOffset);
%offsetMatrix = MatrixMultiply(%objTarget.getTransform(), %worldTransform);
%newTransform = %objToSnap.getTransform();
%newTransform = setWord(%newTransform, 0, getWord(%objTarget.getTransform(), 0) + getWord(%offsetMatrix, 0) );
%newTransform = setWord(%newTransform, 1, getWord(%objTarget.getTransform(), 1) + getWord(%offsetMatrix, 1) );
%newTransform = setWord(%newTransform, 2, getWord(%objTarget.getTransform(), 2) + getWord(%offsetMatrix, 2) );
%objToSnap.setTransform( %newTransform );
}
function WorldEditor::snapToObjXYZPos(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getObjectBox(), 5)) + mAbs(getWord(%objTarget.getObjectBox(), 2));
%worldTransform = %objTarget.getWorldTransform();
%worldTransform = setWord(%worldTransform, 2, getWord(%worldTransform, 2) + %edgeOffset);
%offsetMatrix = MatrixMultiply(%objTarget.getTransform(), %worldTransform);
%newTransform = %objToSnap.getTransform();
%newTransform = setWord(%newTransform, 0, getWord(%objTarget.getTransform(), 0) + getWord(%offsetMatrix, 0) );
%newTransform = setWord(%newTransform, 1, getWord(%objTarget.getTransform(), 1) + getWord(%offsetMatrix, 1) );
%newTransform = setWord(%newTransform, 2, getWord(%objTarget.getTransform(), 2) + getWord(%offsetMatrix, 2) );
%objToSnap.setTransform( %newTransform );
}
function WorldEditor::snapToObjXYZNeg(%this, %objTarget, %objToSnap)
{
%edgeOffset = mAbs(getWord(%objToSnap.getObjectBox(), 2)) + mAbs(getWord(%objTarget.getObjectBox(), 5));
%worldTransform = %objTarget.getWorldTransform();
%worldTransform = setWord(%worldTransform, 2, getWord(%worldTransform, 2) - %edgeOffset);
%offsetMatrix = MatrixMultiply(%objTarget.getTransform(), %worldTransform);
%newTransform = %objToSnap.getTransform();
%newTransform = setWord(%newTransform, 0, getWord(%objTarget.getTransform(), 0) + getWord(%offsetMatrix, 0) );
%newTransform = setWord(%newTransform, 1, getWord(%objTarget.getTransform(), 1) + getWord(%offsetMatrix, 1) );
%newTransform = setWord(%newTransform, 2, getWord(%objTarget.getTransform(), 2) + getWord(%offsetMatrix, 2) );
%objToSnap.setTransform( %newTransform );
}These are rotational-happy snaps for a given side. They're a work in progress as I chew through the math, so not the prettiest code. I'm sure some of the matrix math could be cleaned up.
These aren't single axis alignments either, they snap the other two axes to center, which works well for my cloning stuff. These could be broken out into individual axis snaps, but I consider those a lower priority and I need time for my brain to stop smoking. :)
Anyway, change the following code to access these new functions from the editor. If you don't have the cloneTo function implemented, skip that change.
In the cloneTo function find:
%this.snapTo(%snapType, %objTarget, %objToSnap);and change it to:
%this.snapTo("Obj" @ %snapType, %objTarget, %objToSnap);Then in the SnapTo menu definition, just below this line:
EditorMenuBar.addMenuItem("SnapTo", "XYZ-", 13, "numpad3");add these:EditorMenuBar.addMenuItem("SnapTo", "ObjX+YZ", 14, "shift numpad6");
EditorMenuBar.addMenuItem("SnapTo", "ObjX-YZ", 15, "shift numpad4");
EditorMenuBar.addMenuItem("SnapTo", "ObjXY+Z", 16, "shift numpad8");
EditorMenuBar.addMenuItem("SnapTo", "ObjXY-Z", 17, "shift numpad2");
EditorMenuBar.addMenuItem("SnapTo", "ObjXYZ+", 18, "shift numpad9");
EditorMenuBar.addMenuItem("SnapTo", "ObjXYZ-", 19, "shift numpad3");Finally, in the real snapTo function (the big switch), below:
case "XY-Z": %this.snapToXYNegZ(%objTarget, %objToSnap); case "XYZ+": %this.snapToXYZPos(%objTarget, %objToSnap); case "XYZ-": %this.snapToXYZNeg(%objTarget, %objToSnap);Add these lines:
case "ObjX+YZ": %this.snapToObjXPosYZ(%objTarget, %objToSnap); case "ObjX-YZ": %this.snapToObjXNegYZ(%objTarget, %objToSnap); case "ObjXY+Z": %this.snapToObjXYPosZ(%objTarget, %objToSnap); case "ObjXY-Z": %this.snapToObjXYNegZ(%objTarget, %objToSnap); case "ObjXYZ+": %this.snapToObjXYZPos(%objTarget, %objToSnap); case "ObjXYZ-": %this.snapToObjXYZNeg(%objTarget, %objToSnap);
That's all there is, cloning now defaults to use the object-space snap and shift-numpad keys perform the new object-snap instead of the worldbox snap (which are the numberpad keys alone).
Two objects that are rotated differently will probably not snap very well because I don't account for the two separate object spaces. For best results, make sure the two objects have the exact same rotation.
Luke
#15
12/06/2004 (10:57 am)
this is really hard to follow, is it possible you could make a new resource please Luke D?
#16
12/06/2004 (11:14 am)
Yeah sorry it is quite a ramble. I've been meaning to boil it down to a new resource, I've already had a few requests for such but I've not had time to re-create it all and make sure it works. I'll try hard to get to it real soon though. I'd love for people to take it even further and fix up the quirks with scaled and oddly rotated objects, which I never got around to. Hopefully a concise resource will help that too. :)
#17
02/08/2005 (4:33 pm)
Heres a copy of /common/EditorGui.cs with the changes made as a zip. Just back up your old one and replace it with this one. You still need to make the engine changes outlined a few posts up.
#18
I have tried out the same code with TGE 1.30 and it works ok ( at least the clone tools does)
ktg
03/05/2005 (5:24 pm)
Does this resource work with TSE, i included it and the menu items show up, but it does not do anything on following the instructions for selection.I have tried out the same code with TGE 1.30 and it works ok ( at least the clone tools does)
ktg
#19
ConsoleMethod( WorldEditor, getSelectObject, S32, 3, 3, "(int index)") ;
where as the EditorGui.cs script is expecting getSelectedObject;
In TGE the console method is
ConsoleMethod( WorldEditor, getSelectedObject, S32, 3, 3, "(int index)") ;
whats the process for reporting these sort of typos, I changed my copy of the script to get it work with TSE
ktg
03/06/2005 (9:24 am)
found the reason why it was not working in TSE. The following console method within the engine in WorldEditor.cpp use getSelectObject i.eConsoleMethod( WorldEditor, getSelectObject, S32, 3, 3, "(int index)") ;
where as the EditorGui.cs script is expecting getSelectedObject;
In TGE the console method is
ConsoleMethod( WorldEditor, getSelectedObject, S32, 3, 3, "(int index)") ;
whats the process for reporting these sort of typos, I changed my copy of the script to get it work with TSE
ktg
#20
creator -> GuiTreeViewCtrl -> GuiArrayCtrl -> GuiControl -> SimGroup -> SimSet -> SimObject
creator/editor/EditorGui.cs (1529): Unknown command addItem.
Once for every addGroup and addItem call in editorGui.cs.
What may have caused this?
03/03/2006 (1:07 pm)
In the process of adding in this resource, my entire object tree in WorldEditor disappeared. Errors in console say:creator -> GuiTreeViewCtrl -> GuiArrayCtrl -> GuiControl -> SimGroup -> SimSet -> SimObject
creator/editor/EditorGui.cs (1529): Unknown command addItem.
Once for every addGroup and addItem call in editorGui.cs.
What may have caused this?
Associate Ben Garney