// Touching a dancer shows and hides its path public void doTouch(double x, double y) { // Convert x and y to dance floor coords var range = Math.Min(ActualWidth, ActualHeight); var s = range / 13.0; var px = -(y - ActualHeight / 2) / s; var py = -(x - ActualWidth / 2) / s; var pv = Vector.Create(px, py); // Compare with dancer locations var bestdist = 0.5; Dancer bestd = null; foreach (var d in dancers) { if (!d.hidden) { var dp = d.location; var distsq = (pv - dp).LengthSquared(); if (distsq < bestdist) { bestd = d; bestdist = distsq; } } } if (bestd != null) { bestd.showPath = !bestd.showPath; canvas.Invalidate(); } }
public Dancer(Dancer from) : this(from.number, from.number_couple, from.gender, from.fillColor, from.tx, GeometryMaker.makeOne(from.geom.geometry(), 0), new List <Movement>()) { clonedFrom = from; // For the sequencer, copy dancer data data.active = from.data.active; }
public Handhold(Dancer dancer1, Dancer dancer2, int hold1, int hold2, double angle1, double angle2, double distance, double score) { this.dancer1 = dancer1; this.dancer2 = dancer2; this.hold1 = hold1; this.hold2 = hold2; this.angle1 = angle1; this.angle2 = angle2; this.distance = distance; this.score = score; }
// Return dancer that is facing the front of this dancer public Dancer dancerFacing(Dancer d) { var d2 = dancerInFront(d); if (d2 != null) { var d3 = dancerInFront(d2); if (d3 == d) { return(d2); } } return(null); }
// Return dancer directly to the left of given dancer public Dancer dancerToLeft(Dancer d) { return(dancerClosest(d, isLeft(d))); }
// Return dancer directly to the right of given dancer public Dancer dancerToRight(Dancer d) { return(dancerClosest(d, isRight(d))); }
// Return true if this dancer is in tandem with another dancer public bool isInTandem(Dancer d) { return(d.data.trailer ? dancerInFront(d).data.leader : d.data.leader ? dancerInBack(d).data.trailer : false); }
// Return true if this dancer is in a wave or mini-wave public bool isInWave(Dancer d) { return(d.data.partner != null && angle(d, d.data.partner) == angle(d.data.partner, d)); }
// Return all the dancers in front, in order public IEnumerable <Dancer> dancersInFront(Dancer d) { return(dancersInOrder(d, isInFront(d))); }
// Return dancers that are in between two other dancers public IEnumerable <Dancer> inBetween(Dancer d1, Dancer d2) { return(dancers.Where(d => d != d1 && d != d2 && (distance(d, d1) + distance(d2)).isApprox(distance(d1, d2)) )); }
// Angle of dancer to the origin static public double angle(Dancer d) { return(new Vector2().preConcatenate(d.tx.Inverse()).Angle()); }
// Distance between two dancers static public double distance(Dancer d1, Dancer d2) { return((d1.location - d2.location).Length()); }
int dancerRelation(Dancer d1, Dancer d2) { // TODO fuzzy cases return(angleBin(angle(d1, d2))); }
// Angle of d2 as viewed from d1 // If angle is 0 then d2 is in front of d1 // Angle returned is in the range -pi to pi static public double angle(Dancer d1, Dancer d2) { return(d2.location.concatenate(d1.tx.Inverse()).Angle()); }
public void resetAnimation() { if (tam != null) { leadin = interactiveDancer < 0 ? 2 : 3; leadout = interactiveDancer < 0 ? 2 : 1; // if (isRunnning) // doneCallback(); isRunning = false; beats = 0.0; var tlist = tam.SelectNodes("formation"); var formation = tlist.Length > 0 ? tlist.First() // formation defined in animation : tam.hasAttr("formation") ? TamUtils.getFormation(tam.attr("formation")) // formation reference to formations.xml : tam; // formation passed in for sequencer var flist = formation.SelectNodes("dancer"); dancers = new Dancer[flist.Length * (int)geometry]; // Except for the phantoms, these are the standard colors // used for teaching callers var dancerColor = geometry == GeometryType.HEXAGON ? new Color[] { Colors.Red, Colors.ForestGreen, Colors.Magenta, Colors.Blue, Colors.Yellow, Colors.Cyan, Colors.LightGray, Colors.LightGray, Colors.LightGray, Colors.LightGray } : new Color[] { Colors.Red, ColorUtilities.ColorFromHex(0xff00c000), Colors.Blue, Colors.Yellow, Colors.LightGray, Colors.LightGray, Colors.LightGray, Colors.LightGray }; // Get numbers for dancers and couples // This fetches any custom numbers that might be defined in // the animation to match a Callerlab or Ceder Chest illustration var paths = tam.SelectNodes("path"); var numbers = geometry == GeometryType.HEXAGON ? new string[] { "A", "E", "I", "B", "F", "J", "C", "G", "K", "D", "H", "L", "u", "v", "w", "x", "y", "z" } : geometry == GeometryType.BIGON || paths.Length == 0 ? new string[] { "1", "2", "3", "4", "5", "6", "7", "8" } : TamUtils.getNumbers(tam); var couples = geometry == GeometryType.HEXAGON ? new string[] { "1", "3", "5", "1", "3", "5", "2", "4", "6", "2", "4", "6", "7", "8", "7", "8", "7", "8" } : geometry == GeometryType.BIGON ? new string[] { "1", "2", "3", "4", "5", "6", "7", "8" } : paths.Length == 0 ? new string[] { "1", "3", "1", "3", "2", "4", "2", "4" } : TamUtils.getCouples(tam); var geoms = GeometryMaker.makeAll(geometry); // Select a random dancer of the correct gender for the interactive dancer var icount = -1; var im = Matrix3x2.Identity; if (interactiveDancer > 0) { var rand = new Random(); var selector = interactiveDancer == (int)Gender.BOY ? "dancer[@gender='boy']" : "dancer[@gender='girl']"; var glist = formation.SelectNodes(selector); icount = rand.Next(glist.Count); // If the animations starts with "Heads" or "Sides" // then select the first dancer. // Otherwise the formation could rotate 90 degrees // which would be confusing var title = tam.attr("title"); if (title.Contains("Heads") || title.Contains("Sides")) { icount = 0; } // Find the angle the interactive dancer faces at start // We want to rotate the formation so that direction is up var iangle = glist.Item((uint)icount).attr("angle").toDouble(); im = Matrix.CreateRotation(-iangle.toRadians()) * im; icount = icount * geoms.Count() + 1; } // Create the dancers and set their starting positions int dnum = 0; for (var i = 0; i < flist.Length; i++) { var fd = flist.ElementAt(i); var x = fd.attr("x").toDouble(); var y = fd.attr("y").toDouble(); var angle = fd.attr("angle").toDouble(); var gender = fd.attr("gender"); var g = gender == "boy" ? Gender.BOY : gender == "girl" ? Gender.GIRL : Gender.PHANTOM; var movelist = paths.Length > i?TamUtils.translatePath(paths.ElementAt(i)) : new List <Movement>(); // Each dancer listed in the formation corresponds to // one, two, or three real dancers depending on the geometry foreach (Geometry geom in geoms) { var m = Matrix3x2.Identity * Matrix.CreateRotation(angle.toRadians()) * Matrix.CreateTranslation(x, y) * im; var nstr = g == Gender.PHANTOM ? " " : numbers[dnum]; var cstr = g == Gender.PHANTOM ? " " : couples[dnum]; var c = g == Gender.PHANTOM ? Colors.LightGray : dancerColor[int.Parse(cstr) - 1]; // add one dancer //icount -= 1; if ((int)g == interactiveDancer && --icount == 0) { idancer = new InteractiveDancer(nstr, cstr, g, c, m, geom.clone(), movelist); dancers[dnum] = idancer; } else { dancers[dnum] = new Dancer(nstr, cstr, g, c, m, geom.clone(), movelist); dancers[dnum].hidden = g == Gender.PHANTOM && !showPhantoms; } beats = Math.Max(dancers[dnum].beats + leadout, beats); dnum++; } } // All dancers added // Initialize other stuff parts = tam.attr("parts") + tam.attr("fractions"); hasParts = tam.attr("parts").Length > 0; isRunning = false; beat = -leadin; prevbeat = -leadin; partbeats = partsValues(); // force a redraw canvas.Invalidate(); // ready callback Callouts.animationReady(); } }
public static Handhold Create(Dancer d1, Dancer d2, int geometry) { if (!d1.hidden && !d2.hidden) { // Turn off grips if not specified in current movement if ((d1.hands & Hands.GRIPRIGHT) != Hands.GRIPRIGHT) { d1.rightgrip = null; } if ((d1.hands & Hands.GRIPLEFT) != Hands.GRIPLEFT) { d1.leftgrip = null; } if ((d2.hands & Hands.GRIPRIGHT) != Hands.GRIPRIGHT) { d2.rightgrip = null; } if ((d2.hands & Hands.GRIPLEFT) != Hands.GRIPLEFT) { d2.leftgrip = null; } // Check distance var x1 = d1.tx.M31; var y1 = d1.tx.M32; var x2 = d2.tx.M31; var y2 = d2.tx.M32; var dx = x2 - x1; var dy = y2 - y1; var dfactor1 = 0.1; // for distance up to 2.0 var dfactor2 = 2.0; // for distance past 2.0 var cutover = geometry == GeometryType.HEXAGON ? 2.5 : geometry == GeometryType.BIGON ? 3.7 : 2.0; var d = Math.Sqrt(dx * dx + dy * dy); var dfactor0 = geometry == GeometryType.HEXAGON ? 1.15 : 1.0; var d0 = d * dfactor0; var score1 = d0 > cutover ? (d0 - cutover) * dfactor2 + 2 * dfactor1 : d0 * dfactor1; var score2 = score1; // Angle between dancers var a0 = Math.Atan2(dy, dx); // Angle each dancer is facing var a1 = Math.Atan2(d1.tx.M12, d1.tx.M22); var a2 = Math.Atan2(d2.tx.M12, d2.tx.M22); // For each dancer, try left and right hands int h1 = 0; int h2 = 0; double ah1 = 0; double ah2 = 0; double afactor1 = 0.2; double afactor2 = geometry == GeometryType.BIGON ? 0.6 : 1.0; // Dancer 1 var a = Math.Abs(Math.IEEERemainder(Math.Abs(a1 - a0 + Math.PI * 3.0 / 2.0), Math.PI * 2.0)); var ascore = a > Math.PI / 6.0 ? (a - Math.PI / 6.0) * afactor2 + Math.PI / 6.0 * afactor1 : a * afactor1; if (score1 + ascore < 1 && (d1.hands & Hands.RIGHTHAND) != 0 && d1.rightgrip == null || d1.rightgrip == d2) { score1 = d1.rightgrip == d2 ? 0 : score1 + ascore; h1 = Hands.RIGHTHAND; ah1 = a1 - a0 + Math.PI * 3.0 / 2.0; } else { a = Math.Abs(Math.IEEERemainder(Math.Abs(a1 - a0 + Math.PI / 2.0), Math.PI * 2.0)); ascore = (a > Math.PI / 6.0) ? (a - Math.PI / 6.0) * afactor2 + Math.PI / 6.0 * afactor1 : a * afactor1; if (score1 + ascore < 1.0 && (d1.hands & Hands.LEFTHAND) != 0 && d1.leftgrip == null || d1.leftgrip == d2) { score1 = d1.leftgrip == d2 ? 0.0 : score1 + ascore; h1 = Hands.LEFTHAND; ah1 = a1 - a0 + Math.PI / 2.0; } else { score1 = 10.0; } } // Dancer 2 a = Math.Abs(Math.IEEERemainder(Math.Abs(a2 - a0 + Math.PI / 2.0), Math.PI * 2)); ascore = a > Math.PI / 6 ? (a - Math.PI / 6) * afactor2 + Math.PI / 6 * afactor1 : a * afactor1; if (score2 + ascore < 1 && (d2.hands & Hands.RIGHTHAND) != 0 && d2.rightgrip == null || d2.rightgrip == d1) { score2 = d2.rightgrip == d1 ? 0 : score2 + ascore; h2 = Hands.RIGHTHAND; ah2 = a2 - a0 + Math.PI / 2; } else { a = Math.Abs(Math.IEEERemainder(Math.Abs(a2 - a0 + Math.PI * 3.0 / 2.0), Math.PI * 2)); ascore = (a > Math.PI / 6) ? (a - Math.PI / 6) * afactor2 + Math.PI / 6 * afactor1 : a * afactor1; if (score2 + ascore < 1.0 && (d2.hands & Hands.LEFTHAND) != 0 && d2.leftgrip == null || d2.leftgrip == d1) { score2 = d2.leftgrip == d1 ? 0.0 : score2 + ascore; h2 = Hands.LEFTHAND; ah2 = a2 - a0 + Math.PI * 3.0 / 2.0; } else { score2 = 10.0; } } // Generate return value if (d1.rightgrip == d2 && d2.rightgrip == d1) { return(new Handhold(d1, d2, Hands.RIGHTHAND, Hands.RIGHTHAND, ah1, ah2, d, 0.0)); } else if (d1.rightgrip == d2 && d2.leftgrip == d1) { return(new Handhold(d1, d2, Hands.RIGHTHAND, Hands.LEFTHAND, ah1, ah2, d, 0.0)); } else if (d1.leftgrip == d2 && d2.rightgrip == d1) { return(new Handhold(d1, d2, Hands.LEFTHAND, Hands.RIGHTHAND, ah1, ah2, d, 0.0)); } else if (d1.leftgrip == d2 && d2.leftgrip == d1) { return(new Handhold(d1, d2, Hands.LEFTHAND, Hands.LEFTHAND, ah1, ah2, d, 0.0)); } else if (score1 > 1.0 || score2 > 1.0 || score1 + score2 > 1.2) { return(null); } else { return(new Handhold(d1, d2, h1, h2, ah1, ah2, d, score1 + score2)); } } return(null); // hidden dancer }
static public bool isFacingOut(Dancer d) { var a = Math.Abs(angle(d)); return(!a.isApprox(Math.PI / 2) && a > Math.PI / 2); }
// Distance of one dancer to the origin static public double distance(Dancer d) { return(d.location.Length()); }
// Return all the dancers to the left, in order public IEnumerable <Dancer> dancersToLeft(Dancer d) { return(dancersInOrder(d, isLeft(d))); }
// Return all dancers, ordered by distance, that satisfies a conditional public IEnumerable <Dancer> dancersInOrder(Dancer d, Func <Dancer, bool> f) { return(dancers.Where(f).OrderBy(d2 => distance(d, d2))); }
// Return all the dancers in back, in order public IEnumerable <Dancer> dancersInBack(Dancer d) { return(dancersInOrder(d, isInBack(d))); }
// Return closest dancer that satisfies a given conditional public Dancer dancerClosest(Dancer d, Func <Dancer, bool> f) { return(dancersInOrder(d, f).FirstOrDefault()); }
// Return true if this dancer is part of a couple facing same direction public bool isInCouple(Dancer d) { var d2 = d.data.partner; return(d2 != null && d.tx.Angle().angleEquals(d2.tx.Angle())); }
// Return dancer directly in front of given dancer public Dancer dancerInFront(Dancer d) { return(dancerClosest(d, isInFront(d))); }
public void analyze() { dancers.ForEach(d => { d.animateToEnd(); d.data.beau = false; d.data.belle = false; d.data.leader = false; d.data.trailer = false; d.data.partner = null; d.data.center = false; d.data.end = false; d.data.verycenter = false; }); bool istidal = false; dancers.ForEach(d1 => { Dancer bestleft = null; Dancer bestright = null; int leftcount = 0; int rightcount = 0; int frontcount = 0; int backcount = 0; dancers.Where(d2 => d2 != d1).ForEach(d2 => { // Count dancers to the left and right, // and find the closest on each side if (isRight(d1)(d2)) { rightcount++; if (bestright == null || distance(d1, d2) < distance(d1, bestright)) { bestright = d2; } } else if (isLeft(d1)(d2)) { leftcount++; if (bestleft == null || distance(d1, d2) < distance(d1, bestleft)) { bestleft = d2; } } // Also count dancers in front and in back else if (isInFront(d1)(d2)) { frontcount++; } else if (isInBack(d1)(d2)) { backcount++; } }); // Use the results of the counts to assign belle/beau/leader/trailer // and partner if (leftcount % 2 == 1 && rightcount % 2 == 0 && distance(d1, bestleft) < 3) { d1.data.partner = bestleft; d1.data.belle = true; } else if (rightcount % 2 == 1 && leftcount % 2 == 0 && distance(d1, bestright) < 3) { d1.data.partner = bestright; d1.data.beau = true; } if (frontcount % 2 == 0 && backcount % 2 == 1) { d1.data.leader = true; } else if (frontcount % 2 == 1 && backcount % 2 == 0) { d1.data.trailer = true; } // Assign ends if (rightcount == 0 && leftcount > 1) { d1.data.end = true; } else if (leftcount == 0 && rightcount > 1) { d1.data.end = true; } // The very centers of a tidal wave are ends // Remember this special case for assigning centers later if (rightcount == 3 && leftcount == 4 || rightcount == 4 && leftcount == 3) { d1.data.end = true; istidal = true; } }); // Analyze for centers and very centers // Sort dancers by distance from center var dorder = dancers.OrderBy(d => d.location.Length()).ToArray(); // The 2 dancers closest to the center // are centers (4 dancers) or very centers (8 dancers) if (!((double)dorder[1].location.Length()).isApprox((double)dorder[2].location.Length())) { if (dancers.Count == 4) { dorder[0].data.center = true; dorder[1].data.center = true; } else { dorder[0].data.verycenter = true; dorder[1].data.verycenter = true; } } // If tidal, then the next 4 dancers are centers if (istidal) { Enumerable.Range(2, 4).ForEach(i => dorder[i].data.center = true); } // Otherwise, if there are 4 dancers closer to the center than the other 4, // they are the centers else if (dancers.Count > 4 && !distance(dorder[3]).isApprox(distance(dorder[4]))) { Enumerable.Range(0, 4).ForEach(i => dorder[i].data.center = true); } }
// Return dancer directly in back of given dancer public Dancer dancerInBack(Dancer d) { return(dancerClosest(d, isInBack(d))); }