using System; using System.Collections.Generic; using System.Text; namespace SalsaModel { public class SalsaMover { public PositionModel Model; public BodyGeometry Body; public SalsaMover(BodyGeometry body, Location initialPosition) { Body = body; Model = new PositionModel(); Pose(initialPosition); } private SalsaMover() { } public SalsaMover Clone() { SalsaMover ret = new SalsaMover(); ret.Body = Body; ret.Model = Model.Clone(); return ret; } /// /// Move the given foot to the given position relative to the other foot, /// correcting for hip width. /// /// So, MoveFootTo(side, Location.Origin) would leave feet in natural standing posture. /// public void MoveFootTo(PositionModel.Side side, Location loc) { MoveFootRel(side, loc, 0); } /// /// Move the given foot to the given position relative to the other foot, /// correcting for hip width. /// /// So, MoveFootTo(side, Location.Origin, 0) would leave feet in natural standing posture. /// public void MoveFootRel(PositionModel.Side side, Location offset, double angle) { //CheckPosture(); PositionModel.Leg leg = Model.GetLeg(side); Location toeOffset = leg.Toe.Minus(leg.Foot); Location newFootPos = GetStepLocation(side, offset); leg.Foot = newFootPos; leg.Toe = newFootPos.Plus(toeOffset.RotateZ(angle)).SetZ(0); //Location forward = ForwardKneePlane(); //AlignKnee(leg, forward); //CheckPosture(); } /// /// Returns the position to which the given foot should be moved, to be at the /// given offset relative to it's natural position with respect to the other /// foot. So, moving the foot to GetStepLocation(side, Location.Origin) would /// leave the model standing feet parallel. /// public Location GetStepLocation(PositionModel.Side side, Location offset) { PositionModel.Leg leg = Model.GetLeg(side); PositionModel.Leg otherLeg = Model.GetOtherLeg(side); return otherLeg.Foot.Plus(NaturalFootOffset(side)).Plus(offset).SetZ(0); } public void MoveFootToNoOffset(PositionModel.Side side, Location loc, double angle) { PositionModel.Leg leg = Model.GetLeg(side); PositionModel.Leg otherLeg = Model.GetOtherLeg(side); Location hipOffset = leg.Hip.Minus(otherLeg.Hip); Location toeOffset = leg.Toe.Minus(leg.Foot); leg.Foot = otherLeg.Foot.Plus(loc).SetZ(0); leg.Toe = leg.Foot.Plus(toeOffset.RotateZ(angle)).SetZ(0); Location forward = ForwardKneePlane(); AlignKnee(leg, forward); CheckPosture(); } internal void MoveFootToSetShoulder(PositionModel.Side footSide, PositionModel.Side leadSide, Location leadShoulder) { Location targetOnFloor = leadShoulder.SetZ(0) .Minus(Model.SidewaysUnitDirection(leadSide).Scale((Body.ShoulderWidth - Body.FootSeparation) / 2.0)); if (footSide != leadSide) { targetOnFloor = targetOnFloor.Plus(Model.SidewaysUnitDirection(footSide).Scale(Body.FootSeparation)); } PositionModel.Leg leg = Model.GetLeg(footSide); Location currentOnFloor = leg.Foot.HalfWayTo(leg.Toe).SetZ(0); Location footOrientation = leg.Toe.Minus(leg.Foot).SetZ(0).Normalised(); Location offset = targetOnFloor.Minus(currentOnFloor); leg.Foot = leg.Foot.Plus(offset); leg.Toe = leg.Toe.Plus(offset); Location forward = ForwardKneePlane(); AlignKnee(leg, forward); CheckPosture(); } public Location NaturalFootOffset(PositionModel.Side side) { return Model.SidewaysUnitDirection(side).Scale(Body.FootSeparation); } private Location ForwardKneePlane() { return Model.RLeg.Hip.Minus(Model.LLeg.Hip); } private void AlignKnee(PositionModel.Leg leg, Location kneePlaneNormal) { double legExtent = leg.Foot.DistanceTo(leg.Hip); // how far forward does the knee go? Simplify by assuming that Shin == Thigh. double kneeOff = JointOffsetForLimbExtent(Body.Shin + Body.Thigh, legExtent); double ls = checked(Math.Sqrt(Body.Shin * Body.Shin - kneeOff * kneeOff)); double lt = checked(Math.Sqrt(Body.Thigh * Body.Thigh - kneeOff * kneeOff)); // the algorithm is not correct for large angles, this checks it. //Check.Equal(legExtent, ls + lt); Location footHip = leg.Foot.Minus(leg.Hip); Location kneeCenterLoc = leg.Hip.Plus(footHip.Scale(lt / legExtent)); //double xTotal = leg.Foot.X - leg.Hip.X; //double x1 = (lt / legExtent) * xTotal; //double x2 = (ls / legExtent) * xTotal; //Check.Equal(xTotal, x1 + x2); //double z2 = (ls / legExtent) * (leg.Hip.Z - leg.Foot.Z); Location kneeDirection = footHip.CrossProduct(kneePlaneNormal); leg.Knee = kneeCenterLoc.Plus(kneeDirection.Normalised().Scale(kneeOff)); //leg.Knee = leg.Foot.OffsetBy(-x2, kneeOff, z2); } /// /// Returns the offset for the knee/elbow, given the total length of the /// limb, and the distance from one end to the other. /// /// NB this does make the simplicifacting assumption that both parts of the /// limb are the same length. /// /// /// /// public static double JointOffsetForLimbExtent(double length, double extent) { double partLength = length / 2; double offSqr = (partLength * partLength) - 0.25 * (extent * extent); return Math.Sqrt(offSqr); } public enum TurnDirection { Any, Left, Right } public TorsoMover HipOverFootMover(PositionModel.Side side, TurnDirection turnDirection) { PositionModel.Leg leg = Model.GetLeg(side); PositionModel.Leg otherLeg = Model.GetLeg(PositionModel.Other(side)); Location target = leg.Foot.HalfWayTo(leg.Toe).SetZ(0); Location finalHipPosOnFloor = target.Plus(Model.SidewaysUnitDirection(side).Scale((Body.HipWidth-Body.FootSeparation)/2.0)); //Location newHipPosOnFloor = finalHipPosOnFloor.Minus(startHipPosOnFloor).Scale(percent) // .Plus(startHipPosOnFloor); Location finalHipPos = finalHipPosOnFloor.Plus(0, 0, leg.Hip.Z - leg.Toe.Z); //Check.Equal(Model.HipHeight, leg.Hip.Z - leg.Toe.Z, 0.0001); return new TorsoMover(this, side, finalHipPos, turnDirection); } public void AlignKnees() { Location forward = ForwardKneePlane(); AlignKnee(Model.LLeg, forward); AlignKnee(Model.RLeg, forward); } public void OffsetTorso(Location shift) { Model.LLeg.Hip = Model.LLeg.Hip.Plus(shift); Model.LArm.Shoulder = Model.LArm.Shoulder.Plus(shift); Model.LArm.Elbow = Model.LArm.Elbow.Plus(shift); Model.LArm.Wrist = Model.LArm.Wrist.Plus(shift); Model.RLeg.Hip = Model.RLeg.Hip.Plus(shift); Model.RArm.Shoulder = Model.RArm.Shoulder.Plus(shift); Model.RArm.Elbow = Model.RArm.Elbow.Plus(shift); Model.RArm.Wrist = Model.RArm.Wrist.Plus(shift); Model.Head = Model.Head.Plus(shift); } internal void RotateTorsoAboutZ(Location axis, Angle angle) { RotateTorsoAboutZ(axis, angle.Value); } internal void RotateTorsoAboutZ(Location axis, double angle) { Model.LLeg.Hip = Model.LLeg.Hip.RotateAboutZ(axis, angle); Model.LArm.Shoulder = Model.LArm.Shoulder.RotateAboutZ(axis, angle); Model.LArm.Elbow = Model.LArm.Elbow.RotateAboutZ(axis, angle); Model.LArm.Wrist = Model.LArm.Wrist.RotateAboutZ(axis, angle); Model.RLeg.Hip = Model.RLeg.Hip.RotateAboutZ(axis, angle); Model.RArm.Shoulder = Model.RArm.Shoulder.RotateAboutZ(axis, angle); Model.RArm.Elbow = Model.RArm.Elbow.RotateAboutZ(axis, angle); Model.RArm.Wrist = Model.RArm.Wrist.RotateAboutZ(axis, angle); Model.Head = Model.Head.RotateAboutZ(axis, angle); } public void CheckPosture() { Check.Posture(Model, Body); } private void Pose(Location initialPosition) { double hipToShoulderX = (Body.ShoulderWidth - Body.HipWidth) / 2; // an arbitary guess as to how far we'd want to step, used to calculate // how far to drop the bum const double maxStep = 30; double arseHeight = Math.Sqrt(Math.Pow(Body.Shin + Body.Thigh, 2) - maxStep * maxStep); Model.HipHeight = arseHeight; Model.RLeg.Foot = initialPosition.Plus(-(Body.FootSeparation) / 2, 0, 0); Model.RLeg.Hip = initialPosition.Plus(-(Body.HipWidth) / 2, 0, arseHeight); Model.RArm.Shoulder = Model.RLeg.Hip.Plus(-hipToShoulderX, 0, Body.Trunk); Model.RLeg.Toe = Model.RLeg.Foot.Plus(0, Body.Foot, 0); Model.LLeg.Foot = initialPosition.Plus(Body.FootSeparation / 2, 0, 0); Model.LLeg.Hip = initialPosition.Plus(Body.HipWidth / 2, 0, arseHeight); Model.LArm.Shoulder = Model.LLeg.Hip.Plus(+hipToShoulderX, 0, Body.Trunk); Model.LLeg.Toe = Model.LLeg.Foot.Plus(0, Body.Foot, 0); //Model.RArm.Elbow = Model.RArm.Shoulder.OffsetBy(-Body.UpperArm, 0, 0); //Model.RArm.Wrist = Model.RArm.Elbow.OffsetBy(-Body.LowerArm, 0, 0); Model.RArm.Wrist = Model.RLeg.Hip.HalfWayTo(Model.LLeg.Hip).Plus(-Body.HandSeparation / 2, 10, 10); //Model.LArm.Elbow = Model.LArm.Shoulder.OffsetBy(Body.UpperArm, 0, 0); //Model.LArm.Wrist = Model.LArm.Elbow.OffsetBy(Body.LowerArm, 0, 0); Model.LArm.Wrist = Model.RLeg.Hip.HalfWayTo(Model.LLeg.Hip).Plus(Body.HandSeparation / 2, 10, 10); Model.Head = Model.Neck.Plus(0, 0, Body.Head); AlignKnees(); AlignElbows(ElbowStyle.Out); Check.Posture(Model, Body); // left or right, should be the same HipOverFootMover(PositionModel.Side.Left, TurnDirection.Any).Update(1); Check.Posture(Model, Body); } public enum ElbowStyle { Out, Down } public void AlignElbow(PositionModel.Side side, ElbowStyle style) { PositionModel.Arm arm = Model.GetArm(side); switch (style) { case ElbowStyle.Out: AlignElbow(arm, Model.SidewaysUnitDirection(PositionModel.Other(side)) .CrossProduct(arm.Shoulder.Minus(arm.Wrist))); break; case ElbowStyle.Down: AlignElbow(arm, Model.SidewaysUnitDirection(PositionModel.Side.Left)); break; } } public void AlignElbows(ElbowStyle style) { AlignElbow(PositionModel.Side.Left, style); AlignElbow(PositionModel.Side.Right, style); } private void AlignElbow(PositionModel.Arm arm, Location planeNormal) { double offset = JointOffsetForLimbExtent(Body.UpperArm + Body.LowerArm, arm.Shoulder.DistanceTo(arm.Wrist)); arm.Elbow = arm.Shoulder.HalfWayTo(arm.Wrist).Plus(planeNormal.CrossProduct(arm.Shoulder.Minus(arm.Wrist)).Normalised().Scale(offset)); } public void Flip() { RotateTorsoAboutZ(Model.LLeg.Hip.HalfWayTo(Model.RLeg.Hip), Math.PI); // rotating the torso will have swapped the hips; swap them back, then we'll // swap the entire legs Location tmp; tmp = Model.LLeg.Hip; Model.LLeg.Hip = Model.RLeg.Hip; Model.RLeg.Hip = tmp; PositionModel.Leg tmpleg = Model.LLeg; Model.LLeg = Model.RLeg; Model.RLeg = tmpleg; Model.RLeg.Toe = Model.RLeg.Toe.Plus(Model.RLeg.Foot.Minus(Model.RLeg.Toe).SetZ(0).Scale(2)); Model.LLeg.Toe = Model.LLeg.Toe.Plus(Model.LLeg.Foot.Minus(Model.LLeg.Toe).SetZ(0).Scale(2)); Model.WallAngle += Math.PI; AlignKnees(); CheckPosture(); } /// /// The angle of the hip from the horizontal. /// /// public double HipAngle() { double angle = Math.Acos(Model.RLeg.Hip.Minus(Model.LLeg.Hip).Normalised().DotProduct(Model.SidewaysUnitDirection(PositionModel.Side.Right).SetZ(0).Normalised())); if (Model.RLeg.Hip.Z < Model.LLeg.Hip.Z) { return angle; } else { return -angle; } } public void SetHipAngle(double angle) { Check.Equal(Model.HipHeight, Model.LLeg.Hip.HalfWayTo(Model.RLeg.Hip).Z); Location mid = Model.LLeg.Hip.HalfWayTo(Model.RLeg.Hip); Location XYDir = Model.SidewaysUnitDirection(PositionModel.Side.Left); Location offset = XYDir.Scale(Math.Cos(angle)).Plus(0, 0, Math.Sin(angle)).Scale(Body.HipWidth / 2.0); Model.LLeg.Hip = mid.Plus(offset); Model.RLeg.Hip = mid.Minus(offset); Check.Equal(0, mid.DistanceTo(Model.LLeg.Hip.HalfWayTo(Model.RLeg.Hip))); Check.Equal(Body.HipWidth, Model.LLeg.Hip.DistanceTo(Model.RLeg.Hip)); Check.Equal(Model.HipHeight, Model.LLeg.Hip.HalfWayTo(Model.RLeg.Hip).Z); } public void RotateFootBy(PositionModel.Side side, double a) { PositionModel.Leg leg = Model.GetLeg(side); leg.Toe = leg.Toe.RotateAboutZ(leg.Foot, a); } public void RotateFootAroundToeBy(PositionModel.Side side, double a) { PositionModel.Leg leg = Model.GetLeg(side); leg.Foot = leg.Foot.RotateAboutZ(leg.Toe, a); } } }