private const double MOVEPERSTEPPERCENT = 1d; // this seems to be stable with 100%, if nessassary, drop it down a bit so that parts don't move as far each step #endregion //TODO: Make a better version, probably a combination of pulling in and separating public static void PullInCrude(out bool changed, PartSeparator_Part[] parts) { // Figure out the max radius double[] sizes = parts.Select(o => (o.Size.X + o.Size.Y + o.Size.Z) / 3d).ToArray(); double largestPart = sizes.Max(); double maxRadius = largestPart * 8d; double maxRadiusSquare = maxRadius * maxRadius; Point3D center = Math3D.GetCenter(parts.Select(o => Tuple.Create(o.Position, o.Mass)).ToArray()); changed = false; for (int cntr = 0; cntr < parts.Length; cntr++) { Vector3D offset = parts[cntr].Position - center; //NOTE: This is just going to the center of the part, it's not considering the extents of the part (this method IS called crude) if (offset.LengthSquared < maxRadiusSquare) { continue; } // Pull it straight in double difference = offset.Length - maxRadius; offset.Normalize(); offset *= difference * -1d; parts[cntr].Position += offset; //NOTE: I'm not going to change the center of mass changed = true; } }
// Mineral public AsteroidOrMineralDefinition(PartSeparator_Part part, MineralDNA mineralDefinition) { this.IsAsteroid = false; this.Part = part; this.MineralDefinition = mineralDefinition; this.AsteroidTriangles = null; this.AsteroidRadius = 0; }
// Asteroid public AsteroidOrMineralDefinition(PartSeparator_Part part, ITriangleIndexed[] asteroidTriangles, double asteroidRadius) { this.IsAsteroid = true; this.Part = part; this.AsteroidTriangles = asteroidTriangles; this.AsteroidRadius = asteroidRadius; this.MineralDefinition = null; }
public static CollisionHull[] Separate(out bool changed, PartSeparator_Part[] parts, World world) { changed = false; bool[] hasMoved = new bool[parts.Length]; // defaults to false CollisionHull[] hulls = parts. Select(o => o.CreateCollisionHull(world)). ToArray(); // Move the parts for (int cntr = 0; cntr < MAXSTEPS; cntr++) // execution will break out of this loop early if parts are no longer intersecting { Intersection[] intersections = GetIntersections(parts, hulls, hasMoved, world); if (intersections.Length == 0) { break; } DoStep(intersections, parts, hasMoved); changed = true; } // Ensure hulls are synced for (int cntr = 0; cntr < parts.Length; cntr++) { if (hasMoved[cntr]) { hulls[cntr].Dispose(); hulls[cntr] = parts[cntr].CreateCollisionHull(world); } } // Exit Function return hulls; }
private static void DoStep_Move(PartSeparator_Part[] parts, SortedList<int, List<Tuple<Vector3D?, Quaternion?>>> moves) { foreach (int partIndex in moves.Keys) { foreach (var move in moves[partIndex]) { if (move.Item1 != null) { parts[partIndex].Position += move.Item1.Value; } if (move.Item2 != null) { parts[partIndex].Orientation = parts[partIndex].Orientation.RotateBy(move.Item2.Value); } } } }
private static void DoStep(Intersection[] intersections, PartSeparator_Part[] parts, bool[] hasMoved) { SortedList<int, List<Tuple<Vector3D?, Quaternion?>>> moves = new SortedList<int, List<Tuple<Vector3D?, Quaternion?>>>(); double crazyScale = 1d; if (intersections.Length > parts.Length) { // If there are lots of parts intersecting each other at once, this will move parts too far (because it does a scan of all intersections, // then moves the parts using the sum of all intersections). This is a crude attempt to soften that effect crazyScale = Convert.ToDouble(parts.Length) / Convert.ToDouble(intersections.Length); } // Shoot through all the part pairs foreach (var intersection in intersections) { double mass1 = parts[intersection.Index1].Mass; double mass2 = parts[intersection.Index2].Mass; double totalMass = mass1 + mass2; double sumPenetration = intersection.Intersections.Sum(o => o.PenetrationDistance); // there really needs to be a joke here. Something about seven inches at a time double avgPenetration = sumPenetration / Convert.ToDouble(intersection.Intersections.Length); Vector3D direction = (parts[intersection.Index2].Position - parts[intersection.Index1].Position).ToUnit(); double sizeScale = MOVEPERSTEPPERCENT * (1d / Convert.ToDouble(intersection.Intersections.Length)); // Shoot through the intersecting points between these two parts foreach (var intersectPoint in intersection.Intersections) { // The sum of scaledDistance needs to add up to avgPenetration double percentDistance = intersectPoint.PenetrationDistance / sumPenetration; double scaledDistance = avgPenetration * percentDistance; // May not want to move the full distance in one step scaledDistance *= MOVEPERSTEPPERCENT * crazyScale; double distance1 = scaledDistance * ((totalMass - mass1) / totalMass); double distance2 = scaledDistance * ((totalMass - mass2) / totalMass); // Part1 Vector3D translation, torque; Vector3D offset1 = intersectPoint.ContactPoint - parts[intersection.Index1].Position; Math3D.SplitForceIntoTranslationAndTorque(out translation, out torque, offset1, direction * (-1d * distance1)); DoStep_AddForce(moves, intersection.Index1, translation, DoStep_Rotate(torque, intersection.AvgSize1, sizeScale)); // don't use the full size, or the rotation won't even be noticable // Part2 Vector3D offset2 = intersectPoint.ContactPoint - parts[intersection.Index2].Position; Math3D.SplitForceIntoTranslationAndTorque(out translation, out torque, offset2, direction * distance2); DoStep_AddForce(moves, intersection.Index2, translation, DoStep_Rotate(torque, intersection.AvgSize2, sizeScale)); } } // Apply the movements DoStep_Move(parts, moves); // Remember which parts were modified foreach (int index in moves.Keys) { hasMoved[index] = true; } }
/// <summary> /// This finds intersections between all the hulls /// </summary> private static Intersection[] GetIntersections(PartSeparator_Part[] parts, CollisionHull[] hulls, bool[] hasMoved, World world) { List<Intersection> retVal = new List<Intersection>(); // Compare each hull to the others for (int outer = 0; outer < hulls.Length - 1; outer++) { double? sizeOuter = null; for (int inner = outer + 1; inner < hulls.Length; inner++) { // Rebuild hulls if nessessary if (hasMoved[outer]) { hulls[outer].Dispose(); hulls[outer] = parts[outer].CreateCollisionHull(world); hasMoved[outer] = false; } if (hasMoved[inner]) { hulls[inner].Dispose(); hulls[inner] = parts[inner].CreateCollisionHull(world); hasMoved[inner] = false; } // Get intersecting points CollisionHull.IntersectionPoint[] points = hulls[outer].GetIntersectingPoints_HullToHull(100, hulls[inner], 0); if (points != null && points.Length > 0) { sizeOuter = sizeOuter ?? (parts[outer].Size.X + parts[outer].Size.X + parts[outer].Size.X) / 3d; double sizeInner = (parts[inner].Size.X + parts[inner].Size.X + parts[inner].Size.X) / 3d; double sumSize = sizeOuter.Value + sizeInner; double minSize = sumSize * IGNOREDEPTHPERCENT; // Filter out the shallow penetrations //TODO: May need to add the lost distance to the remaining intersections points = points.Where(o => o.PenetrationDistance > minSize).ToArray(); if (points != null && points.Length > 0) { retVal.Add(new Intersection(outer, inner, sizeOuter.Value, sizeInner, points)); } } } } // Exit Function return retVal.ToArray(); }
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; }
// Mineral public AsteroidOrMineralDefinition(PartSeparator_Part part, MineralDNA mineralDefinition, Vector3D velocity) { this.IsAsteroid = false; this.Part = part; this.MineralDefinition = mineralDefinition; this.Velocity = velocity; this.AsteroidTriangles = null; this.AsteroidRadius = 0; this.ShouldAsteroidSelfDestruct = false; }
// Asteroid public AsteroidOrMineralDefinition(PartSeparator_Part part, ITriangleIndexed[] asteroidTriangles, double asteroidRadius, Vector3D velocity, bool shouldSelfDestruct, MineralDNA[] mineralsAfterSelfDestruct) { this.IsAsteroid = true; this.Part = part; this.AsteroidTriangles = asteroidTriangles; this.AsteroidRadius = asteroidRadius; this.ShouldAsteroidSelfDestruct = shouldSelfDestruct; this.MineralsAfterSelfDestruct = mineralsAfterSelfDestruct; this.Velocity = velocity; this.MineralDefinition = null; }
/// <summary> /// Defines the child asteroid shapes, then finds positions/orientations for all child asteroids and minerals (makes /// sure nothing overlaps) /// </summary> private AsteroidOrMineralDefinition[] PositionChildAsteroidsAndMinerals(double[] asteroidVolumes, MineralDNA[] mineralDefinitions) { const double FOURTHIRDSPI = 4d / 3d * Math.PI; const double ONETHRID = 1d / 3d; double positionRange = _radiusMin * .05; #region Asteroids PartSeparator_Part[] asteroidParts = new PartSeparator_Part[asteroidVolumes.Length]; ITriangleIndexed[][] asteroidTriangles = new ITriangleIndexed[asteroidVolumes.Length][]; double[] asteroidRadii = new double[asteroidVolumes.Length]; for (int cntr = 0; cntr < asteroidVolumes.Length; cntr++) { // r^3=v/(4/3pi) asteroidRadii[cntr] = Math.Pow(asteroidVolumes[cntr] / FOURTHIRDSPI, ONETHRID); asteroidTriangles[cntr] = GetHullTriangles_Initial(asteroidRadii[cntr]); double currentMass = _getMassByRadius(asteroidRadii[cntr], asteroidTriangles[cntr]); asteroidParts[cntr] = new PartSeparator_Part(asteroidTriangles[cntr][0].AllPoints, currentMass, Math3D.GetRandomVector_Spherical(positionRange).ToPoint(), Math3D.GetRandomRotation()); } #endregion #region Minerals PartSeparator_Part[] mineralParts = new PartSeparator_Part[mineralDefinitions.Length]; for (int cntr = 0; cntr < mineralDefinitions.Length; cntr++) { //NOTE: Copied logic from Mineral that gets collision points and mass // Points Point3D[] mineralPoints = UtilityWPF.GetPointsFromMesh((MeshGeometry3D)_sharedVisuals.Value.GetMineralMesh(mineralDefinitions[cntr].MineralType)); // Mass double mass = mineralDefinitions[cntr].Density * mineralDefinitions[cntr].Volume; // Store it mineralParts[cntr] = new PartSeparator_Part(mineralPoints, mass, Math3D.GetRandomVector_Spherical(positionRange).ToPoint(), Math3D.GetRandomRotation()); } #endregion #region Pull Apart if (asteroidParts.Length + mineralParts.Length > 1) { PartSeparator_Part[] allParts = UtilityCore.ArrayAdd(asteroidParts, mineralParts); bool dummy; CollisionHull[] hulls = PartSeparator.Separate(out dummy, allParts, _world); foreach (CollisionHull hull in hulls) { hull.Dispose(); } } #endregion #region Build Return List<AsteroidOrMineralDefinition> retVal = new List<AsteroidOrMineralDefinition>(); for (int cntr = 0; cntr < asteroidParts.Length; cntr++) { retVal.Add(new AsteroidOrMineralDefinition(asteroidParts[cntr], asteroidTriangles[cntr], asteroidRadii[cntr])); } for (int cntr = 0; cntr < mineralParts.Length; cntr++) { retVal.Add(new AsteroidOrMineralDefinition(mineralParts[cntr], mineralDefinitions[cntr])); } #endregion return retVal.ToArray(); }
private AsteroidOrMineralDefinition[] PositionMinerals(MineralDNA[] minerals, double? maxRandomVelocity) { double positionRange = _radiusMin * .05; #region build parts PartSeparator_Part[] parts = new PartSeparator_Part[minerals.Length]; for (int cntr = 0; cntr < minerals.Length; cntr++) { //NOTE: Copied logic from Mineral that gets collision points and mass // Points Point3D[] mineralPoints = UtilityWPF.GetPointsFromMesh((MeshGeometry3D)_sharedVisuals.Value.GetMineralMesh(minerals[cntr].MineralType)); // Mass double mass = minerals[cntr].Density * minerals[cntr].Volume; // Store it parts[cntr] = new PartSeparator_Part(mineralPoints, mass, Math3D.GetRandomVector_Spherical(positionRange).ToPoint(), Math3D.GetRandomRotation()); } #endregion #region pull apart if (parts.Length > 1) { bool dummy; CollisionHull[] hulls = PartSeparator.Separate(out dummy, parts, _world); foreach (CollisionHull hull in hulls) { hull.Dispose(); } } #endregion #region build return List<AsteroidOrMineralDefinition> retVal = new List<AsteroidOrMineralDefinition>(); for (int cntr = 0; cntr < parts.Length; cntr++) { Vector3D velocity = new Vector3D(0, 0, 0); if (maxRandomVelocity != null) { velocity = Math3D.GetRandomVector_Spherical(maxRandomVelocity.Value); } retVal.Add(new AsteroidOrMineralDefinition(parts[cntr], minerals[cntr], velocity)); } #endregion return retVal.ToArray(); }