Applying the Coordinates of a Master Controller Widget to a Node

T-HSCP-002-011

When using a widget like the Point2dWidget, which is used to input X and Y coordinates by dragging a dot around the scene, you may want to connect the Master Controller or to a node system so that it is properly positioned in the stage. For example, a widget that controls a body part of a character may be connected to that character's master peg so that it follows the character's movement in the scene.

The coordinates you obtain from a Point2dWidget when it is moved by the user will be relative to its parent peg's position. For example, the Point2dWidget may initially appear in the corner of a scene because it is the child of a character that is positioned there, but its initial position will still be 0, 0, 0, which might not be accurate for the widget's purpose. Hence, before you can use the widget's coordinates to make transformations on an element in the scene, you will have to calculate its actual position in the stage (referred to here as a world point) by applying its parent's transformations, or matrix, to its coordinates. This can be accomplished with the following function:

// Return the given point 2d, in OGL coordinates, in world coordinates. function getWorldPoint2d(modelPoint2d, nodePath) { // Convert the model point into world coordinates. // Apply the parent node transformation to the position // to go from (the node) model coordinates to world coordinates. var parentNode = node.srcNode(nodePath, 0); var parentTransformation = node.getMatrix(parentNode, frame.current()); var modelPoint3d = new Point3d(modelPoint2d.x, modelPoint2d.y, 0); var worldPoint = parentTransformation.multiply(modelPoint3d); return worldPoint; }

If the element that the Point2dWidget is supposed to control is also connected to a node system that applies transformations to it, for example a body part in a character model, applying transformations using a world point is likely going to offset it a lot more than intended. Even if that body part is in a corner of the stage because this is where its character is located, its actual coordinates might also be 0, 0, 0 if only its parents were offset, but it itself has not been moved directly. Hence, before applying the coordinates of the world point calculated above to the target element, you will need to calculate the relative coordinates you want to apply to the element, referred to here as a model point, by applying the reverse matrix of the element's parent to the world point, using a function such as the following one:

// Convert the position from world coordinates to the target model // coordinates. // Apply the inverse of the node parent transformation to the world position // to go from world coordinates to (the node) model coordinates function getModelPoint(worldPoint, nodePath) { var parentNode = node.srcNode(nodePath, 0); var parentTransformation = node.getMatrix(parentNode, frame.current()); var inverseTransformation = parentTransformation.getInverse(); var modelPoint = inverseTransformation.multiply(worldPoint); return modelPoint; }

To bring all this together, several more calculations must be made. For example, you may have to convert points from Harmony's field coordinates system to an orthonormal coordinates system (referred to in the scripting interface as OpenGL coordinates). This is because Harmony's field system does not use the same length for horizontal and vertical fields, which can cause serious inaccuracies, for example, when handling matrices that contain rotations.

The following example demonstrates how to process the coordinates provided by a Point2dWidget and apply them to a peg named MyPeg in the top level of the scene, using the two functions declared above in the process:

// Callback when the position value has changed function point2dValueChanged(newFieldPosition2d) { // The 2d position attributes typically have their value expressed in // field. // Convert the field position into OpenGL coordinates. // The OpenGL point will be expressed in the Master Controller node model // coordinates system. var mcModelPoint2d = scene.toOGL(newFieldPosition2d); // Compute the position of the master controller in world coordinates. var worldPoint = getWorldPoint2d(mcModelPoint2d, "Top/MasterController"); // Convert the position from world coordinates to the target peg model // coordinates // Apply the inverse of the peg parent node transformation to the world // position to go from world coordinates to (the peg) model coordinates var pegNode = "Top/MyPeg"; var pegModelPoint = getModelPoint(worldPoint, pegNode); // Get the peg position which is in fields var fieldCenterPoint = node.getAttr(pegNode, frame.current(), "POSITION" ).pos3dValue(); // Convert from fields to OpenGL coordinates var pegModelCenterPoint = scene.toOGL(fieldCenterPoint); // Compute the new position of the Peg after applying the magnet to it. var newPegModelCenterPoint = computePosition(pegModelPoint, pegModelCenterPoint, 0.001); // Convert from OpenGL to fields coordinates var newPegFieldCenterPoint = scene.fromOGL(newPegModelCenterPoint); // Set the peg attribute values to the new values. node.setTextAttr(pegNode, "POSITION.3DPATH.X", frame.current(), newPegFieldCenterPoint.x); node.setTextAttr(pegNode, "POSITION.3DPATH.Y", frame.current(), newPegFieldCenterPoint.y); node.setTextAttr(pegNode, "POSITION.3DPATH.Z", frame.current(), newPegFieldCenterPoint.z); }
TIP: You can easily obtain the names of a node's attributes for scripting by enabling Publish Attribute Mode in the Node View and opening a node's Layer Properties dialog. For more information, see Getting Node Attribute Names for Scripting.