public bool RemoveKnotAtPosition(Vector3 pos) { CameraPathKnot removeKnot = m_Path.GetKnotAtPosition(pos); if (removeKnot != null) { // If we're removing the last position knot, just destroy the whole path. if (removeKnot is CameraPathPositionKnot && m_Path.NumPositionKnots == 1) { WidgetManager.m_Instance.DeleteCameraPath(this); } else { TrTransform knotXf = TrTransform.TR(pos, removeKnot.transform.rotation); // Gather all the knots affected by this removal. List<CameraPathKnot> pks = m_Path.GetKnotsOrphanedByKnotRemoval(removeKnot); // If we have any knots affected by this change, we need to remove those first, before // we remove the original knot. This is because, on undo, we need to add the parent // first, before adding all the orphaned knots. if (pks.Count > 0) { BaseCommand parent = new BaseCommand(); for (int i = 0; i < pks.Count; ++i) { TrTransform childXf = TrTransform.TR(pos, pks[i].transform.rotation); new RemovePathKnotCommand(this, pks[i], childXf, parent); } new RemovePathKnotCommand(this, removeKnot, knotXf, parent); SketchMemoryScript.m_Instance.PerformAndRecordCommand(parent); } else { SketchMemoryScript.m_Instance.PerformAndRecordCommand( new RemovePathKnotCommand(this, removeKnot, knotXf)); } } // Reset collision results after a delete. for (int i = 0; i < m_LastCollisionResults.Length; ++i) { m_LastCollisionResults[i].Set(null, CameraPathKnot.kDefaultControl, null, null); } } return removeKnot != null; }
public override void FindClosestPointOnSurface(Vector3 pos, out Vector3 surfacePos, out Vector3 surfaceNorm) { // The closest-point functions operate on an unrotated ellipsoid at the origin. // I'll call that coordinate system "ellipse space (ES)". // "object space (OS)" is the true scale-compensated object-space coordinate system. // Take (uniform) scale from parent, but no scale from this object. // Our local scale is instead treated as Extents TrTransform xfWorldFromEllipse = TrTransform.FromTransform(transform.parent) * TrTransform.TR(transform.localPosition, transform.localRotation); TrTransform xfEllipseFromWorld = xfWorldFromEllipse.inverse; Vector3 halfExtent = Extents * .5f; Vector3 pos_OS = transform.InverseTransformPoint(pos); Vector3 pos_ES = xfEllipseFromWorld * pos; Vector3 closest_ES = MathEllipsoidAnton.ClosestPointEllipsoid(halfExtent, pos_ES); // Transform from ES -> OS, get the OS normal, then transform OS -> WS. // Normals are axial, so OS -> WS uses the inv-transpose. That all ends // up simplifying to this: surfaceNorm = transform.rotation * CDiv(closest_ES, CMul(halfExtent, halfExtent)).normalized; surfacePos = xfWorldFromEllipse * closest_ES; }
private void State_ProcessingStraightEdge(bool terminate) { int cpPerFrame = Mathf.Max( m_StraightEdgeControlPoints_CS.Count / STRAIGHTEDGE_DRAWIN_FRAMES, 2); TrTransform xfCanvas = Coords.CanvasPose; for (int p = 0; p < cpPerFrame && m_StraightEdgeControlPointIndex < m_StraightEdgeControlPoints_CS.Count; p++, m_StraightEdgeControlPointIndex++) { ControlPoint cp = m_StraightEdgeControlPoints_CS[m_StraightEdgeControlPointIndex]; TrTransform xfPointer = xfCanvas * TrTransform.TR(cp.m_Pos, cp.m_Orient); SetMainPointerPosition(xfPointer.translation); SetMainPointerRotation(xfPointer.rotation); for (int i = 0; i < m_NumActivePointers; ++i) { m_Pointers[i].m_Script.UpdateLineFromObject(); } var stencil = WidgetManager.m_Instance.ActiveStencil; if (stencil != null) { stencil.AdjustLift(1); } } // we reached the end! if (terminate || m_StraightEdgeControlPointIndex >= m_StraightEdgeControlPoints_CS.Count) { FinalizeLine(); m_CurrentLineCreationState = LineCreationState.WaitingForInput; } }
void UpdateSymmetryPointerTransforms() { switch (m_CurrentSymmetryMode) { case SymmetryMode.SinglePlane: { Plane plane = m_SymmetryWidgetScript.ReflectionPlane; TrTransform xf0 = TrTransform.FromTransform(m_MainPointerData.m_Script.transform); TrTransform xf1 = plane.ReflectPoseKeepHandedness(xf0); xf1.ToTransform(m_Pointers[1].m_Script.transform); // This is a hack. // In the event that the user is painting on a plane stencil and that stencil is // orthogonal to the symmetry plane, the main pointer and mirrored pointer will // have the same depth and their strokes will overlap, causing z-fighting. if (WidgetManager.m_Instance.ActiveStencil != null) { m_Pointers[1].m_Script.transform.position += m_Pointers[1].m_Script.transform.forward * m_SymmetryPointerStencilBoost; } break; } case SymmetryMode.FourAroundY: { TrTransform pointer0 = TrTransform.FromTransform(m_MainPointerData.m_Script.transform); // aboutY is an operator that rotates worldspace objects N degrees around the widget's Y TrTransform aboutY; { var xfWidget = TrTransform.FromTransform(m_SymmetryWidget); float angle = 360f / m_NumActivePointers; aboutY = TrTransform.TR(Vector3.zero, Quaternion.AngleAxis(angle, Vector3.up)); // convert from widget-local coords to world coords aboutY = xfWidget * aboutY * xfWidget.inverse; } TrTransform cur = TrTransform.identity; for (int i = 1; i < m_NumActivePointers; ++i) { cur = aboutY * cur; // stack another rotation on top var tmp = (cur * pointer0); // Work around 2018.3.x Mono parse bug tmp.ToTransform(m_Pointers[i].m_Script.transform); } break; } case SymmetryMode.DebugMultiple: { var xf0 = m_Pointers[0].m_Script.transform; for (int i = 1; i < m_NumActivePointers; ++i) { var xf = m_Pointers[i].m_Script.transform; xf.position = xf0.position + m_SymmetryDebugMultipleOffset * i; xf.rotation = xf0.rotation; } break; } } }
protected override IEnumerable <ControlPoint> DoGetPoints(TrTransform finalTransform) { float radius = (finalTransform.translation - m_initialTransform.translation).magnitude; double time0 = m_initialTime; double time1 = App.Instance.CurrentSketchTime; // The number of times that theta wraps around. float loops; { // Distance is the length of a longitude line, which is half a circumference. float distance = radius * Mathf.PI; loops = distance / m_BrushSize_CS; } // "steal" some of the pretty rotation on the butt end and put it on the front end. float thetaOffset = -(loops * .5f * 2 * Mathf.PI); int pointTotal; { pointTotal = (int)Mathf.Max(loops * 20, 2f); // Keeping this removes a very slight objectionable artifact, likely the result of // the thetaOffset stealing. if (pointTotal % 2 == 1) { pointTotal += 1; } } TrTransform spherePose; { var sphereForward = (finalTransform.translation - m_initialTransform.translation).normalized; var sphereRot = Quaternion.FromToRotation(Vector3.forward, sphereForward); spherePose = TrTransform.TR(m_initialTransform.translation, sphereRot); } for (int k = 0; k < pointTotal; k++) { float t = (float)k / (pointTotal - 1); // parametrization variable float theta = t * loops * 2 * Mathf.PI + thetaOffset; float phi = t * Mathf.PI; TrTransform cpPose; { Vector3 localPos = radius * new Vector3( Mathf.Cos(theta) * Mathf.Sin(phi), Mathf.Sin(theta) * Mathf.Sin(phi), Mathf.Cos(phi)); Quaternion localRot = QuaternionE.AngleAxisRad(theta, Vector3.forward) * // latitudinal motion QuaternionE.AngleAxisRad(phi, Vector3.up); // longitudinal motion cpPose = spherePose * TrTransform.TR(localPos, localRot); } yield return(new ControlPoint { m_Pos = cpPose.translation, m_Orient = cpPose.rotation, m_Pressure = 1, m_TimestampMs = (uint)(Mathf.Lerp((float)time0, (float)time1, t) * kSecondsToMs) }); } }
public void AddPathConstrainedKnot(CameraPathKnot.Type type, Vector3 pos, Quaternion rot) { // Determine the position knot and path t for this new knot. Vector3 error = Vector3.zero; if (m_Path.ProjectPositionOnToPath(pos, out PathT pathT, out error)) { // For PositionKnots, validT is the position in the position list. if (type == CameraPathKnot.Type.Position) { pathT = new PathT(pathT.T + 1.0f); } SketchMemoryScript.m_Instance.PerformAndRecordCommand(new CreatePathKnotCommand( this, type, pathT, TrTransform.TR(pos, rot))); } }
// // BaseBrushScript api // protected override bool UpdatePositionImpl( Vector3 translation, Quaternion rotation, float pressure) { TrTransform parentXf = TrTransform.TR(translation, rotation); // Update m_knots { Debug.Assert(m_knots.Count > 1, "There should always be at least 2 knots"); PbKnot cur = m_knots[m_knots.Count - 1]; PbKnot prev = m_knots[m_knots.Count - 2]; Vector3 move = parentXf.translation - prev.m_pointer.translation; cur.m_pointer = parentXf; float moveLen = move.magnitude; cur.m_tangentFrame = (moveLen > 1e-5f) ? MathUtils.ComputeMinimalRotationFrame( move / moveLen, prev.m_tangentFrame, cur.m_pointer.rotation) : prev.m_tangentFrame; cur.m_distance = prev.m_distance + moveLen; cur.m_pressuredSize = PressuredSize(pressure); } MaybeCreateChildren(); bool createdControlPoint = false; for (int i = 0; i < m_children.Count; ++i) { PbChild child = m_children[i]; var childXf = child.CalculateChildXfFixedScale(m_knots); if (child.m_brush.UpdatePosition_LS(childXf, pressure)) { // Need to save off any control point which is applicable to any of our children. // This does mean that if we have a giant tree of children, we might be saving // off every control point. // TODO: maybe there's a way for the parent to impose some order on this; // like it doesn't always send positions to its children? But that would make // interactive drawing less pretty. createdControlPoint = true; } } if (createdControlPoint) { m_knots.Add(m_knots[m_knots.Count - 1].Clone()); } return(createdControlPoint); }
public void BringToUser() { // Get brush controller and place a little in front and a little higher. Vector3 controllerPos = InputManager.m_Instance.GetController(InputManager.ControllerName.Brush).position; Vector3 headPos = ViewpointScript.Head.position; Vector3 headToController = controllerPos - headPos; Vector3 offset = headToController.normalized * m_JumpToUserControllerOffsetDistance + Vector3.up * m_JumpToUserControllerYOffset; TrTransform xf_GS = TrTransform.TR(controllerPos + offset, transform.rotation); // The transform we built was global space, but we need it in widget local for the command. TrTransform newXf = TrTransform.FromTransform(m_NonScaleChild.parent).inverse *xf_GS; SketchMemoryScript.m_Instance.PerformAndRecordCommand( new MoveWidgetCommand(this, newXf, CustomDimension, final: true), discardIfNotMerged: false); }
/// Given a transform, returns that transform in another basis. /// The new basis is specified by outputFromInput. /// /// Not guaranteed to work if the change-of-basis matrix has non-axis-aligned /// scale, or if the rotation portion can't be expressed as the product of 90 degree /// rotations about an axis. Otherwise, the resulting scale will not "fit" in a Vec3. public static void ChangeBasis( Vector3 inputTranslation, Quaternion inputRotation, Vector3 inputScale, out Vector3 translation, out Quaternion rotation, out Vector3 scale, Matrix4x4 outputFromInput, Matrix4x4 inputFromOutput) { TrTransform output = ChangeBasis( TrTransform.TR(inputTranslation, inputRotation), outputFromInput, inputFromOutput); translation = output.translation; rotation = output.rotation; // Scale is a bit trickier. Matrix4x4 m = outputFromInput * Matrix4x4.Scale(inputScale) * inputFromOutput; scale = new Vector3(m[0, 0], m[1, 1], m[2, 2]); m[0, 0] = m[1, 1] = m[2, 2] = 1; Debug.Assert(m.isIdentity); }
private void FlipSelection() { foreach (var stroke in m_StrokesFlipped) { for (int i = 0; i < stroke.m_ControlPoints.Length; i++) { var xf_CS = m_FlipPlane_CS.ReflectPoseKeepHandedness( TrTransform.TR(stroke.m_ControlPoints[i].m_Pos, stroke.m_ControlPoints[i].m_Orient)); stroke.m_ControlPoints[i].m_Pos = xf_CS.translation; stroke.m_ControlPoints[i].m_Orient = xf_CS.rotation; } // Recreate the stroke stroke.InvalidateCopy(); stroke.Uncreate(); stroke.Recreate(); } foreach (var widget in m_WidgetsFlipped) { if (Canvas != widget.Canvas) { // The widget API we use only operates in the space of the widget's own canvas // We could get around this by transforming m_FlipPlane from the selection canvas // to the widget's canvas, but better to put effort into removing the localScale // restriction from GrabWidget. #if false // aka SelectionCanvasPoseInWidgetCanvasSpace TrTransform widgetCanvasFromSelectionCanvas = widget.Canvas.AsCanvas[Canvas.transform]; Plane flipPlaneInWidgetCanvasSpace = widgetCanvasFromSelectionCanvas * m_FlipPlane_CS; #else Debug.LogError("Cannot currently flip widgets in other canvases"); continue; #endif } widget.LocalTransform = widget.SupportsNegativeSize ? m_FlipPlane_CS.ToTrTransform() * widget.LocalTransform : m_FlipPlane_CS.ReflectPoseKeepHandedness(widget.LocalTransform); } // Update the bounds for the selection widget SelectionManager.m_Instance.UpdateSelectionWidget(); }
// // Mutators // private void BakeGameObjTransform(Stroke stroke) { TrTransform xf_CS = Coords.AsCanvas[TransformForStroke(stroke)]; if (xf_CS == TrTransform.identity) { return; } var cps = stroke.m_ControlPoints; for (int i = 0; i < cps.Length; ++i) { var cp = xf_CS * TrTransform.TR(cps[i].m_Pos, cps[i].m_Orient); cps[i].m_Pos = cp.translation; cps[i].m_Orient = cp.rotation; } stroke.m_BrushScale *= xf_CS.scale; }
/// Given the position of a main pointer, find a corresponding symmetry position. /// Results are undefined unless you pass MainPointer or one of its /// dedicated symmetry pointers. public TrTransform GetSymmetryTransformFor(PointerScript pointer, TrTransform xfMain) { int child = pointer.ChildIndex; // "active pointers" is the number of pointers the symmetry widget is using, // including the main pointer. if (child == 0 || child >= m_NumActivePointers) { return(xfMain); } // This needs to be kept in sync with UpdateSymmetryPointerTransforms switch (m_CurrentSymmetryMode) { case SymmetryMode.SinglePlane: { return(m_SymmetryWidgetScript.ReflectionPlane.ReflectPoseKeepHandedness(xfMain)); } case SymmetryMode.FourAroundY: { // aboutY is an operator that rotates worldspace objects N degrees around the widget's Y TrTransform aboutY; { var xfWidget = TrTransform.FromTransform(m_SymmetryWidget); float angle = (360f * child) / m_NumActivePointers; aboutY = TrTransform.TR(Vector3.zero, Quaternion.AngleAxis(angle, Vector3.up)); // convert from widget-local coords to world coords aboutY = aboutY.TransformBy(xfWidget); } return(aboutY * xfMain); } case SymmetryMode.DebugMultiple: { var xfLift = TrTransform.T(m_SymmetryDebugMultipleOffset * child); return(xfLift * xfMain); } default: return(xfMain); } }
public void TestMengerCurvatureNonDegenerate() { float kRadius = 2.5f; float kInvRadius = 1 / kRadius; Vector3 radius = new Vector3(kRadius, 0, 0); Vector3 axis = new Vector3(0, 1, 0); // should be perpendicular to radius TrTransform arbitrary = TrTransform.TR( new Vector3(5, 8, -10), Quaternion.AngleAxis(35, new Vector3(-1, 3, -5))); // Pick a bunch of triplets on a circle of radius r. // Points are chosen by selecting two arc angles a, b, and computing the third arc angle c. // Repetitions are avoided by choosing a, b, c such that a <= b <= c. // Degenerate cases are avoided by choosing a > 0 (implying a, b, c > 0). // If a > 120, one of b or c will always be smaller than a for (int a = 12; a <= 120; a += 24) { for (int b = a; /*b <= c*/; b += 24) { int c = 360 - a - b; if (!(b <= c)) { break; } Vector3 va = TrTransform.R(0, axis) * radius; Vector3 vb = TrTransform.R(a, axis) * radius; Vector3 vc = TrTransform.R(a + b, axis) * radius; AssertAlmostEqual(kInvRadius, MathUtils.MengerCurvature(va, vb, vc)); Vector3 va2 = arbitrary * va; Vector3 vb2 = arbitrary * vb; Vector3 vc2 = arbitrary * vc; AssertAlmostEqual(kInvRadius, MathUtils.MengerCurvature(va2, vb2, vc2)); } } }
// Continue drawing stroke for this frame. public void Update() { if (m_isDone) { return; } var rPointerScript = m_pointer; var rPointerObject = m_pointer.gameObject; bool needMeshUpdate = false; bool needPointerUpdate = false; bool strokeFinished = false; var lastCp = new PointerManager.ControlPoint(); OverlayManager.m_Instance.UpdateProgress(SketchMemoryScript.m_Instance.GetDrawnPercent()); RdpStrokeSimplifier simplifier = QualityControls.m_Instance.StrokeSimplifier; if (simplifier.Level > 0.0f) { simplifier.CalculatePointsToDrop(m_stroke, m_pointer.CurrentBrushScript); } while (true) { if (m_nextControlPoint >= m_stroke.m_ControlPoints.Length) { needMeshUpdate = true; // Is this really necessary? strokeFinished = true; break; } var cp = m_stroke.m_ControlPoints[m_nextControlPoint]; if (!IsControlPointReady(cp)) { break; } if (!m_stroke.m_ControlPointsToDrop[m_nextControlPoint]) { rPointerScript.UpdateLineFromControlPoint(cp); needMeshUpdate = true; lastCp = cp; needPointerUpdate = true; } ++m_nextControlPoint; } if (needMeshUpdate) { rPointerScript.UpdateLineVisuals(); } if (needPointerUpdate) { // This is only really done for visual reasons var xf_GS = Coords.CanvasPose * TrTransform.TR(lastCp.m_Pos, lastCp.m_Orient); xf_GS.scale = rPointerObject.transform.GetUniformScale(); Coords.AsGlobal[rPointerObject.transform] = xf_GS; rPointerScript.SetPressure(lastCp.m_Pressure); } if (strokeFinished) { rPointerScript.EndLineFromMemory(m_stroke); m_isDone = true; } }