public void TestReadTrTransform() { var xf = Deserialize <TrTransform>("[ [0,1,0], [0,0,0,1], 3]"); Assert.AreEqual(xf, TrTransform.TRS(Vector3.up, Quaternion.identity, 3)); }
/// Non-playback case: /// - Update the stroke based on the object's position. /// - Save off control points /// - Play audio. public void UpdateLineFromObject() { var xf_LS = GetTransformForLine(m_CurrentLine.transform, Coords.AsRoom[transform]); if (!PointerManager.m_Instance.IsMainPointerProcessingLine() && m_CurrentCreator != null) { var straightEdgeGuide = PointerManager.m_Instance.StraightEdgeGuide; if (straightEdgeGuide.SnapEnabled) { // Snapping should be applied before symmetry, and lift is applied // after symmetry, so redo both. TrTransform xfMain_RS = Coords.AsRoom[PointerManager.m_Instance.MainPointer.transform]; xfMain_RS.translation = Coords.CanvasPose * straightEdgeGuide.GetTargetPos(); TrTransform xfSymmetry_RS = PointerManager.m_Instance.GetSymmetryTransformFor( this, xfMain_RS); xf_LS = GetTransformForLine(m_CurrentLine.transform, xfSymmetry_RS); } m_ControlPoints.Clear(); m_ControlPoints.AddRange(m_CurrentCreator.GetPoints(xf_LS)); float scale = xf_LS.scale; m_CurrentLine.ResetBrushForPreview( TrTransform.TRS(m_ControlPoints[0].m_Pos, m_ControlPoints[0].m_Orient, scale)); for (int i = 0; i < m_ControlPoints.Count; ++i) { if (m_CurrentLine.IsOutOfVerts()) { m_ControlPoints.RemoveRange(i, m_ControlPoints.Count - i); break; } m_CurrentLine.UpdatePosition_LS( TrTransform.TRS(m_ControlPoints[i].m_Pos, m_ControlPoints[i].m_Orient, scale), m_ControlPoints[i].m_Pressure); } UpdateLineVisuals(); return; } bool bQuadCreated = m_CurrentLine.UpdatePosition_LS(xf_LS, m_CurrentPressure); // TODO: let brush take care of storing control points, not us SetControlPoint(xf_LS, isKeeper: bQuadCreated); // TODO: Pointers should hold a reference to the stencil they're painting on. This // is a hacky temporary check to ensure mirrored pointers don't add to the lift of // the active stencil. if (PointerManager.m_Instance.MainPointer == this) { // Increase stencil lift if we're painting on one. StencilWidget stencil = WidgetManager.m_Instance.ActiveStencil; if (stencil != null && m_CurrentCreator == null) { float fPointerMovement_CS = GetMovementDelta() / Coords.CanvasPose.scale; stencil.AdjustLift(fPointerMovement_CS); m_LineLength_CS += fPointerMovement_CS; } } UpdateLineVisuals(); /* Update desired brush audio * if (m_AudioSources.Length > 0) { * float fMovementSpeed = Vector3.Distance(m_PreviousPosition, transform.position) / * Time.deltaTime; * * float fVelRangeRange = m_BrushAudioVolumeVelocityRange.y - m_BrushAudioVolumeVelocityRange.x; * float fVolumeRatio = Mathf.Clamp01((fMovementSpeed - m_BrushAudioVolumeVelocityRange.x) / fVelRangeRange); * m_AudioVolumeDesired = fVolumeRatio; * * float fPitchRangeRange = m_BrushAudioPitchVelocityRange.y - m_BrushAudioPitchVelocityRange.x; * float fPitchRatio = Mathf.Clamp01((fMovementSpeed - m_BrushAudioPitchVelocityRange.x) / fPitchRangeRange); * m_AudioPitchDesired = m_BrushAudioBasePitch + (fPitchRatio * m_BrushAudioMaxPitchShift); * } */ }
private void ApplyRevolver(ref Vector3 pos, ref Quaternion rot) { if (!m_RevolverActive) { return; } Transform lAttachPoint = InputManager.m_Instance.GetWandControllerAttachPoint(); Vector3 lPos = lAttachPoint.position; Transform rAttachPoint = InputManager.m_Instance.GetBrushControllerAttachPoint(); Vector3 rPos = rAttachPoint.position; Vector3 guideDelta = lPos - m_btCursorPos; Vector3 radialDelta = Vector3.ProjectOnPlane(rPos - m_btIntersectGoal, guideDelta.normalized); Quaternion radialLookRot = Quaternion.LookRotation(radialDelta.normalized, guideDelta); m_RevolverAngle = m_RevolverAngle + m_RevolverVelocity * 720 * Time.deltaTime; if (m_brushTrigger) { if (InputManager.m_Instance.IsBrushScrollActive()) { float turnRate = InputManager.m_Instance.GetBrushScrollAmount(); // apply a cubic exponential speed curve to make joystick handling easier turnRate = Mathf.Sign(turnRate) * Mathf.Pow(Mathf.Abs(turnRate), 3); m_RevolverVelocity = Mathf.MoveTowards(m_RevolverVelocity, -turnRate, Time.deltaTime * (turnRate == 0 ? 0.15f : 0.333f)); } } else { Transform brushAttachTransform = InputManager.m_Instance.GetBrushControllerAttachPoint(); Quaternion brushWorldRotation = brushAttachTransform.rotation * sm_OrientationAdjust; m_RevolverBrushRotationOffset = radialLookRot.TrueInverse() * brushWorldRotation; BrushGhostTilt = m_RevolverBrushRotationOffset; if (m_RevolverVelocity == 0) { m_RevolverAngle = 0; } } Quaternion spindleRotation = Quaternion.AngleAxis(m_RevolverAngle, guideDelta.normalized); if (m_brushUndoButton) { SetRevolverRadius(m_brushTrigger ? m_lazyInputRate : 1); } Vector3 revolverOffset = spindleRotation * radialDelta.normalized * m_RevolverRadius; Quaternion btCursorRotGoal = spindleRotation * radialLookRot * m_RevolverBrushRotationOffset; m_btCursorRot = btCursorRotGoal; pos = (m_brushTrigger && !m_LazyInputActive ? m_btCursorPos : m_btIntersectGoal) + revolverOffset; rot = m_btCursorRot; BrushGhostTransform = TrTransform.TRS(m_btIntersectGoal, Quaternion.LookRotation(radialDelta.normalized, guideDelta), PointerManager.m_Instance.MainPointer.BrushSizeAbsolute); BrushGhostLerpT = (BrushGhostLerpT + Time.deltaTime * 0.25f) % 1f; }
// Constructs an updated transform obj1 such that the object-space // positions of the left and right grip are invariant. More generally, // all points on the line L->R are invariant. Precisely: // // - inv(obj0) * L0.pos == inv(obj1) * L1.pos // - inv(obj0) * R0.pos == inv(obj1) * L1.pos // // If abs(R0-L0) != abs(R1-L1), then a scaling is necessary. // This method chooses to apply a uniform scale. // // If scale bounds kick in, only a single point on the L->R line can // be made invariant; see bUseLeftAsPivot for how this is chosen. // // Given 2 position deltas, there are 6 DOFs available: // = 3DOF (movement of average position) // + 2DOF (change of direction of vector between L and R) // + 1DOF (scaling, change of length of vector between L and R) // // One more DOF is needed to fully specify the rotation. This method // chooses to get it from L and R's rotations about the L-R vector. // // Pass: // gripL0, L1, R0, R1 - // The left and right grip points, in their old (0) and new (1) positions. // The scale portion of the TrTransform is ignored. // obj0 - // Transform of the object being manipulated. // deltaScale{Min,Max} - // Constrains the range of result.scale. // Negative means do not constrain that endpoint. // rotationAxisConstraint - // Constrains the value of result.rotation. If passed, the delta rotation // from obj0 -> result will only be about this axis. // bUseLeftAsPivot - // Controls the invariant point if scale constraints are applied. // If false, uses the midpoint between L and R. // If true, uses the point L. // // Returns: // New position, rotation, and scale static public TrTransform TwoPointObjectTransformation( TrTransform gripL0, TrTransform gripR0, // prev TrTransform gripL1, TrTransform gripR1, // next TrTransform obj0, float deltaScaleMin = -1.0f, float deltaScaleMax = -1.0f, Vector3 rotationAxisConstraint = default(Vector3), bool bUseLeftAsPivot = false) { // Vectors from left-hand to right-hand Vector3 vLR0 = (gripR0.translation - gripL0.translation); Vector3 vLR1 = (gripR1.translation - gripL1.translation); // World-space position whose object-space position is used to constrain obj1 // inv(obj0) * vInvariant0 = inv(obj1) * vInvariant1 Vector3 vInvariant0, vInvariant1; { // Use left grip or average of grips as pivot point. Maybe switch the // bool to be a parametric t instead, so if caller wants to use right // grip as pivot they don't need to swap arguments? float t = bUseLeftAsPivot ? 0f : 0.5f; vInvariant0 = Vector3.Lerp(gripL0.translation, gripR0.translation, t); vInvariant1 = Vector3.Lerp(gripL1.translation, gripR1.translation, t); } // Strategy: // 1. Move invariant point to the correct spot, with a translation. // 2. Rotate about that point. // 3. Uniform scale about that point. // Items 2 and 3 can happen in the same TrTransform, since rotation // and uniform scale commute as long as they use the same pivot. TrTransform xfDelta1 = TrTransform.T(vInvariant1 - vInvariant0); TrTransform xfDelta23; { // calculate worldspace scale; will adjust center-of-scale later float dist0 = vLR0.magnitude; float dist1 = vLR1.magnitude; float deltaScale = (dist0 == 0) ? 1 : dist1 / dist0; // Clamp scale if requested. if (deltaScaleMin >= 0) { deltaScale = Mathf.Max(deltaScale, deltaScaleMin); } if (deltaScaleMax >= 0) { deltaScale = Mathf.Min(deltaScale, deltaScaleMax); } // This gets the left-right axis pointing in the correct direction Quaternion qSwing0To1 = Quaternion.FromToRotation(vLR0, vLR1); // This applies some twist about that left-right axis. The choice of constraint axis // (vLR0 vs vLR1) depends on whether qTwist is right- or left-multiplied vs qReach. Quaternion qTwistAbout0 = Quaternion.Slerp( ConstrainRotationDelta(gripL0.rotation, gripL1.rotation, vLR0), ConstrainRotationDelta(gripR0.rotation, gripR1.rotation, vLR0), 0.5f); Quaternion qDelta = qSwing0To1 * qTwistAbout0; // Constrain the rotation if requested. if (rotationAxisConstraint != default(Vector3)) { qDelta = ConstrainRotationDelta(Quaternion.identity, qDelta, rotationAxisConstraint); } xfDelta23 = TrTransform .TRS(Vector3.zero, qDelta, deltaScale) .TransformBy(TrTransform.T(vInvariant1)); } return(xfDelta23 * xfDelta1 * obj0); }
void ApplyLazyInput(ref Vector3 pos, ref Quaternion rot) { if (!m_PaintingActive || !m_LazyInputActive || m_GridSnapActive) { // if (m_GridSnapActive) // ApplyGridSnap(ref pos, ref rot); m_btCursorPos = pos; m_btCursorRot = rot; m_lazyInputRate = 0; EndLazyInputVisuals(); return; } UpdateLazyInputRate(); //Vector3 beeline = pos - m_btCursorPos; // //// Vector3 beelineDelta = Vector3.Lerp(Vector3.zero, beeline, m_lazyInputRate); // //Vector3 oldCursorNormal = m_btCursorRot * Vector3.forward; //Vector3 newCursorNormal = rot * Vector3.forward; // //Vector3 forwardDelta = Vector3.ProjectOnPlane(beeline, oldCursorNormal); //Vector3 midPointDelta = Vector3.Project(Vector3.ProjectOnPlane(beeline, newCursorNormal), forwardDelta.normalized); // //float midPointLerp = Mathf.InverseLerp(0, beeline.magnitude, Vector3.Project(midPointDelta, beeline.normalized).magnitude); // //m_btCursorRot = Quaternion.Slerp(m_btCursorRot, rot, Mathf.Lerp(midPointLerp, 1, Mathf.Abs(Vector3.Dot(beeline.normalized, newCursorNormal))) * m_lazyInputRate); // //Vector3 posDelta = Vector3.Lerp(Vector3.zero, Vector3.Lerp(midPointDelta, beeline, midPointLerp), m_lazyInputRate); //m_btCursorPos = m_btCursorPos + posDelta; // //// if (beelineDelta.magnitude > 0) { //// m_btCursorPos = m_btCursorPos + beelineDelta; //// //// m_btCursorRot = Quaternion.Slerp(m_btCursorRot, rot, m_lazyInputRate); //// } TrTransform result = LazyLerp(TrTransform.TRS(m_btCursorPos, m_btCursorRot, PointerManager.m_Instance.MainPointer.BrushSizeAbsolute), TrTransform.TRS(pos, rot, PointerManager.m_Instance.MainPointer.BrushSizeAbsolute), m_lazyInputRate, m_LazyInputTangentMode); m_btCursorPos = result.translation; m_btCursorRot = result.rotation; pos = m_btCursorPos; rot = m_btCursorRot; UpdateLazyInputVisuals(); }