public static HullVoronoiExploder_Response ShootHull(ITriangleIndexed[] convexHull, Tuple<Point3D, Vector3D, double>[] shots, HullVoronoiExploder_Options options = null) { options = options ?? new HullVoronoiExploder_Options(); var aabb = Math3D.GetAABB(convexHull); double aabbLen = (aabb.Item2 - aabb.Item1).Length; // Create a voronoi, and intersect with the hull Tuple<HullVoronoiExploder_Response, bool> retVal = null; for (int cntr = 0; cntr < 15; cntr++) { try { retVal = SplitHull(convexHull, shots, options, aabbLen); break; } catch (Exception) { // Every once in a while, there is an error with the voronoi, or voronoi intersecting the hull, etc. Just try // again with new random points } } if (retVal == null) return null; else if (!retVal.Item2) return retVal.Item1; // Figure out velocities retVal.Item1.Velocities = GetVelocities(retVal.Item1.Shards, retVal.Item1.Hits, Math.Sqrt(aabbLen / 2), options); return retVal.Item1; }
private static Tuple <HullVoronoiExploder_Response, bool> SplitHull(ITriangleIndexed[] convexHull, Tuple <Point3D, Vector3D, double>[] shots, HullVoronoiExploder_Options options, double aabbLen) { #region intersect with the hull var hits = shots. Select(o => new HullVoronoiExploder_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, options.MinCount, options.MaxCount, aabbLen); VoronoiResult3D voronoi = Math3D.GetVoronoi(controlPoints, true); if (voronoi == null) { return(null); } #endregion // There is enough to start populating the response HullVoronoiExploder_Response retVal = new HullVoronoiExploder_Response() { 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 HullVoronoiExploder_Shard() { VoronoiControlPointIndex = o.Item1, Hull_ParentCoords = o.Item2, Hull_Centered = shiftedTriangles, Radius = radius, Center_ParentCoords = center, }); }). Where(o => o != null). ToArray(); #endregion return(Tuple.Create(retVal, true)); }
/// <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, HullVoronoiExploder_Options 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, HullVoronoiExploder_ShotHit[] hits, double hullRadius, HullVoronoiExploder_Options 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); Vector3D[] velocities = DistributeForces(centers, shotStart, hit.Shot.Item2 * hit.Shot.Item3, hullRadius, options); for (int cntr = 0; cntr < retVal.Length; cntr++) { retVal[cntr] += velocities[cntr]; } } return(retVal); }
private static Vector3D[] GetVelocities(HullVoronoiExploder_Shard[] hullShards, HullVoronoiExploder_ShotHit[] hits, double hullRadius, HullVoronoiExploder_Options 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 HullVoronoiExploder_Response ShootHull(ITriangleIndexed[] convexHull, Tuple <Point3D, Vector3D, double>[] shots, HullVoronoiExploder_Options options = null) { options = options ?? new HullVoronoiExploder_Options(); var aabb = Math3D.GetAABB(convexHull); double aabbLen = (aabb.Item2 - aabb.Item1).Length; // Create a voronoi, and intersect with the hull Tuple <HullVoronoiExploder_Response, bool> retVal = null; for (int cntr = 0; cntr < 15; cntr++) { try { retVal = SplitHull(convexHull, shots, options, aabbLen); break; } catch (Exception) { // Every once in a while, there is an error with the voronoi, or voronoi intersecting the hull, etc. Just try // again with new random points } } if (retVal == null) { return(null); } else if (!retVal.Item2) { return(retVal.Item1); } // Figure out velocities retVal.Item1.Velocities = GetVelocities(retVal.Item1.Shards, retVal.Item1.Hits, Math.Sqrt(aabbLen / 2), options); return(retVal.Item1); }
private static Tuple<HullVoronoiExploder_Response, bool> SplitHull(ITriangleIndexed[] convexHull, Tuple<Point3D, Vector3D, double>[] shots, HullVoronoiExploder_Options options, double aabbLen) { #region intersect with the hull var hits = shots. Select(o => new HullVoronoiExploder_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, options.MinCount, options.MaxCount, aabbLen); VoronoiResult3D voronoi = Math3D.GetVoronoi(controlPoints, true); if (voronoi == null) { return null; } #endregion // There is enough to start populating the response HullVoronoiExploder_Response retVal = new HullVoronoiExploder_Response() { 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 HullVoronoiExploder_Shard() { VoronoiControlPointIndex = o.Item1, Hull_ParentCoords = o.Item2, Hull_Centered = shiftedTriangles, Radius = radius, Center_ParentCoords = center, }; }). Where(o => o != null). ToArray(); #endregion return Tuple.Create(retVal, true); }
/// <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, HullVoronoiExploder_Options 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, HullVoronoiExploder_ShotHit[] hits, double hullRadius, HullVoronoiExploder_Options 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); Vector3D[] velocities = DistributeForces(centers, shotStart, hit.Shot.Item2 * hit.Shot.Item3, hullRadius, options); for (int cntr = 0; cntr < retVal.Length; cntr++) { retVal[cntr] += velocities[cntr]; } } return retVal; }
private static Vector3D[] GetVelocities(HullVoronoiExploder_Shard[] hullShards, HullVoronoiExploder_ShotHit[] hits, double hullRadius, HullVoronoiExploder_Options 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; }
private void ProcessShots() { int parsedInt; int? minCount = null; if (int.TryParse(txtMinShards.Text, out parsedInt)) { minCount = parsedInt; txtMinShards.Effect = null; } else { txtMinShards.Effect = _errorEffect; } int? maxCount = null; if (int.TryParse(txtMaxShards.Text, out parsedInt)) { maxCount = parsedInt; txtMaxShards.Effect = null; } else { txtMaxShards.Effect = _errorEffect; } // Dist dot power double? distDotPow = null; txtDistDotPow.Effect = null; if (chkUseDistDot.IsChecked.Value) { double parsed; if (double.TryParse(txtDistDotPow.Text, out parsed)) { distDotPow = parsed; } else { txtDistDotPow.Effect = _errorEffect; } } HullVoronoiExploder_Options options = new HullVoronoiExploder_Options() { DistanceDot_Power = distDotPow, InteriorVelocityCenterPercent = chkUseInteriorVelocityCenterPercent.IsChecked.Value ? trkInteriorVelocityCenterPercent.Value : (double?)null, RandomVelocityPercent = chkUseRandomVelocityPercent.IsChecked.Value ? trkRandomVelocityPercent.Value : (double?)null, ShouldSmoothShards = chkSmoothShards.IsChecked.Value, }; if (minCount != null) options.MinCount = minCount.Value; if (maxCount != null) options.MaxCount = maxCount.Value; // The length of _shots direction is for the gui (it starts beyond the asteroid, and goes well past it). The worker needs an actual length var adjustedShots = _shots. Select(o => Tuple.Create(o.Item1, o.Item2.ToUnit() * trkShotMultiplier.Value, o.Item3)). ToArray(); _explosion = HullVoronoiExploder.ShootHull(_asteroid, adjustedShots, options); DrawShatteredAsteroid(); }
// This also creates minerals private AsteroidOrMineralDefinition[] GetChildAsteroidsOrMinerals(Tuple<Point3D, Vector3D, double>[] hits, double overDamage) { int childCount = GetChildCounts(overDamage); // Reduce the length of the hits so the shard velocities aren't so high hits = hits. Select(o => Tuple.Create(o.Item1, o.Item2 * .33, o.Item3)). ToArray(); HullVoronoiExploder_Options options = new HullVoronoiExploder_Options() { MinCount = childCount, MaxCount = childCount, }; HullVoronoiExploder_Response shards = HullVoronoiExploder.ShootHull(_parent._triangles, hits, options); #region cap velocities //NOTE: This caps the velocities by running them through an s curve, but if they are all too high to begin with, they //will all become very near MAXVELOCITY if (shards != null && shards.Velocities != null) { shards.Velocities = shards.Velocities. Select(o => { double length = o.Length; double adjusted = Math1D.PositiveSCurve(length, MAXVELOCITY, .75); double ratio = adjusted / length; return o * ratio; }). ToArray(); } #endregion return DetermineDestroyedChildrenMinerals(shards, overDamage, _parent.RadiusVect, _minChildRadius, _getMassByRadius, _getMineralsByDestroyedMass); }