/// <summary> /// This will figure how much velocity to apply to each bodyCenter /// TODO: Also calculate angular velocities /// </summary> /// <param name="bodyCenters">The bodies to apply velocity to</param> /// <param name="hitStart">The point of impact</param> /// <param name="hitDirection">The direction and force of the impact</param> /// <param name="mainBodyRadius">The avg radius of the body that is getting blown apart</param> private static Vector3D[] DistributeForces(Point3D[] bodyCenters, Point3D hitStart, Vector3D hitDirection, double mainBodyRadius, ExplosionForceOptions options) { Vector3D hitDirectionUnit = hitDirection.ToUnit(); double hitForceBase = hitDirection.Length / mainBodyRadius; var vectors = bodyCenters. Select(o => { Vector3D direction = o - hitStart; Vector3D directionUnit = direction.ToUnit(); double distance = direction.Length; double scaledDistance = distance; if (options.DistanceDot_Power != null) { double linearDot = Math3D.GetLinearDotProduct(hitDirectionUnit, directionUnit); // making it linear so the power function is more predictable // Exaggerate the distance based on dot product. That way, points in line with the hit will get more of the impact double scale = (1 - Math.Abs(linearDot)); scale = Math.Pow(scale, options.DistanceDot_Power.Value); scaledDistance = scale * distance; } return new { ForcePoint = o, Distance = distance, ScaledDistance = scaledDistance, DirectionUnit = directionUnit, }; }). ToArray(); double[] percents = GetPercentOfProjForce(vectors.Select(o => o.ScaledDistance).ToArray()); Vector3D[] retVal = new Vector3D[vectors.Length]; for (int cntr = 0; cntr < vectors.Length; cntr++) { retVal[cntr] = vectors[cntr].DirectionUnit * (hitForceBase * percents[cntr]); } return retVal; }
private static Vector3D[] CalculateVelocites(Point3D[] centers, ShotHit[] hits, double hullRadius, ExplosionForceOptions options) { Vector3D[] retVal = new Vector3D[centers.Length]; foreach (var hit in hits) { Point3D shotStart = hit.Hit.Item1; if (options.InteriorVelocityCenterPercent != null) { shotStart += ((hit.Hit.Item2 - hit.Hit.Item1) * options.InteriorVelocityCenterPercent.Value); } Vector3D[] velocities = DistributeForces(centers, shotStart, hit.Shot.Item2 * (hit.Shot.Item3 * options.ShotMultiplier), hullRadius, options); for (int cntr = 0; cntr < retVal.Length; cntr++) { retVal[cntr] += velocities[cntr]; } } return retVal; }
private static Tuple<ExplosionForceResponse, bool> SplitHull(ITriangleIndexed[] convexHull, Tuple<Point3D, Vector3D, double>[] shots, ExplosionForceOptions options) { #region intersect with the hull var hits = shots. Select(o => new ShotHit() { Shot = o, Hit = GetHit(convexHull, o.Item1, o.Item2, o.Item3), }). Where(o => o.Hit != null). ToArray(); if (hits.Length == 0) { return null; } #endregion #region voronoi Point3D[] controlPoints = GetVoronoiCtrlPoints(hits, convexHull); VoronoiResult3D voronoi = Math3D.GetVoronoi(controlPoints, true); if (voronoi == null) { return null; } #endregion // There is enough to start populating the response ExplosionForceResponse retVal = new ExplosionForceResponse() { Hits = hits, ControlPoints = controlPoints, Voronoi = voronoi, }; #region intersect voronoi and hull // Intersect Tuple<int, ITriangleIndexed[]>[] shards = null; try { shards = Math3D.GetIntersection_Hull_Voronoi_full(convexHull, voronoi); } catch (Exception) { return Tuple.Create(retVal, false); } if (shards == null) { return Tuple.Create(retVal, false); } // Smooth if (options.ShouldSmoothShards) { shards = shards. Select(o => Tuple.Create(o.Item1, Asteroid.SmoothTriangles(o.Item2))). ToArray(); } // Validate shards = shards. Where(o => o.Item2 != null && o.Item2.Length >= 3). Where(o => { Vector3D firstNormal = o.Item2[0].NormalUnit; return o.Item2.Skip(1).Any(p => !Math.Abs(Vector3D.DotProduct(firstNormal, p.NormalUnit)).IsNearValue(1)); }). ToArray(); if (shards.Length == 0) { return Tuple.Create(retVal, false); } #endregion #region populate shards retVal.Shards = shards. Select(o => { var aabb = Math3D.GetAABB(o.Item2); double radius = Math.Sqrt((aabb.Item2 - aabb.Item1).Length / 2); Point3D center = Math3D.GetCenter(TriangleIndexed.GetUsedPoints(o.Item2)); Vector3D centerVect = center.ToVector(); Point3D[] allPoints = o.Item2[0].AllPoints. Select(p => p - centerVect). ToArray(); TriangleIndexed[] shiftedTriangles = o.Item2. Select(p => new TriangleIndexed(p.Index0, p.Index1, p.Index2, allPoints)). ToArray(); return new ExplosionForceShard() { VoronoiControlPointIndex = o.Item1, Hull_ParentCoords = o.Item2, Hull_Centered = shiftedTriangles, Radius = radius, Center_ParentCoords = center, }; }). ToArray(); #endregion return Tuple.Create(retVal, true); }
private static Vector3D[] GetVelocities(ExplosionForceShard[] hullShards, ShotHit[] hits, double hullRadius, ExplosionForceOptions options) { //TODO: When the asteroid shards aren't resmoothed, the velocities are very small // //Maybe add more orth velocity // //Maybe pull the hit source slightly into the asteroid --- done // //Maybe add some random velocity (size proportional to the velocity) --- done Vector3D[] retVal = CalculateVelocites(hullShards.Select(o => o.Center_ParentCoords).ToArray(), hits, hullRadius, options); if (options.RandomVelocityPercent != null) { retVal = retVal. Select(o => o + Math3D.GetRandomVector_Spherical(o.Length * options.RandomVelocityPercent.Value)). ToArray(); } return retVal; }
public static ExplosionForceResponse ShootHull(ITriangleIndexed[] convexHull, Tuple<Point3D, Vector3D, double>[] shots, ExplosionForceOptions options = null) { options = options ?? new ExplosionForceOptions(); // Create a voronoi, and intersect with the hull var retVal = SplitHull(convexHull, shots, options); if (retVal == null) return null; else if (!retVal.Item2) return retVal.Item1; // Figure out velocities var aabb = Math3D.GetAABB(convexHull); retVal.Item1.Velocities = GetVelocities(retVal.Item1.Shards, retVal.Item1.Hits, Math.Sqrt((aabb.Item2 - aabb.Item1).Length / 2), options); return retVal.Item1; }