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)); }
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); }
private void ClearShots() { _shots = null; _voronoi = null; _explosion = null; ClearVisuals(); }
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(); }
private AsteroidOrMineralDefinition[] DetermineDestroyedChildrenMinerals(HullVoronoiExploder_Response shards, double overDamage, Vector3D parentRadius, double minChildRadius, Func<double, ITriangleIndexed[], double> getMassByRadius, Func<double, MineralDNA[]> getMineralsByDestroyedMass) { const double MAXOVERDMG = 13; // overdamage of 1 is the smallest value (the asteroid was barely destroyed). Larger values are overkill, and the asteroid becomes more fully destroyed if (shards == null || shards.Shards == null || shards.Shards.Length == 0) { // There was problem, so just pop out some minerals return DetermineDestroyedChildrenMinerals_Minerals(parentRadius, getMassByRadius, getMineralsByDestroyedMass); } Random rand = StaticRandom.GetRandomForThread(); #region calculate volumes var shardVolumes = shards.Shards. Select(o => { Vector3D radius = GetEllipsoidRadius(o.Hull_Centered); return new { Radius = radius, Volume = GetEllipsoidVolume(radius) }; }). ToArray(); // Figure out how much volume should permanently be destroyed double parentVolume = GetEllipsoidVolume(parentRadius); double volumeToDestroy = GetVolumeToDestroy(parentVolume, overDamage, MAXOVERDMG); #endregion double destroyedVolume = 0; bool[] shouldSelfDestruct = new bool[shards.Shards.Length]; #region detect too small // Get rid of any that are too small //TODO: Also get rid of any that are too thin for (int cntr = 0; cntr < shards.Shards.Length; cntr++) { if (shards.Shards[cntr].Radius < minChildRadius) { shouldSelfDestruct[cntr] = true; destroyedVolume += shardVolumes[cntr].Volume; } } #endregion if (destroyedVolume < volumeToDestroy) { #region remove more // Find the shards that could be removed var candidates = Enumerable.Range(0, shards.Shards.Length). Select(o => new { Index = o, Shard = shards.Shards[o], Volume = shardVolumes[o] }). Where(o => !shouldSelfDestruct[o.Index] && o.Volume.Volume < volumeToDestroy - destroyedVolume). OrderBy(o => o.Volume.Volume). ToList(); while (candidates.Count > 0 && destroyedVolume < volumeToDestroy) { // Figure out which to self destruct (the rand power will favor inicies closer to zero) int index = UtilityCore.GetIndexIntoList(rand.NextPow(2), candidates.Count); // Remove it shouldSelfDestruct[candidates[index].Index] = true; destroyedVolume += candidates[index].Volume.Volume; candidates.RemoveAt(index); // Remove the items at the end that are now too large index = candidates.Count - 1; while (index >= 0) { if (candidates[index].Volume.Volume < volumeToDestroy - destroyedVolume) { break; // it's sorted, so the rest will also be under } else { candidates.RemoveAt(index); index--; } } } #endregion } #region distribute minerals // Figure out the mineral value of the destroyed volume MineralDNA[] mineralDefinitions = null; if (destroyedVolume > 0 && getMineralsByDestroyedMass != null) { double inferredRadius = GetEllipsoidRadius(destroyedVolume); double destroyedMass = getMassByRadius(inferredRadius, null); if (destroyedMass > 0) { mineralDefinitions = getMineralsByDestroyedMass(destroyedMass); } } // Figure out which of the temp asteroids should contain minerals var packedMinerals = new Tuple<int, MineralDNA[]>[0]; if (mineralDefinitions != null && mineralDefinitions.Length > 0) { int[] destroyedIndicies = Enumerable.Range(0, shouldSelfDestruct.Length). Where(o => shouldSelfDestruct[o]). ToArray(); packedMinerals = DistributeMinerals(mineralDefinitions, destroyedIndicies); } #endregion #region final array AsteroidOrMineralDefinition[] retVal = new AsteroidOrMineralDefinition[shards.Shards.Length]; for (int cntr = 0; cntr < retVal.Length; cntr++) { Vector3D velocity = new Vector3D(0, 0, 0); if (shards.Velocities != null && shards.Velocities.Length == shards.Shards.Length) { velocity = shards.Velocities[cntr]; } //NOTE: Only position is needed (The first attempt created random asteroids and pulled them apart. This second attempt //doesn't need to pull them apart) PartSeparator_Part part = new PartSeparator_Part(new[] { new Point3D() }, 0, shards.Shards[cntr].Center_ParentCoords, Quaternion.Identity); //MineralDNA[] mineralsAfter = packedMinerals?.FirstOrDefault(o => o.Item1 == cntr)?.Item2; MineralDNA[] mineralsAfter = null; if (packedMinerals != null) { var found = packedMinerals.FirstOrDefault(o => o.Item1 == cntr); if (found != null) { mineralsAfter = found.Item2; } } retVal[cntr] = new AsteroidOrMineralDefinition(part, shards.Shards[cntr].Hull_Centered, shards.Shards[cntr].Radius, velocity, shouldSelfDestruct[cntr], mineralsAfter); } #endregion return retVal; }