private void DecideArrowDrawing(Matrix4F localToWorld, Camera camera, out bool drawX, out bool drawY, out bool drawZ) { // Use our axis in world space, so we can prevent picking of lines and squares that // are being viewed edge-on which leads to numerical instability. Vec3F xAxis = localToWorld.XAxis; Vec3F yAxis = localToWorld.YAxis; Vec3F zAxis = localToWorld.ZAxis; Vec3F control = localToWorld.Translation; Vec3F eyeToControl; if (camera.ProjectionType == ProjectionType.Perspective) { eyeToControl = control - camera.WorldEye; eyeToControl.Normalize(); } else { eyeToControl = camera.WorldLookAt; } drawX = Math.Abs(Vec3F.Dot(eyeToControl, xAxis)) < 1.0f - EdgeAngle; drawY = Math.Abs(Vec3F.Dot(eyeToControl, yAxis)) < 1.0f - EdgeAngle; drawZ = Math.Abs(Vec3F.Dot(eyeToControl, zAxis)) < 1.0f - EdgeAngle; }
/// <summary> /// Projects the specified x and y, in normalized window coordinates, onto the grid, /// and snaps it to the nearest grid vertex if necessary. /// Normalized window coordinates are in the range [-0.5,0.5] with +x pointing to the /// right and +y pointing up.</summary> /// <param name="x">Window x in normalized window coords</param> /// <param name="y">Window y in normalized window coords</param> /// <param name="camera">Camera</param> /// <returns>Projection of x and y onto the grid, in world space.</returns> public Vec3F Project(float x, float y, Camera camera) { Ray3F ray = camera.CreateRay(x, y); Matrix4F V = new Matrix4F(camera.ViewMatrix); V.Mul(m_invAxisSystem, V); if (camera.Frustum.IsOrtho) { V = new Matrix4F(m_V); V.Translation = camera.ViewMatrix.Translation; } // origin Vec3F delta = new Vec3F(0, Height, 0); V.Transform(delta, out delta); Vec3F o = delta; // Up vec Vec3F axis = V.YAxis; Vec3F projPt = ray.IntersectPlane(axis, -Vec3F.Dot(o, axis)); // Transform back into world space Matrix4F Inv = new Matrix4F(); Inv.Invert(camera.ViewMatrix); Inv.Transform(projPt, out projPt); if (Snap) { projPt = SnapPoint(projPt); } return projPt; }
/// <summary> /// Gets the view matrix corresponding to a view direction</summary> /// <param name="viewDirection">View direction</param> /// <returns>View matrix corresponding to the view direction</returns> public static Matrix4F LookAtMatrix(Vec3F viewDirection) { // check if viewDirection is non-zero length and normalize it if so. float length = viewDirection.Length; if (length == 0.0f) { return(new Matrix4F()); } viewDirection = viewDirection * (1.0f / length); // find a basis vector (x-axis or y-axis) that is not parallel to viewDirection. // give preference to y-axis for historical purposes. // Create 's' and 'u' that are orthonormal vectors, relative to viewDirection. // 'viewDirection' is like the z-axis. 's' is the x-axis, and 'u' is the y-axis. // We want: s X u = viewDir so that resulting coordinate system is right-handed. // Otherwise, you get the triangles all flipped. Vec3F s; Vec3F xAxis = new Vec3F(1, 0, 0); Vec3F yAxis = new Vec3F(0, 1, 0); if (Math.Abs(Vec3F.Dot(yAxis, viewDirection)) < 0.98) { s = Vec3F.Cross(yAxis, viewDirection); //x' = y X z } else { // viewDirection == yAxis s = Vec3F.Cross(viewDirection, xAxis); //x' = y X x } Vec3F u = Vec3F.Cross(viewDirection, s); //y' = z X x' s = s / s.Length; u = u / u.Length; Matrix4F T = new Matrix4F ( s.X, s.Y, s.Z, 0, u.X, u.Y, u.Z, 0, viewDirection.X, viewDirection.Y, viewDirection.Z, 0, 0, 0, 0, 1 ); return(T); }
private void Track(Point current) { if (current.X < 0 || current.X > m_width) { return; } if (current.Y < 0 || current.Y > m_height) { return; } Vec3F currentPoint = ProjectToArcball(current); Vec3F i = Vec3F.Cross(currentPoint, m_firstPoint); float r = Vec3F.Dot(m_firstPoint, currentPoint); m_currentRotation = new QuatF(i.X, i.Y, i.Z, r); }
private static float CalcAngle(Vec3F v0, Vec3F v1, Vec3F axis, float snapAngle) { const float twoPi = (float)(2.0 * Math.PI); float angle = Vec3F.Dot(v0, v1); if (angle > 1.0f) { angle = 1.0f; } else if (angle <= -1.0f) { angle = 1.0f; } angle = (float)Math.Acos(angle); // Check if v0 is left of v1 Vec3F cross = Vec3F.Cross(v0, v1); if (Vec3F.Dot(cross, axis) < 0) { angle = twoPi - angle; } // round to nearest multiple of preference if (snapAngle > 0) { if (angle >= 0) { int n = (int)((angle + snapAngle * 0.5f) / snapAngle); angle = n * snapAngle; } else { int n = (int)((angle - snapAngle * 0.5f) / snapAngle); angle = n * snapAngle; } } // const float epsilone = (float)1e-7; // if (twoPi - angle <= epsilone) angle = 0.0f; return(angle); }
private static float CalcAngle(Vec3F v0, Vec3F v1, Vec3F axis, float snapAngle) { float angle = Vec3F.Dot(v0, v1); if (angle > 1.0f) { angle = 1.0f; } else if (angle < -1.0f) { angle = 1.0f; } angle = (float)Math.Acos(angle); // Check if v0 is left of v1 Vec3F cross = Vec3F.Cross(v0, v1); if (Vec3F.Dot(cross, axis) < 0) { angle = -angle; } // round to nearest multiple of preference if (snapAngle > 0) { if (angle >= 0) { int n = (int)((angle + snapAngle * 0.5) / snapAngle); angle = n * snapAngle; } else { int n = (int)((angle - snapAngle * 0.5) / snapAngle); angle = n * snapAngle; } } return(angle); }
public override void OnDragging(ViewControl vc, Point scrPt) { if (m_hitRegion == HitRegion.None || m_activeOp == null || m_activeOp.NodeList.Count == 0) { return; } Matrix4F view = vc.Camera.ViewMatrix; // compute world * view Matrix4F wv = new Matrix4F(); wv.Mul(HitMatrix, view); // create ray in view space. Ray3F rayV = vc.GetRay(scrPt, vc.Camera.ProjectionMatrix); Vec3F xAxis = wv.XAxis; Vec3F yAxis = wv.YAxis; Vec3F zAxis = wv.ZAxis; Vec3F origin = wv.Translation; m_scale = new Vec3F(1, 1, 1); float scale = 1; float a1, a2; switch (m_hitRegion) { case HitRegion.XAxis: case HitRegion.NegXAxis: { a1 = Math.Abs(Vec3F.Dot(HitRayV.Direction, yAxis)); a2 = Math.Abs(Vec3F.Dot(HitRayV.Direction, zAxis)); Vec3F axis = (a1 > a2 ? yAxis : zAxis); Vec3F p0 = HitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), xAxis); if (m_hitRegion == HitRegion.NegXAxis) { dragAmount *= -1; } m_scale.X = 1.0f + dragAmount / m_hitScale; scale = m_scale.X; } break; case HitRegion.YAxis: case HitRegion.NegYAxis: { a1 = Math.Abs(Vec3F.Dot(HitRayV.Direction, zAxis)); a2 = Math.Abs(Vec3F.Dot(HitRayV.Direction, xAxis)); Vec3F axis = (a1 > a2 ? zAxis : xAxis); Vec3F p0 = HitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), yAxis); if (m_hitRegion == HitRegion.NegYAxis) { dragAmount *= -1; } m_scale.Y = 1.0f + dragAmount / m_hitScale; scale = m_scale.Y; } break; case HitRegion.ZAxis: case HitRegion.NegZAxis: { a1 = Math.Abs(Vec3F.Dot(HitRayV.Direction, xAxis)); a2 = Math.Abs(Vec3F.Dot(HitRayV.Direction, yAxis)); Vec3F axis = (a1 > a2 ? xAxis : yAxis); Vec3F p0 = HitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), zAxis); if (m_hitRegion == HitRegion.NegZAxis) { dragAmount *= -1; } m_scale.Z = 1.0f + dragAmount / m_hitScale; scale = m_scale.Z; } break; default: throw new ArgumentOutOfRangeException(); } if (m_isUniformScaling) { m_scale = new Vec3F(scale, scale, scale); } // scale for (int i = 0; i < m_activeOp.NodeList.Count; i++) { ITransformable node = m_activeOp.NodeList[i]; node.Scale = Vec3F.Mul(m_originalScales[i], m_scale); Matrix4F mtrx = TransformUtils.CalcTransform( Vec3F.ZeroVector, node.Rotation, node.Scale, m_pivotOffset[i]); node.Translation = m_originalTranslations[i] + mtrx.Translation; } }
public override void OnDragging(ViewControl vc, Point scrPt) { if (m_hitRegion == HitRegion.None || NodeList.Count == 0) { return; } Matrix4F view = vc.Camera.ViewMatrix; // compute world * view Matrix4F wv = new Matrix4F(); wv.Mul(HitMatrix, view); // create ray in view space. Ray3F rayV = vc.GetRay(scrPt, vc.Camera.ProjectionMatrix); Vec3F xAxis = wv.XAxis; Vec3F yAxis = wv.YAxis; Vec3F zAxis = wv.ZAxis; Vec3F origin = wv.Translation; //Vec3F pos; m_scale = new Vec3F(1, 1, 1); float scale = 1; float a1, a2; switch (m_hitRegion) { case HitRegion.XAxis: { a1 = Math.Abs(Vec3F.Dot(HitRayV.Direction, yAxis)); a2 = Math.Abs(Vec3F.Dot(HitRayV.Direction, zAxis)); Vec3F axis = (a1 > a2 ? yAxis : zAxis); Vec3F p0 = HitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), xAxis); m_scale.X = 1.0f + dragAmount / m_hitScale; scale = m_scale.X; } break; case HitRegion.YAxis: { a1 = Math.Abs(Vec3F.Dot(HitRayV.Direction, zAxis)); a2 = Math.Abs(Vec3F.Dot(HitRayV.Direction, xAxis)); Vec3F axis = (a1 > a2 ? zAxis : xAxis); Vec3F p0 = HitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), yAxis); m_scale.Y = 1.0f + dragAmount / m_hitScale; scale = m_scale.Y; } break; case HitRegion.ZAxis: { a1 = Math.Abs(Vec3F.Dot(HitRayV.Direction, xAxis)); a2 = Math.Abs(Vec3F.Dot(HitRayV.Direction, yAxis)); Vec3F axis = (a1 > a2 ? xAxis : yAxis); Vec3F p0 = HitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), zAxis); m_scale.Z = 1.0f + dragAmount / m_hitScale; scale = m_scale.Z; } break; case HitRegion.FreeRect: { Vec3F axis = new Vec3F(0, 0, 1); Vec3F p0 = HitRayV.IntersectPlane(axis, -origin.Z); Vec3F p1 = rayV.IntersectPlane(axis, -origin.Z); Vec3F dragVec = p1 - p0; float dragAmount = 1.0f + dragVec.X / m_hitScale; m_scale.X = dragAmount; m_scale.Y = dragAmount; m_scale.Z = dragAmount; scale = m_scale.X; } break; default: throw new ArgumentOutOfRangeException(); } if (m_isUniformScaling) { m_scale = new Vec3F(scale, scale, scale); } // scale for (int i = 0; i < NodeList.Count; i++) { ITransformable transformable = NodeList[i]; transformable.Scale = Vec3F.Mul(m_originalValues[i], m_scale); } }
public Vec3F OnDragging(Ray3F rayV) { if (m_hitRegion == HitRegion.None) { return(Vec3F.ZeroVector); } Vec3F xLocal = m_hitMatrix.XAxis; Vec3F yLocal = m_hitMatrix.YAxis; Vec3F zLocal = m_hitMatrix.ZAxis; Vec3F xAxis = m_hitWorldView.XAxis; Vec3F yAxis = m_hitWorldView.YAxis; Vec3F zAxis = m_hitWorldView.ZAxis; Vec3F origin = m_hitWorldView.Translation; Vec3F translate; float a1, a2; switch (m_hitRegion) { case HitRegion.XAxis: { a1 = Math.Abs(Vec3F.Dot(m_hitRayV.Direction, yAxis)); a2 = Math.Abs(Vec3F.Dot(m_hitRayV.Direction, zAxis)); Vec3F axis = (a1 > a2 ? yAxis : zAxis); Vec3F p0 = m_hitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), xAxis); translate = dragAmount * xLocal; } break; case HitRegion.YAxis: { a1 = Math.Abs(Vec3F.Dot(m_hitRayV.Direction, zAxis)); a2 = Math.Abs(Vec3F.Dot(m_hitRayV.Direction, xAxis)); Vec3F axis = (a1 > a2 ? zAxis : xAxis); Vec3F p0 = m_hitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), yAxis); translate = dragAmount * yLocal; } break; case HitRegion.ZAxis: { a1 = Math.Abs(Vec3F.Dot(m_hitRayV.Direction, xAxis)); a2 = Math.Abs(Vec3F.Dot(m_hitRayV.Direction, yAxis)); Vec3F axis = (a1 > a2 ? xAxis : yAxis); Vec3F p0 = m_hitRayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = rayV.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot((p1 - p0), zAxis); translate = dragAmount * zLocal; } break; case HitRegion.XYSquare: { Vec3F p0 = m_hitRayV.IntersectPlane(zAxis, -Vec3F.Dot(zAxis, origin)); Vec3F p1 = rayV.IntersectPlane(zAxis, -Vec3F.Dot(zAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragX = Vec3F.Dot(xAxis, deltaLocal); float dragY = Vec3F.Dot(yAxis, deltaLocal); translate = dragX * xLocal + dragY * yLocal; } break; case HitRegion.YZSquare: { Vec3F p0 = m_hitRayV.IntersectPlane(xAxis, -Vec3F.Dot(xAxis, origin)); Vec3F p1 = rayV.IntersectPlane(xAxis, -Vec3F.Dot(xAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragY = Vec3F.Dot(yAxis, deltaLocal); float dragZ = Vec3F.Dot(zAxis, deltaLocal); translate = dragY * yLocal + dragZ * zLocal; } break; case HitRegion.XZSquare: { Vec3F p0 = m_hitRayV.IntersectPlane(yAxis, -Vec3F.Dot(yAxis, origin)); Vec3F p1 = rayV.IntersectPlane(yAxis, -Vec3F.Dot(yAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragX = Vec3F.Dot(xAxis, deltaLocal); float dragZ = Vec3F.Dot(zAxis, deltaLocal); translate = dragX * xLocal + dragZ * zLocal; } break; default: throw new ArgumentOutOfRangeException(); } return(translate); }
/// <summary> /// Given an object's current Euler angles and a surface normal, will calculate /// the Euler angles necessary to rotate the object so that it's up-vector is /// aligned with the surface normal. /// </summary> /// <param name="originalEulers">From an IRenderableNode.Rotation, for example.</param> /// <param name="surfaceNormal">a unit vector that the object should be rotate-snapped to</param> /// <param name="upAxis">y or z is up?</param> /// <returns>The resulting angles to be assigned to IRenderableNode.Rotation</returns> /// <remarks> /// Note that QuatF was attempted to be used, but I could not get it to work reliably /// with the Matrix3F.GetEulerAngles(). Numerical instability? The basis vector /// method below works well except for when the target surface is 90 degrees different /// than the starting up vector in which case the rotation around the up vector is lost /// (gimbal lock) but the results are always valid in the sense that the up vector /// is aligned with the surface normal. --Ron Little /// </remarks> public static Vec3F RotateToVector(Vec3F originalEulers, Vec3F surfaceNormal, AxisSystemType upAxis) { // get basis vectors for the current rotation Matrix3F rotMat = new Matrix3F(); rotMat.Rotation(originalEulers); Vec3F a1 = rotMat.XAxis; Vec3F a2 = rotMat.YAxis; Vec3F a3 = rotMat.ZAxis; // calculate destination basis vectors Vec3F b1, b2, b3; if (upAxis == AxisSystemType.YIsUp) { // a2 is the current up vector. b2 is the final up vector. // now, find either a1 or a3, whichever is most orthogonal to surface b2 = new Vec3F(surfaceNormal); float a1DotS = Vec3F.Dot(a1, surfaceNormal); float a3DotS = Vec3F.Dot(a3, surfaceNormal); if (Math.Abs(a1DotS) < Math.Abs(a3DotS)) { b1 = new Vec3F(a1); b3 = Vec3F.Cross(b1, b2); b1 = Vec3F.Cross(b2, b3); } else { b3 = new Vec3F(a3); b1 = Vec3F.Cross(b2, b3); b3 = Vec3F.Cross(b1, b2); } } else { // a3 is the current up vector. b3 is the final up vector. // now, find either a1 or a2, whichever is most orthogonal to surface b3 = new Vec3F(surfaceNormal); float a1DotS = Vec3F.Dot(a1, surfaceNormal); float a2DotS = Vec3F.Dot(a2, surfaceNormal); if (Math.Abs(a1DotS) < Math.Abs(a2DotS)) { b1 = new Vec3F(a1); b2 = Vec3F.Cross(b3, b1); b1 = Vec3F.Cross(b2, b3); } else { b2 = new Vec3F(a2); b1 = Vec3F.Cross(b2, b3); b2 = Vec3F.Cross(b3, b1); } } // in theory, this isn't necessary, but in practice... b1.Normalize(); b2.Normalize(); b3.Normalize(); // construct new rotation matrix and extract euler angles rotMat.XAxis = b1; rotMat.YAxis = b2; rotMat.ZAxis = b3; Vec3F newEulers = new Vec3F(); rotMat.GetEulerAngles(out newEulers.X, out newEulers.Y, out newEulers.Z); return(newEulers); }
/// <summary> /// Performs actions during control drag</summary> /// <param name="hit">Hit record</param> /// <param name="x">Mouse x position</param> /// <param name="y">Mouse y position</param> /// <param name="action">Render action</param> /// <param name="camera">Camera</param> /// <param name="transform">Transform</param> /// <returns>Translation, in world coordinates</returns> public Vec3F OnDrag(HitRecord hit, float x, float y, IRenderAction action, Camera camera, Matrix4F transform) { float a1, a2; Matrix4F W = new Matrix4F(); W.Mul(transform, camera.ViewMatrix); // Setup rays, in view space. (-z goes into the screen.) Ray3F ray0 = camera.CreateRay(m_iX, m_iY); Ray3F ray = camera.CreateRay(x, y); // Build axis and origin in view space Vec3F xAxis = W.XAxis; Vec3F yAxis = W.YAxis; Vec3F zAxis = W.ZAxis; Vec3F origin = W.Translation; Vec3F trans = new Vec3F(); // Choose the best projection plane according to the projection angle switch ((HitElement)hit.RenderObjectData[1]) { case HitElement.X_ARROW: { a1 = Math.Abs(Vec3F.Dot(ray0.Direction, yAxis)); a2 = Math.Abs(Vec3F.Dot(ray0.Direction, zAxis)); Vec3F axis = (a1 > a2 ? yAxis : zAxis); Vec3F p0 = ray0.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = ray.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot(xAxis, p1 - p0); Vec3F xLocal = transform.XAxis; trans = dragAmount * xLocal; } break; case HitElement.Y_ARROW: { a1 = Math.Abs(Vec3F.Dot(ray0.Direction, zAxis)); a2 = Math.Abs(Vec3F.Dot(ray0.Direction, xAxis)); Vec3F axis = (a1 > a2 ? zAxis : xAxis); Vec3F p0 = ray0.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = ray.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot(yAxis, p1 - p0); Vec3F yLocal = transform.YAxis; trans = dragAmount * yLocal; } break; case HitElement.Z_ARROW: { a1 = Math.Abs(Vec3F.Dot(ray0.Direction, xAxis)); a2 = Math.Abs(Vec3F.Dot(ray0.Direction, yAxis)); Vec3F axis = (a1 > a2 ? xAxis : yAxis); Vec3F p0 = ray0.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = ray.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot(zAxis, p1 - p0); Vec3F zLocal = transform.ZAxis; trans = dragAmount * zLocal; } break; case HitElement.XY_SQUARE: { Vec3F p0 = ray0.IntersectPlane(zAxis, -Vec3F.Dot(zAxis, origin)); Vec3F p1 = ray.IntersectPlane(zAxis, -Vec3F.Dot(zAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragX = Vec3F.Dot(xAxis, deltaLocal); float dragY = Vec3F.Dot(yAxis, deltaLocal); Vec3F xLocal = transform.XAxis; Vec3F yLocal = transform.YAxis; trans = dragX * xLocal + dragY * yLocal; } break; case HitElement.YZ_SQUARE: { Vec3F p0 = ray0.IntersectPlane(xAxis, -Vec3F.Dot(xAxis, origin)); Vec3F p1 = ray.IntersectPlane(xAxis, -Vec3F.Dot(xAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragY = Vec3F.Dot(yAxis, deltaLocal); float dragZ = Vec3F.Dot(zAxis, deltaLocal); Vec3F yLocal = transform.YAxis; Vec3F zLocal = transform.ZAxis; trans = dragY * yLocal + dragZ * zLocal; } break; case HitElement.XZ_SQUARE: { Vec3F p0 = ray0.IntersectPlane(yAxis, -Vec3F.Dot(yAxis, origin)); Vec3F p1 = ray.IntersectPlane(yAxis, -Vec3F.Dot(yAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragX = Vec3F.Dot(xAxis, deltaLocal); float dragZ = Vec3F.Dot(zAxis, deltaLocal); Vec3F xLocal = transform.XAxis; Vec3F zLocal = transform.ZAxis; trans = dragX * xLocal + dragZ * zLocal; } break; } return(trans); }