/// <summary> /// Shoot a missile from the source. /// For players. /// </summary> public void SpawnPlayerMissile(Mobj source, MobjType type) { var hs = this.world.Hitscan; // See which target is to be aimed at. var angle = source.Angle; var slope = hs.AimLineAttack(source, angle, Fixed.FromInt(16 * 64)); if (hs.LineTarget == null) { angle += new Angle(1 << 26); slope = hs.AimLineAttack(source, angle, Fixed.FromInt(16 * 64)); if (hs.LineTarget == null) { angle -= new Angle(2 << 26); slope = hs.AimLineAttack(source, angle, Fixed.FromInt(16 * 64)); } if (hs.LineTarget == null) { angle = source.Angle; slope = Fixed.Zero; } } var x = source.X; var y = source.Y; var z = source.Z + Fixed.FromInt(32); var missile = this.SpawnMobj(x, y, z, type); if (missile.Info.SeeSound != 0) { this.world.StartSound(missile, missile.Info.SeeSound, SfxType.Misc); } missile.Target = source; missile.Angle = angle; missile.MomX = new Fixed(missile.Info.Speed) * Trig.Cos(angle); missile.MomY = new Fixed(missile.Info.Speed) * Trig.Sin(angle); missile.MomZ = new Fixed(missile.Info.Speed) * slope; this.CheckMissileSpawn(missile); }
/// <summary> /// Shoot a missile from the source to the destination. /// For monsters. /// </summary> public Mobj SpawnMissile(Mobj source, Mobj dest, MobjType type) { var missile = this.SpawnMobj(source.X, source.Y, source.Z + Fixed.FromInt(32), type); if (missile.Info.SeeSound != 0) { this.world.StartSound(missile, missile.Info.SeeSound, SfxType.Misc); } // Where it came from? missile.Target = source; var angle = Geometry.PointToAngle(source.X, source.Y, dest.X, dest.Y); // Fuzzy player. if ((dest.Flags & MobjFlags.Shadow) != 0) { var random = this.world.Random; angle += new Angle((random.Next() - random.Next()) << 20); } var speed = this.GetMissileSpeed(missile.Type); missile.Angle = angle; missile.MomX = new Fixed(speed) * Trig.Cos(angle); missile.MomY = new Fixed(speed) * Trig.Sin(angle); var dist = Geometry.AproxDistance(dest.X - source.X, dest.Y - source.Y); var num = (dest.Z - source.Z).Data; var den = (dist / speed).Data; if (den < 1) { den = 1; } missile.MomZ = new Fixed(num / den); this.CheckMissileSpawn(missile); return(missile); }
/// <summary> /// Fire a hitscan bullet. /// If damage == 0, it is just a test trace that will leave linetarget set. /// </summary> public void LineAttack(Mobj shooter, Angle angle, Fixed range, Fixed slope, int damage) { this.currentShooter = shooter; this.currentShooterZ = shooter.Z + (shooter.Height >> 1) + Fixed.FromInt(8); this.currentRange = range; this.currentAimSlope = slope; this.currentDamage = damage; var targetX = shooter.X + range.ToIntFloor() * Trig.Cos(angle); var targetY = shooter.Y + range.ToIntFloor() * Trig.Sin(angle); this.world.PathTraversal.PathTraverse( shooter.X, shooter.Y, targetX, targetY, PathTraverseFlags.AddLines | PathTraverseFlags.AddThings, this.shootTraverseFunc ); }
public void Cos() { for (var deg = -720; deg <= 720; deg++) { var angle = Angle.FromDegree(deg); var fineAngle = (int)(angle.Data >> Trig.AngleToFineShift); var radian = 2 * Math.PI * deg / 360; var expected = Math.Cos(radian); { var actual = Trig.Cos(angle).ToDouble(); Assert.AreEqual(expected, actual, 1.0E-3); } { var actual = Trig.Cos(fineAngle).ToDouble(); Assert.AreEqual(expected, actual, 1.0E-3); } } }
/// <summary> /// Adjusts the x and y movement so that the next move will /// slide along the wall. /// </summary> private void HitSlideLine(LineDef line) { if (line.SlopeType == SlopeType.Horizontal) { this.slideMoveY = Fixed.Zero; return; } if (line.SlopeType == SlopeType.Vertical) { this.slideMoveX = Fixed.Zero; return; } var side = Geometry.PointOnLineSide(this.slideThing.X, this.slideThing.Y, line); var lineAngle = Geometry.PointToAngle(Fixed.Zero, Fixed.Zero, line.Dx, line.Dy); if (side == 1) { lineAngle += Angle.Ang180; } var moveAngle = Geometry.PointToAngle(Fixed.Zero, Fixed.Zero, this.slideMoveX, this.slideMoveY); var deltaAngle = moveAngle - lineAngle; if (deltaAngle > Angle.Ang180) { deltaAngle += Angle.Ang180; } var moveDist = Geometry.AproxDistance(this.slideMoveX, this.slideMoveY); var newDist = moveDist * Trig.Cos(deltaAngle); this.slideMoveX = newDist * Trig.Cos(lineAngle); this.slideMoveY = newDist * Trig.Sin(lineAngle); }
Vector3F rotatePointAroundPivot(Vector3F point, Vector3F angles) { Fix32 x = point.x; Fix32 y = point.y; Fix32 z = point.z; Fix32 a = angles.z * Fix32.Deg2Rad; Fix32 b = angles.y * Fix32.Deg2Rad; Fix32 c = angles.x * Fix32.Deg2Rad; Fix32 x1 = x * Trig.Cos(a) - y * Trig.Sin(a); Fix32 y1 = x * Trig.Sin(a) + y * Trig.Cos(a); Fix32 x2 = x * Trig.Cos(b) - z * Trig.Sin(b); Fix32 z2 = x * Trig.Sin(b) + z * Trig.Cos(b); Fix32 y3 = y * Trig.Cos(c) - z * Trig.Sin(c); Fix32 z3 = y * Trig.Sin(c) + z * Trig.Cos(c); var ret = new Vector3F(x2, y3, z3); return(ret); }
private void Func() { DataContext = null; int N = Convert.ToInt32(Ntext.Text); double ax = -1, bx = 1; double h = (bx - ax) / (N); double[] x = new double[N + 1]; List <double> y = new List <double>(); double[,] ACB = new double[N, N]; double[] F = new double[N]; double a = Convert.ToDouble(Atext.Text); double YAnalytic(double x) => (Trig.Cos(Math.PI * x) + x + ((x * SpecialFunctions.Erf(x / Math.Sqrt(2 * a)) + Math.Sqrt((2 * a) / Math.PI) * Math.Exp(-(x * x) / (2 * a))) / (SpecialFunctions.Erf(1 / Math.Sqrt(2 * a)) + Math.Sqrt(2 * a / Math.PI) * Math.Exp(-1 / (2 * a))))); for (int i = 0; i <= N; i++) { x[i] = ax + i * h; } for (int i = 0; i <= N; i++) { y.Add(YAnalytic(x[i])); } var ACBmatrix = MathNet.Numerics.LinearAlgebra.Matrix <double> .Build.DenseOfArray(new double[, ] { { a *Math.PI *Math.PI + 1, Math.PI }, { a *Math.PI *Math.PI + Math.PI / 4 - 1, (-8 * a *Math.PI *Math.PI) / Math.Sqrt(2) - 1 } }); var Fvector = MathNet.Numerics.LinearAlgebra.Vector <double> .Build.DenseOfArray(new double[] { -Math.PI / 2, // -Math.PI/2+((a*Math.PI*Math.PI/8)*Math.Sin(Math.PI/8))+(Math.PI/4)*Math.Cos(Math.PI/8)-1+2*Math.Sin(Math.PI/8), -(1 + a *Math.PI *Math.PI) - Math.PI / 4 // -(1+a*Math.PI*Math.PI)-Math.PI/4+Math.Sqrt(2)*(((a*Math.PI*Math.PI)/8)*Math.Sin(5*Math.PI/16)-(Math.PI/8)*Math.Cos(5*Math.PI/16)-1+2*Math.Sin(5*Math.PI/16)) }); var Cvector = ACBmatrix.Solve(Fvector); double c1 = Cvector[0]; double c2 = Cvector[1]; List <double> yres = new List <double>(); for (int i = 0; i <= N; i++) { yres.Add(x[i] + c1 * (1 - Math.Pow(x[i], 2)) + c2 * (1 + x[i] - Math.Pow(x[i], 2) - Math.Pow(x[i], 3))); } SeriesCollection = new SeriesCollection { new LineSeries { Title = "Analytic Y", Values = new ChartValues <double>(y.ToList <double>()) }, new LineSeries { Title = "Gilerkin", Values = new ChartValues <double>(yres) } }; Labels = x.Select(x => x.ToString("F2")).ToArray(); YFormatter = value => value.ToString("F2"); DataContext = this; List <MyTable> result = new List <MyTable>(); for (int i = 0; i <= N; i++) { result.Add(new MyTable(x[i], y[i], yres[i])); } grid.ItemsSource = result; }
static void Main(string[] args) { // A simple accelerometer is constructed by suspending a // mass m from a string of length L that is tied to the top // of a cart. As the cart is accelerated the string-mass // system makes a constant angle th with the vertical. // (a) Assuming that the string mass is negligible compared // with m, derive an expression for the cart’s acceleration // in terms of and show that it is independent of // the mass mand the length L. // (b) Determine the acceleration of the cart when th = 23.0°. var F1 = new Symbol("F1"); // force of string var F2 = new Symbol("F2"); // force of gravity var th1 = new Symbol("th1"); var th2 = new Symbol("th2");; var _F1 = new Point() { angle = th1 }; var _F2 = new Point() { angle = th2, magnitude = F2 }; var m = new Symbol("m"); var g = new Symbol("g"); var obj = new Obj() { mass = m }; obj.acceleration.y = 0; obj.forces.Add(_F1); obj.forces.Add(_F2); _F1.magnitude = obj.ForceMagnitude(_F1); ("Derive an expression for the cart’s acceleration in terms " + "of and show that it is independent of the mass mand the length L:").Disp(); "".Disp(); obj.AccelerationX() .Substitute(F2, m * g) .Substitute(Trig.Cos(th2), 0) .Substitute(Trig.Sin(th2), -1) .Disp(); "".Disp(); "Determine the acceleration of the cart when th = 23.0°".Disp(); "".Disp(); obj.AccelerationX() .Substitute(F2, m * g) .Substitute(Trig.Cos(th2), 0) .Substitute(Trig.Sin(th2), -1) .Substitute(th1, (90 - 23).ToRadians()) .Substitute(Trig.Pi, Math.PI) .Substitute(g, 9.8) .Disp(); Console.ReadLine(); }
/// <summary> /// Creating the multiplier for each x,y,z movement by the amplitude , aimuth , elevtion and tilt. /// </summary> /// <param name="amplitude">Amplitude (degree).</param> /// <param name="azimuth">Azimuth (degree).</param> /// <param name="elevation">Elevation (degree).</param> /// <param name="tilt">Tilt (degree).</param> /// <returns></returns> public Tuple <double, double, double> CreateMultiplyTuple(double amplitude, double azimuth, double elevation, double tilt) { //converting the angles to radian. amplitude = amplitude * Math.PI / 180; azimuth = azimuth * Math.PI / 180; elevation = elevation * Math.PI / 180; tilt = tilt * Math.PI / 180; //making the multipliers vector. double xM = -Trig.Sin(amplitude) * Trig.Sin(azimuth) * Trig.Cos(tilt) + Trig.Cos(amplitude) * (Trig.Cos(azimuth) * Trig.Cos(elevation) + Trig.Sin(azimuth) * Trig.Sin(tilt) * Trig.Sin(elevation)); double yM = Trig.Sin(amplitude) * Trig.Cos(azimuth) * Trig.Cos(tilt) + Trig.Cos(amplitude) * (Trig.Sin(azimuth) * Trig.Cos(elevation) + -Trig.Cos(azimuth) * Trig.Sin(tilt) * Trig.Sin(elevation)); //the axis for y and z are opposite in the Moog. yM = -yM; double zM = -Trig.Sin(amplitude) * Trig.Sin(tilt) - Trig.Cos(amplitude) * Trig.Sin(elevation) * Trig.Cos(tilt); //the axis for y and z are opposite in the Moog. zM = -zM; //return the requested vector. return(new Tuple <double, double, double>(xM, yM, zM)); }
/// <summary> /// Moves the given origin along a given angle. /// </summary> public void Thrust(Player player, Angle angle, Fixed move) { player.Mobj.MomX += move * Trig.Cos(angle); player.Mobj.MomY += move * Trig.Sin(angle); }
/// <summary> /// Returns the cosine of an <paramref name="angle" /> in radians. /// </summary> /// <param name="absoluteValue">Return absolute values? (default: false).</param> public static double Cos(this double angle, bool absoluteValue = false) => absoluteValue ? Trig.Cos(angle).CoerceZero(1E-6).Abs() : Trig.Cos(angle).CoerceZero(1E-6);
private void Func() { DataContext = null; int N = Convert.ToInt32(Ntext.Text); double ax = -1, bx = 1; double h = (bx - ax) / (N); double[] x = new double[N + 1]; List <double> y = new List <double>(); double[,] ACB = new double[N, N]; double[] F = new double[N]; double a = Convert.ToDouble(Atext.Text); double YAnalytic(double x) => (Trig.Cos(Math.PI * x) + x + ((x * SpecialFunctions.Erf(x / Math.Sqrt(2 * a)) + Math.Sqrt((2 * a) / Math.PI) * Math.Exp(-(x * x) / (2 * a))) / (SpecialFunctions.Erf(1 / Math.Sqrt(2 * a)) + Math.Sqrt(2 * a / Math.PI) * Math.Exp(-1 / (2 * a))))); for (int i = 0; i <= N; i++) { x[i] = ax + i * h; } for (int i = 0; i <= N; i++) { y.Add(YAnalytic(x[i])); } ACB[0, 0] = -a + ((x[0] * h) / 2); ACB[0, 1] = 2 * a - ((-1) * h * h); F[0] = -(1 + a * Math.Pow(Math.PI, 2)) * Math.Cos(Math.PI * x[0]) - Math.PI * x[0] * Math.Sin(Math.PI * x[0]) + (((-a + ((x[0] * h) / 2)) * (-1)) / (h * h)); for (int i = 1; i < N - 1; i++) { ACB[i, i - 1] = -a + ((x[i] * h) / 2); ACB[i, i] = 2 * a - ((-1) * h * h); ACB[i, i + 1] = -a - ((x[i] * h) / 2); F[i] = -(1 + a * Math.Pow(Math.PI, 2)) * Math.Cos(Math.PI * x[i]) - Math.PI * x[i] * Math.Sin(Math.PI * x[i]); } ACB[N - 1, N - 2] = 2 * a - ((-1) * h * h); ACB[N - 1, N - 1] = -a - ((x[N - 1] * h) / 2); F[N - 1] = -(1 + a * Math.Pow(Math.PI, 2)) * Math.Cos(Math.PI * x[N - 1]) - Math.PI * x[N - 1] * Math.Sin(Math.PI * x[N - 1]) + (((-a - ((x[N - 1] * h) / 2)) * (1)) / (h * h)); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { Console.Write(ACB[i, j].ToString("f3") + " "); } Console.WriteLine(" = " + F[i]); } var ACBmatrix = MathNet.Numerics.LinearAlgebra.Matrix <double> .Build.DenseOfArray(ACB); var Fvector = MathNet.Numerics.LinearAlgebra.Vector <double> .Build.DenseOfArray(F); Fvector = Fvector * -Math.Pow(h, 2); var Yvector = ACBmatrix.Solve(Fvector); List <double> yres = new List <double>(); for (int i = 0; i < N; i++) { yres.Add(Yvector[i]); } yres.Insert(0, -1); yres[N] = 1; SeriesCollection = new SeriesCollection { new LineSeries { Title = "Analytic Y", Values = new ChartValues <double>(y.ToList <double>()) }, new LineSeries { Title = "Y method", Values = new ChartValues <double>(yres) } }; Labels = x.Select(x => x.ToString("F2")).ToArray(); YFormatter = value => value.ToString("F2"); DataContext = this; List <MyTable> result = new List <MyTable>(); result.Add(new MyTable(x[0], y[0], -1)); for (int i = 1; i < N; i++) { result.Add(new MyTable(x[i], y[i], yres[i])); } result.Add(new MyTable(x[N], y[N], 1)); grid.ItemsSource = result; }
static void Main(string[] args) { // Two people pull as hard as they can on ropes attached // to a boat that has a mass of 200 kg. If they pull in the // same direction, the boat has an acceleration of // 1.52 m/s^2 to the right. If they pull in opposite directions, // the boat has an acceleration of 0.518 m/s^2 // to the left. What is the force exerted by each person on the // boat? (Disregard any other forces on the boat.) // Trig.Cos(new DoubleFloat(Math.PI)).Disp(); var m = new Symbol("m"); var aAx = new Symbol("aAx"); var aBx = new Symbol("aBx"); var objA = new Obj() { mass = m }; objA.acceleration.x = aAx; var _F1A = new Point() { angle = 0 }; var _F2A = new Point() { angle = 0 }; objA.forces.Add(_F1A); objA.forces.Add(_F2A); var objB = new Obj() { mass = m }; objB.acceleration.x = aBx; var _F1B = new Point() { angle = 0 }; var _F2B = new Point() { angle = new DoubleFloat(Math.PI) }; objB.forces.Add(_F1B); objB.forces.Add(_F2B); "force 1 magnitude (symbolic):".Disp(); "".Disp(); Calc.ForceMagnitude(objA, objB, _F1A, _F1B) .Substitute(Trig.Cos(0), 1) .Disp(); "force 1 magnitude (numeric):".Disp(); "".Disp(); Calc.ForceMagnitude(objA, objB, _F1A, _F1B) .Substitute(Trig.Cos(0), 1) .Substitute(m, 200) .Substitute(aAx, 1.52) .Substitute(aBx, -0.518) .Disp(); "".Disp(); "force 2 magnitude (symbolic):".Disp(); "".Disp(); Calc.ForceMagnitude(objA, objB, _F2A, _F2B) .Substitute(Trig.Cos(0), 1) .Disp(); "force 2 magnitude (numeric):".Disp(); "".Disp(); Calc.ForceMagnitude(objA, objB, _F2A, _F2B) .Substitute(Trig.Cos(0), 1) .Substitute(m, 200) .Substitute(aAx, 1.52) .Substitute(aBx, -0.518) .Disp(); "".Disp(); Console.ReadLine(); }
/// <summary> /// Returns false if the player cannot be respawned at the given /// mapthing spot because something is occupying it. /// </summary> public bool CheckSpot(int playernum, MapThing mthing) { var players = this.world.Options.Players; if (players[playernum].Mobj == null) { // First spawn of level, before corpses. for (var i = 0; i < playernum; i++) { if (players[i].Mobj.X == mthing.X && players[i].Mobj.Y == mthing.Y) { return(false); } } return(true); } var x = mthing.X; var y = mthing.Y; if (!this.world.ThingMovement.CheckPosition(players[playernum].Mobj, x, y)) { return(false); } // Flush an old corpse if needed. if (this.bodyQueSlot >= ThingAllocation.bodyQueSize) { this.RemoveMobj(this.bodyQue[this.bodyQueSlot % ThingAllocation.bodyQueSize]); } this.bodyQue[this.bodyQueSlot % ThingAllocation.bodyQueSize] = players[playernum].Mobj; this.bodyQueSlot++; // Spawn a teleport fog. var subsector = Geometry.PointInSubsector(x, y, this.world.Map); var angle = (Angle.Ang45.Data >> Trig.AngleToFineShift) * ((int)Math.Round(mthing.Angle.ToDegree()) / 45); // // The code below to reproduce respawn fog bug in deathmath // is based on Chocolate Doom's implementation. // Fixed xa; Fixed ya; switch (angle) { case 4096: // -4096: xa = Trig.Tan(2048); // finecosine[-4096] ya = Trig.Tan(0); // finesine[-4096] break; case 5120: // -3072: xa = Trig.Tan(3072); // finecosine[-3072] ya = Trig.Tan(1024); // finesine[-3072] break; case 6144: // -2048: xa = Trig.Sin(0); // finecosine[-2048] ya = Trig.Tan(2048); // finesine[-2048] break; case 7168: // -1024: xa = Trig.Sin(1024); // finecosine[-1024] ya = Trig.Tan(3072); // finesine[-1024] break; case 0: case 1024: case 2048: case 3072: xa = Trig.Cos((int)angle); ya = Trig.Sin((int)angle); break; default: throw new Exception("Unexpected angle: " + angle); } var mo = this.SpawnMobj(x + 20 * xa, y + 20 * ya, subsector.Sector.FloorHeight, MobjType.Tfog); if (!this.world.FirstTicIsNotYetDone) { // Don't start sound on first frame. this.world.StartSound(mo, Sfx.TELEPT, SfxType.Misc); } return(true); }
public void CanComputeCosine(double value, double expected) { var actual = Trig.Cos(value); AssertHelpers.AlmostEqual(expected, actual, 14); }
/// <summary> /// Damages both enemies and players. /// "inflictor" is the thing that caused the damage creature /// or missile, can be null (slime, etc). /// "source" is the thing to target after taking damage creature /// or null. /// Source and inflictor are the same for melee attacks. /// Source can be null for slime, barrel explosions and other /// environmental stuff. /// </summary> public void DamageMobj(Mobj target, Mobj inflictor, Mobj source, int damage) { if ((target.Flags & MobjFlags.Shootable) == 0) { // Shouldn't happen... return; } if (target.Health <= 0) { return; } if ((target.Flags & MobjFlags.SkullFly) != 0) { target.MomX = target.MomY = target.MomZ = Fixed.Zero; } var player = target.Player; if (player != null && this.world.Options.Skill == GameSkill.Baby) { // Take half damage in trainer mode. damage >>= 1; } // Some close combat weapons should not inflict thrust and // push the victim out of reach, thus kick away unless using the chainsaw. var notChainsawAttack = source == null || source.Player == null || !(source.Player.ReadyWeapon.Info is WeaponChainsaw); if (inflictor != null && (target.Flags & MobjFlags.NoClip) == 0 && notChainsawAttack) { var ang = Geometry.PointToAngle(inflictor.X, inflictor.Y, target.X, target.Y); var thrust = new Fixed(damage * (Fixed.FracUnit >> 3) * 100 / target.Info.Mass); // Make fall forwards sometimes. if (damage < 40 && damage > target.Health && target.Z - inflictor.Z > Fixed.FromInt(64) && (this.world.Random.Next() & 1) != 0) { ang += Angle.Ang180; thrust *= 4; } target.MomX += thrust * Trig.Cos(ang); target.MomY += thrust * Trig.Sin(ang); } // Player specific. if (player != null) { // End of game hell hack. if (target.Subsector.Sector.Special == (SectorSpecial)11 && damage >= target.Health) { damage = target.Health - 1; } // Below certain threshold, ignore damage in GOD mode, or with INVUL power. if (damage < 1000 && ((player.Cheats & CheatFlags.GodMode) != 0 || player.Powers[(int)PowerType.Invulnerability] > 0)) { return; } int saved; if (player.ArmorType != 0) { if (player.ArmorType == 1) { saved = damage / 3; } else { saved = damage / 2; } if (player.ArmorPoints <= saved) { // Armor is used up. saved = player.ArmorPoints; player.ArmorType = 0; } player.ArmorPoints -= saved; damage -= saved; } // Mirror mobj health here for Dave. player.Health -= damage; if (player.Health < 0) { player.Health = 0; } player.Attacker = source; // Add damage after armor / invuln. player.DamageCount += damage; if (player.DamageCount > 100) { // Teleport stomp does 10k points... player.DamageCount = 100; } } // Do the damage. target.Health -= damage; if (target.Health <= 0) { this.KillMobj(source, target); return; } if ((this.world.Random.Next() < target.Info.PainChance) && (target.Flags & MobjFlags.SkullFly) == 0) { // Fight back! target.Flags |= MobjFlags.JustHit; target.SetState(target.Info.PainState); } // We're awake now... target.ReactionTime = 0; if ((target.Threshold == 0 || target.Type == MobjType.Vile) && source != null && source != target && source.Type != MobjType.Vile) { // If not intent on another player, chase after this one. target.Target = source; target.Threshold = ThingInteraction.baseThreshold; if (target.State == DoomInfo.States[(int)target.Info.SpawnState] && target.Info.SeeState != MobjState.Null) { target.SetState(target.Info.SeeState); } } }
private static double ObjectiveFunction(double x) { return(-x *Trig.Cos(-2 *x) * Math.Exp(-x / 3)); }
/// <summary> /// Spawns cubes upon an arc of an ellipse. /// </summary> /// <param name="n"> The number of cubes. </param> /// <param name="center"> The center of the ellipse. </param> /// <param name="sectorChordLength"> The length of the arc's chord. </param> /// <param name="semiMinorAxisLength"> Half the length of the minor axis. </param> /// <param name="sectorAngleDeg"> The angular length of the arc in degrees. </param> /// <param name="cubePrefab"> The template for the cubes. </param> /// <param name="parent"> The parent of the cubes structure. </param> /// <returns> Returns the GameObject containing the cubes, whose parent is 'parent'. </returns> /// <remarks> This function is definitely very time-expensive. Use sparingly. </remarks> private static GameObject SpawnEllipseCubes(int n, Vector3 center, float sectorChordLength, float semiMinorAxisLength, float sectorAngleDeg, GameObject cubePrefab, Transform parent = null) { // TODO: Find constraints for every parameter and variable (minimum and maximum values) // Problem: for some combination of input parameters, the FindRoots.OfFunction method fails with an exception: // "ArithmeticException: Function does not accept floating point Not-a-Number values." // Sample parameters: (16,, 12.0f, 6.0f, 90°,,) #region Maths /* * C: center of the ellipse * R: sectorChordLength, the length of the chord * a: half the length of the major axis of the ellipse * b: semiMinorAxisLength, half the length of the minor axis of the ellipse * α: sectorAngleDeg, the sector extension in degrees, from (90° - α/2) to (90° + α/2) counterclock-wise * n: number of cubes * * * § 1) ELLIPSE'S EQUATIONS * * ξ(ϑ): ellipse's parametric equations [-180° ≤ ϑ ≤ 180°] * where ϑ is the angle of (ξˣ(ϑ), ξʸ(ϑ)) with the major axis of the ellipse itself * ___________________________ * ξˣ(ϑ) = a b cos(ϑ) √(a² sin²(ϑ) + b² cos²(ϑ))⁻¹ * ___________________________ * ξʸ(ϑ) = a b sin(ϑ) √(a² sin²(ϑ) + b² cos²(ϑ))⁻¹ * * A = ξ(90° + α/2) * B = ξ(90° - α/2) * * __ _______________________________ * R = AB = 2 a b sin(α/2) √(a² cos²(α/2) + b² sin²(α/2))⁻¹ * _________________________________________ _______________________ * a = √(b² R² tan²(α/2)) / (4 b² tan²(α/2) - R²) = (b R tan(α/2)) √(4 b² tan²(α/2) - R²)⁻¹ * * * § 2) CUBES PLACEMENT * A _____________________ * L = ∫(√D[ξˣ(ϑ)]² + D[ξʸ(ϑ)]²)dϑ: length of arc AB * B * l = L/n: distance between each cube's center on the arc * * θᵢ: angle of the iᵗʰ arc segment; arc length to the iᵗʰ arc segment = i l * θᵢ = FindRoot[∫(D[ξˣ(ϑ)]² + D[ξʸ(ϑ)]²)dϑ = i l] 😭 * * Pᵢ = ξ(θᵢ): position of the iᵗʰ cube * * * § 3) CUBES' SIDE LENGTH * Let Pᵢ and Pⱼ be the positions of two consecutive cubes (j = i + 1) with angles θᵢ and θⱼ. * Let Pₖ be a third point placed at the same arc-distance from both Pᵢ and Pⱼ; the angle of Pₖ is θₖ. * * rₖ: line from C to Pₖ * rᵢ: line from C to Pᵢ * rⱼ: line from C to Pⱼ * * M: parametric point on rₖ * dᵢ: segment MPᵢ * dⱼ: segment MPⱼ * * * M = (mˣ, mʸ) = (mˣ, mˣ tan(θₖ)) * ____________________________________ * dᵢ = √(x(Pᵢ) - mˣ)² + (y(Pᵢ) - mˣ tan(θₖ))² * ____________________________________ * dⱼ = √(x(Pⱼ) - mˣ)² + (y(Pⱼ) - mˣ tan(θₖ))² * * ASSERTION * dᵢ = dⱼ = d: dᵢ and dⱼ are the semi-diagonals of the two squares * * (x(Pᵢ) - mˣ)² + (y(Pᵢ) - mˣ tan(θₖ))² = (x(Pⱼ) - mˣ)² + (y(Pⱼ) - mˣ tan(θₖ))² * x(Pᵢ)² + (mˣ)² - 2 x(Pᵢ) mˣ + y(Pᵢ)² + (mˣ tan(θₖ))² - 2 y(Pᵢ) mˣ tan(θₖ) = x(Pⱼ)² + (mˣ)² - 2 x(Pⱼ) mˣ + y(Pⱼ)² + (mˣ tan(θₖ))² - 2 y(Pⱼ) mˣ tan(θₖ) * x(Pᵢ)² - 2 x(Pᵢ) mˣ + y(Pᵢ)² - 2 y(Pᵢ) mˣ tan(θₖ) = x(Pⱼ)² - 2 x(Pⱼ) mˣ + y(Pⱼ)² - 2 y(Pⱼ) mˣ tan(θₖ) * 2 x(Pⱼ) mˣ + 2 y(Pⱼ) mˣ tan(θₖ) - 2 x(Pᵢ) mˣ - 2 y(Pᵢ) mˣ tan(θₖ) = x(Pⱼ)² + y(Pⱼ)² - x(Pᵢ)² - y(Pᵢ)² * 2 mˣ (x(Pⱼ) + y(Pⱼ) tan(θₖ) - x(Pᵢ) - y(Pᵢ) tan(θₖ)) = x(Pⱼ)² + y(Pⱼ)² - x(Pᵢ)² - y(Pᵢ)² * * x(Pⱼ)² + y(Pⱼ)² - x(Pᵢ)² - y(Pᵢ)² * mˣ = —————————————————————————————————————————————————— * 2 (x(Pⱼ) + y(Pⱼ) tan(θₖ) - x(Pᵢ) - y(Pᵢ) tan(θₖ)) * * cube's side length = 2 (d/√2) * */ #endregion Maths double R = sectorChordLength; double b = semiMinorAxisLength; #region § 1) ELLIPSE'S EQUATION double alphaRad = Trig.DegreeToRadian(sectorAngleDeg); double bTanHalfAlpha = b * Trig.Tan(alphaRad / 2.0); double a = (R * bTanHalfAlpha) / Math.Sqrt(4 * bTanHalfAlpha * bTanHalfAlpha - R * R); Func <double, double> EllipseX = t => { double sin = Trig.Sin(t); double cos = Trig.Cos(t); double aSin = a * sin; double bCos = b * cos; return((a * bCos) / Math.Sqrt(aSin * aSin + bCos * bCos)); }; Func <double, double> EllipseY = t => { double sin = Trig.Sin(t); double cos = Trig.Cos(t); double aSin = a * sin; double bCos = b * cos; return((b * aSin) / Math.Sqrt(aSin * aSin + bCos * bCos)); }; #endregion § 1) ELLIPSE'S EQUATION #region § 2) CUBES PLACEMENT Func <double, double> DEllipseX = Differentiate.FirstDerivativeFunc(EllipseX); Func <double, double> DEllipseY = Differentiate.FirstDerivativeFunc(EllipseY); Func <double, double> SqrtOfSumOfSquaredDerivatives = t => { double x = DEllipseX(t); double y = DEllipseY(t); return(Math.Sqrt(x * x + y * y)); }; Func <double, double, double> AngleToArcLength = (fromRad, toRad) => Integrate.OnClosedInterval(SqrtOfSumOfSquaredDerivatives, fromRad, toRad); Func <double, double> ArcLengthToAngle = arcLength => FindRoots.OfFunction(toRad => AngleToArcLength((Math.PI - alphaRad) / 2, toRad) - arcLength, 0, Math.PI); double L = AngleToArcLength((Math.PI - alphaRad) / 2, (Math.PI + alphaRad) / 2); double l = L / (n - 1); Func <int, object[]> CalculateCubePositionAndAngle = i => { double angle = ArcLengthToAngle(i * l); Vector2 position = new Vector2((float)EllipseX(angle), (float)EllipseY(angle)); return(new object[] { position, angle }); }; #endregion § 2) CUBES PLACEMENT #region § 3) CUBES' SIDE LENGTH Vector2[] positions = new Vector2[n]; double[] angles = new double[n]; double[] middleAngles = new double[n - 1]; for (int i = 0; i < n; ++i) { object[] res = CalculateCubePositionAndAngle(i); positions[i] = (Vector2)res[0]; angles[i] = (double)res[1]; if (i > 0) { middleAngles[i - 1] = ArcLengthToAngle((2 * i - 1) * l / 2.0); } } double[] sideLengths = new double[n - 1]; Func <int, double> SideLength = j => { double xj = positions[j].x; double yj = positions[j].y; double xi = positions[j - 1].x; double yi = positions[j - 1].y; double tanK = Trig.Tan(middleAngles[j - 1]); double mx = (xj * xj + yj * yj - (xi * xi + yi * yi)) / (2.0 * (xj + yj * tanK - (xi + yi * tanK))); return(Math.Sqrt((xi - mx) * (xi - mx) + (yi - mx * tanK) * (yi - mx * tanK)) * (2.0 / Math.Sqrt(2.0))); }; for (int i = 1; i < n; ++i) { sideLengths[i - 1] = SideLength(i); } float squareSide = (float)sideLengths.Min(); #endregion § 3) CUBES' SIDE LENGTH // Save parent's current local rotation and scale; it will be re-set later Quaternion parentRotation = parent?.localRotation ?? Quaternion.identity; Vector3 parentScale = parent?.localScale ?? Vector3.one; if (parent != null) { parent.localRotation = Quaternion.identity; parent.localScale = Vector3.one; } GameObject cubesContainer = parent?.gameObject ?? new GameObject(); cubesContainer.name = $"Ellipse_{n}Cubes"; cubesContainer.transform.localPosition = center; // Create the cubes for (int i = 0; i < n; ++i) { GameObject cube = Instantiate(cubePrefab); cube.name = $"Cube{i}"; cube.SetActive(true); // Awake it now, to let it store the prefab's original scale cube.transform.localScale = new Vector3(squareSide, squareSide, squareSide); cube.transform.position = cubesContainer.transform.position + new Vector3(positions[i].x, 0.0f, positions[i].y); cube.transform.localRotation = Quaternion.Euler(0.0f, (float)(90.0 - Trig.RadianToDegree(angles[i])), 0.0f); cube.transform.parent = cubesContainer.transform; } cubesContainer.transform.localRotation = Quaternion.identity; // Re-set parent's original local rotation if (parent != null) { parent.localRotation = parentRotation; parent.localScale = parentScale; } return(cubesContainer); }