/// <summary> /// The True Anomaly in radians /// https://en.wikipedia.org/wiki/True_anomaly#From_state_vectors /// </summary> /// <returns>The True Anomaly in radians</returns> /// <param name="eccentVector">Eccentricity vector.</param> /// <param name="position">Position ralitive to parent</param> /// <param name="velocity">Velocity ralitive to parent</param> public static double TrueAnomaly(Vector3 eccentVector, Vector3 position, Vector3 velocity) { double e = eccentVector.Length(); //eccentricity double r = position.Length(); if (e > Epsilon) //if eccentricity is bigger than a tiny amount, it's a circular orbit. { double dotEccPos = Vector3.Dot(eccentVector, position); double talen = e * r; talen = dotEccPos / talen; talen = GMath.Clamp(talen, -1, 1); var trueAnomoly = Math.Acos(talen); if (Vector3.Dot(position, velocity) < 0) { trueAnomoly = Math.PI * 2 - trueAnomoly; } return(Angle.NormaliseRadiansPositive(trueAnomoly)); } else { return(Angle.NormaliseRadiansPositive(Math.Atan2(position.Y, position.X))); //circular orbit, assume AoP is 0; } }
/// <summary> /// Generates Data for a star based on it's spectral type and populates it with the data. /// </summary> /// <remarks> /// This function randomly generates the Radius, Temperature, Luminosity, Mass and Age of a star and then returns a star populated with those generated values. /// What follows is a brief description of how that is done for each data point: /// <list type="Bullet"> /// <item> /// <b>Temperature:</b> The Temp. of the star is obtained by using the Randon.Next(min, max) function to get a random Temp. in the range a star of the given /// spectral type. /// </item> /// <item> /// <b>Luminosity:</b> The Luminosity of a star is calculated by using the RNG_NextDoubleRange() function to get a random Luminosity in the range a star of the /// given spectral type. /// </item> /// <item> /// <b>Age:</b> The possible ages for a star depend largely on its mass. The bigger and heaver the star the more pressure is put on its core where fusion occur /// which increases the rate that it burns Hydrogen which reduces the life of the star. The Big O class stars only last a few million years before either /// going Hyper Nova or devolving into a class B star. on the other hand a class G star (like Sol) has a life expectancy of about 10 billion years while a /// little class M star could last 100 billion years or more (hard to tell given that the Milky way is 13.2 billion years old and the universe is only /// about a billion years older then that). Given this we first use the mass of the star to produce a number between 0 and 1 that we can use to pick a /// possible age from the range (just like all the above). To get the number between 0 and 1 we use the following formula: /// <c>1 - Mass / MaxMassOfStarOfThisType</c> /// </item> /// </list> /// </remarks> /// <param name="starMVDB">The SystemBodyDB of the star.</param> /// <param name="spectralType">The Spectral Type of the star.</param> /// <param name="randomSelection">Random selection to generate consistent values.</param> /// <returns>A StarInfoDB Populated with data generated based on Spectral Type and SystemBodyDB information provided.</returns> private StarInfoDB GenerateStarInfo(MassVolumeDB starMVDB, SpectralType spectralType, double randomSelection) { double maxStarAge = _galaxyGen.Settings.StarAgeBySpectralType[spectralType].Max; StarInfoDB starData = new StarInfoDB {// for star age we will make it proportional to the inverse of the stars mass ratio (for that type of star). // while this will produce the same age for the same mass/type of star the chances of getting the same // mass/type are tiny. Tho there will still be the obvious inverse relationship here. Age = (1 - starMVDB.Mass / _galaxyGen.Settings.StarMassBySpectralType[spectralType].Max) * maxStarAge, SpectralType = spectralType, Temperature = (uint)Math.Round(GMath.SelectFromRange(_galaxyGen.Settings.StarTemperatureBySpectralType[spectralType], randomSelection)), Luminosity = (float)GMath.SelectFromRange(_galaxyGen.Settings.StarLuminosityBySpectralType[spectralType], randomSelection) }; // Generate a string specifying the full spectral class form a star. // start by getting the sub-division, which is based on temp. double sub = starData.Temperature / _galaxyGen.Settings.StarTemperatureBySpectralType[starData.SpectralType].Max; // temp range from 0 to 1. starData.SpectralSubDivision = (ushort)Math.Round((1 - sub) * 10); // invert temp range as 0 is hottest, 9 is coolest. // now get the luminosity class //< @todo For right now everthing is just main sequence. see http://en.wikipedia.org/wiki/Stellar_classification // on how this should be done. For right now tho class V is fine (its just flavor text). starData.LuminosityClass = LuminosityClass.V; // finally add them all up to get the class string: starData.Class = starData.SpectralType + starData.SpectralSubDivision.ToString() + "-" + starData.LuminosityClass; return(starData); }
private void CalculateExtendedParameters() { if (IsStationary) { return; } // Calculate extended parameters. // http://en.wikipedia.org/wiki/Standard_gravitational_parameter#Two_bodies_orbiting_each_other GravitationalParameter_Km3S2 = GMath.GravitationalParameter_Km3s2(_parentMass + _myMass); // Normalize GravitationalParameter from m^3/s^2 to km^3/s^2 GravitationalParameterAU = GMath.GrabitiationalParameter_Au3s2(_parentMass + _myMass); // (149597870700 * 149597870700 * 149597870700); GravitationalParameter_m3S2 = GMath.StandardGravitationalParameter(_parentMass + _myMass); double orbitalPeriod = 2 * Math.PI * Math.Sqrt(Math.Pow(Distance.AuToKm(SemiMajorAxis_AU), 3) / (GravitationalParameter_Km3S2)); if (orbitalPeriod * 10000000 > long.MaxValue) { OrbitalPeriod = TimeSpan.MaxValue; } else { OrbitalPeriod = TimeSpan.FromSeconds(orbitalPeriod); } // http://en.wikipedia.org/wiki/Mean_motion MeanMotion_DegreesSec = Math.Sqrt(GravitationalParameter_Km3S2 / Math.Pow(Distance.AuToKm(SemiMajorAxis_AU), 3)); // Calculated in radians. MeanMotion_DegreesSec = Angle.ToDegrees(MeanMotion_DegreesSec); // Stored in degrees. Apoapsis_AU = (1 + Eccentricity) * SemiMajorAxis_AU; Periapsis_AU = (1 - Eccentricity) * SemiMajorAxis_AU; SOI_m = OrbitMath.GetSOI(SemiMajorAxis, _myMass, _parentMass); }
internal static void MineResources(Entity colonyEntity) { Dictionary <Guid, int> mineRates = colonyEntity.GetDataBlob <ColonyMinesDB>().MineingRate; Dictionary <Guid, MineralDepositInfo> planetMinerals = colonyEntity.GetDataBlob <ColonyInfoDB>().PlanetEntity.GetDataBlob <SystemBodyInfoDB>().Minerals; //Dictionary<Guid, int> colonyMineralStockpile = colonyEntity.GetDataBlob<ColonyInfoDB>().MineralStockpile; CargoStorageDB stockpile = colonyEntity.GetDataBlob <CargoStorageDB>(); float mineBonuses = 1;//colonyEntity.GetDataBlob<ColonyBonusesDB>().GetBonus(AbilityType.Mine); foreach (var kvp in mineRates) { double accessability = planetMinerals[kvp.Key].Accessibility; double actualRate = kvp.Value * mineBonuses * accessability; int mineralsMined = (int)Math.Min(actualRate, planetMinerals[kvp.Key].Amount); long capacity = StorageSpaceProcessor.RemainingCapacity(stockpile, stockpile.CargoTypeID(kvp.Key)); if (capacity > 0) { //colonyMineralStockpile.SafeValueAdd<Guid>(kvp.Key, mineralsMined); StorageSpaceProcessor.AddItemToCargo(stockpile, kvp.Key, mineralsMined); MineralDepositInfo mineralDeposit = planetMinerals[kvp.Key]; int newAmount = mineralDeposit.Amount -= mineralsMined; accessability = Math.Pow((float)mineralDeposit.Amount / mineralDeposit.HalfOriginalAmount, 3) * mineralDeposit.Accessibility; double newAccess = GMath.Clamp(accessability, 0.1, mineralDeposit.Accessibility); mineralDeposit.Amount = newAmount; mineralDeposit.Accessibility = newAccess; } } }
public static double GetEccentricAnomalyFromStateVectors(Vector3 position, double semiMajAxis, double linierEccentricity, double aop) { var x = (position.X * Math.Cos(-aop)) - (position.Y * Math.Sin(-aop)); x = linierEccentricity + x; double foo = GMath.Clamp(x / semiMajAxis, -1, 1); //because sometimes we were getting a floating point error that resulted in numbers infinatly smaller than -1 return(Math.Acos(foo)); }
public static void UpdateAtmosphere(AtmosphereDB atmoDB, SystemBodyInfoDB bodyDB) { if (atmoDB.Exists) { // clear old values. atmoDB.Pressure = 0; atmoDB.GreenhousePressure = 0; foreach (var gas in atmoDB.Composition) { atmoDB.Pressure += gas.Value; // only add a greenhouse gas if it is not frozen or liquid: if (atmoDB.SurfaceTemperature >= gas.Key.BoilingPoint) { // actual greenhouse pressure adjusted by gas GreenhouseEffect. // note that this produces the same affect as in aurora if all GreenhouseEffect bvalue are -1, 0 or 1. atmoDB.GreenhousePressure += (float)gas.Key.GreenhouseEffect * gas.Value; } } if (bodyDB.BodyType == BodyType.GasDwarf || bodyDB.BodyType == BodyType.GasGiant || bodyDB.BodyType == BodyType.IceGiant) { // special gas giant stuff, needed because we do not apply greenhouse factor to them: atmoDB.SurfaceTemperature = bodyDB.BaseTemperature * (1 - atmoDB.Albedo); atmoDB.Pressure = 1; // because thats the definition of the surface of these planets, when // atmosphereic pressure = the pressure of earths atmosphere at its surface (what we call 1 atm). } else { // From Aurora: Greenhouse Factor = 1 + (Atmospheric Pressure /10) + Greenhouse Pressure (Maximum = 3.0) atmoDB.GreenhouseFactor = (atmoDB.Pressure * 0.035F) + atmoDB.GreenhousePressure; // note that we do without the extra +1 as it seems to give us better temps. atmoDB.GreenhouseFactor = (float)GMath.Clamp(atmoDB.GreenhouseFactor, -3.0, 3.0); // From Aurora: Surface Temperature in Kelvin = Base Temperature in Kelvin x Greenhouse Factor x Albedo atmoDB.SurfaceTemperature = Temperature.ToKelvin(bodyDB.BaseTemperature); atmoDB.SurfaceTemperature += atmoDB.SurfaceTemperature * atmoDB.GreenhouseFactor * (float)Math.Pow(1 - atmoDB.Albedo, 0.25); // We need to raise albedo to the power of 1/4, see: http://en.wikipedia.org/wiki/Stefan%E2%80%93Boltzmann_law atmoDB.SurfaceTemperature = Temperature.ToCelsius(atmoDB.SurfaceTemperature); } } else { // simply apply albedo, see here: http://en.wikipedia.org/wiki/Stefan%E2%80%93Boltzmann_law atmoDB.Pressure = 0; atmoDB.SurfaceTemperature = Temperature.ToKelvin(bodyDB.BaseTemperature); atmoDB.SurfaceTemperature = atmoDB.SurfaceTemperature * (float)Math.Pow(1 - atmoDB.Albedo, 0.25); // We need to raise albedo to the power of 1/4 } // update the descriptions: atmoDB.GenerateDescriptions(); }
/* * /// <summary> * /// The True Anomaly in radians * /// </summary> * /// <returns>The anomaly.</returns> * /// <param name="position">Position.</param> * /// <param name="loP">Lo p.</param> * public static double TrueAnomaly(Vector4 position, double loP) * { * return Math.Atan2(position.Y, position.X) - loP; * } */ /// <summary> /// The True Anomaly in radians /// https://en.wikipedia.org/wiki/True_anomaly#From_state_vectors /// </summary> /// <returns>The True Anomaly in radians</returns> /// <param name="eccentVector">Eccentricity vector.</param> /// <param name="position">Position ralitive to parent</param> /// <param name="velocity">Velocity ralitive to parent</param> public static double TrueAnomaly(Vector3 eccentVector, Vector3 position, Vector3 velocity) { var dotEccPos = Vector3.Dot(eccentVector, position); var talen = eccentVector.Length() * position.Length(); talen = dotEccPos / talen; talen = GMath.Clamp(talen, -1, 1); var trueAnomoly = Math.Acos(talen); if (Vector3.Dot(position, velocity) < 0) { trueAnomoly = Math.PI * 2 - trueAnomoly; } return(trueAnomoly); }
private void MineResources(Entity colonyEntity) { Dictionary <Guid, int> mineRates = colonyEntity.GetDataBlob <MiningDB>().MineingRate; Dictionary <Guid, MineralDepositInfo> planetMinerals = colonyEntity.GetDataBlob <ColonyInfoDB>().PlanetEntity.GetDataBlob <SystemBodyInfoDB>().Minerals; VolumeStorageDB stockpile = colonyEntity.GetDataBlob <VolumeStorageDB>(); float mineBonuses = 1;//colonyEntity.GetDataBlob<ColonyBonusesDB>().GetBonus(AbilityType.Mine); foreach (var kvp in mineRates) { ICargoable mineral = _minerals[kvp.Key]; Guid cargoTypeID = mineral.CargoTypeID; double itemMassPerUnit = mineral.MassPerUnit; double accessability = planetMinerals[kvp.Key].Accessibility; double actualRate = kvp.Value * mineBonuses * accessability; int unitsMinableThisTick = (int)Math.Min(actualRate, planetMinerals[kvp.Key].Amount); if (!stockpile.TypeStores.ContainsKey(mineral.CargoTypeID)) { var type = StaticRefLib.StaticData.CargoTypes[mineral.CargoTypeID]; string erstr = "We didn't mine a potential " + unitsMinableThisTick + " of " + mineral.Name + " because we have no way to store " + type.Name + " cargo."; StaticRefLib.EventLog.AddPlayerEntityErrorEvent(colonyEntity, erstr); continue; //can't store this mineral } var unitsMinedThisTick = stockpile.AddCargoByUnit(mineral, unitsMinableThisTick); if (unitsMinableThisTick > unitsMinedThisTick) { var dif = unitsMinableThisTick - unitsMinedThisTick; var type = StaticRefLib.StaticData.CargoTypes[mineral.CargoTypeID]; string erstr = "We didn't mine a potential " + dif + " of " + mineral.Name + " because we don't have enough space to store it."; StaticRefLib.EventLog.AddPlayerEntityErrorEvent(colonyEntity, erstr); } MineralDepositInfo mineralDeposit = planetMinerals[kvp.Key]; int newAmount = mineralDeposit.Amount -= unitsMinedThisTick; accessability = Math.Pow((float)mineralDeposit.Amount / mineralDeposit.HalfOriginalAmount, 3) * mineralDeposit.Accessibility; double newAccess = GMath.Clamp(accessability, 0.1, mineralDeposit.Accessibility); mineralDeposit.Amount = newAmount; mineralDeposit.Accessibility = newAccess; } }
/// <summary> /// This returns the heading mesured from the periapsis (AoP) and is on the plane of the object /// Add the LoP to this to get the true heading in a 2d orbit. /// </summary> /// <returns>The from periaps.</returns> /// <param name="pos">Position.</param> /// <param name="eccentcity">Eccentcity.</param> /// <param name="semiMajorAxis">Semi major axis.</param> /// <param name="trueAnomaly">True anomaly.</param> public static double HeadingFromPeriaps(Vector3 pos, double eccentcity, double semiMajorAxis, double trueAnomaly) { double r = pos.Length(); double a = semiMajorAxis; double e = eccentcity; double k = r / a; double f = trueAnomaly; double bar = ((2 - 2 * e * e) / (k * (2 - k))) - 1; double foo = GMath.Clamp(bar, -1, 1); double alpha = Math.Acos(foo); if (trueAnomaly > Math.PI || trueAnomaly < 0) { alpha = -alpha; } double heading = f + ((Math.PI - alpha) / 2); return(heading); }
private void MineResources(Entity colonyEntity) { Dictionary <Guid, int> mineRates = colonyEntity.GetDataBlob <MiningDB>().MineingRate; Dictionary <Guid, MineralDepositInfo> planetMinerals = colonyEntity.GetDataBlob <ColonyInfoDB>().PlanetEntity.GetDataBlob <SystemBodyInfoDB>().Minerals; CargoStorageDB stockpile = colonyEntity.GetDataBlob <CargoStorageDB>(); float mineBonuses = 1;//colonyEntity.GetDataBlob<ColonyBonusesDB>().GetBonus(AbilityType.Mine); foreach (var kvp in mineRates) { ICargoable mineral = _minerals[kvp.Key]; Guid cargoTypeID = mineral.CargoTypeID; int itemMassPerUnit = mineral.Mass; double accessability = planetMinerals[kvp.Key].Accessibility; double actualRate = kvp.Value * mineBonuses * accessability; int amountMinableThisTick = (int)Math.Min(actualRate, planetMinerals[kvp.Key].Amount); long freeCapacity = stockpile.StoredCargoTypes[mineral.CargoTypeID].FreeCapacity; long weightMinableThisTick = itemMassPerUnit * amountMinableThisTick; weightMinableThisTick = Math.Min(weightMinableThisTick, freeCapacity); int actualAmountToMineThisTick = (int)(weightMinableThisTick / itemMassPerUnit); //get the number of items from the mass transferable long actualweightMinaedThisTick = actualAmountToMineThisTick * itemMassPerUnit; StorageSpaceProcessor.AddCargo(stockpile, mineral, actualAmountToMineThisTick); MineralDepositInfo mineralDeposit = planetMinerals[kvp.Key]; int newAmount = mineralDeposit.Amount -= actualAmountToMineThisTick; accessability = Math.Pow((float)mineralDeposit.Amount / mineralDeposit.HalfOriginalAmount, 3) * mineralDeposit.Accessibility; double newAccess = GMath.Clamp(accessability, 0.1, mineralDeposit.Accessibility); mineralDeposit.Amount = newAmount; mineralDeposit.Accessibility = newAccess; } }
/// <summary> /// In calculation this is referred to as RAAN or LoAN or Ω /// </summary> /// <param name="nodeVector">The node vector of the Kepler elements</param> /// <returns>Radians as a double</returns> public static double CalculateLongitudeOfAscendingNode(Vector3 nodeVector) { double longitudeOfAscendingNodeLength = nodeVector.X / nodeVector.Length(); if (double.IsNaN(longitudeOfAscendingNodeLength)) { longitudeOfAscendingNodeLength = 0; } else { longitudeOfAscendingNodeLength = GMath.Clamp(longitudeOfAscendingNodeLength, -1, 1); } double longitudeOfAscendingNode = 0; if (longitudeOfAscendingNodeLength != 0) { longitudeOfAscendingNode = Math.Acos(longitudeOfAscendingNodeLength); } return(longitudeOfAscendingNode); }
/// <summary> /// Heading on the orbital plane. /// </summary> /// <returns>The from periaps.</returns> /// <param name="pos">Position.</param> /// <param name="eccentricity">Eccentricity.</param> /// <param name="semiMajAxis">Semi major axis.</param> /// <param name="trueAnomaly">True anomaly.</param> /// <param name="aoP">Argument Of Periapsis</param> /// public static double ObjectLocalHeading(Vector3 pos, double eccentricity, double semiMajAxis, double trueAnomaly, double aoP) { double r = pos.Length(); double a = semiMajAxis; double e = eccentricity; double k = r / a; double f = trueAnomaly; double bar = ((2 - 2 * e * e) / (k * (2 - k))) - 1; double foo = GMath.Clamp(bar, -1, 1); double alpha = Math.Acos(foo); if (trueAnomaly > Math.PI || trueAnomaly < 0) { alpha = -alpha; } double heading = ((Math.PI - alpha) / 2) + f; heading += aoP; Angle.NormaliseRadiansPositive(heading); return(heading); }
public static Entity CreateJumpPoint(StarSystemFactory ssf, StarSystem system) { var primaryStarInfoDB = system.GetFirstEntityWithDataBlob <StarInfoDB>().GetDataBlob <OrbitDB>().Root.GetDataBlob <StarInfoDB>(); NameDB jpNameDB = new NameDB("Jump Point"); PositionDB jpPositionDB = new PositionDB(0, 0, 0, system.Guid); TransitableDB jpTransitableDB = new TransitableDB(); jpTransitableDB.IsStabilized = system.Game.Settings.AllJumpPointsStabilized ?? false; if (!jpTransitableDB.IsStabilized) { // TODO: Introduce a random chance to stablize jumppoints. } var jpPositionLimits = new MinMaxStruct(ssf.GalaxyGen.Settings.OrbitalDistanceByStarSpectralType[primaryStarInfoDB.SpectralType].Min, ssf.GalaxyGen.Settings.OrbitalDistanceByStarSpectralType[primaryStarInfoDB.SpectralType].Max); jpPositionDB.X_AU = GMath.SelectFromRange(jpPositionLimits, system.RNG.NextDouble()); jpPositionDB.Y_AU = GMath.SelectFromRange(jpPositionLimits, system.RNG.NextDouble()); // Randomly flip the position sign to allow negative values. if (system.RNG.Next(0, 100) < 50) { jpPositionDB.X_AU = 0 - jpPositionDB.X_AU; } if (system.RNG.Next(0, 100) < 50) { jpPositionDB.Y_AU = 0 - jpPositionDB.Y_AU; } var dataBlobs = new List <BaseDataBlob> { jpNameDB, jpTransitableDB, jpPositionDB }; Entity jumpPoint = Entity.Create(system, Guid.Empty, dataBlobs); return(jumpPoint); }
/// <summary> /// Creates on Orbit at current location from a given velocity /// </summary> /// <returns>The Orbit Does not attach the OrbitDB to the entity!</returns> /// <param name="parent">Parent. must have massdb</param> /// <param name="entity">Entity. must have massdb</param> /// <param name="velocity_m">Velocity in meters.</param> public static OrbitDB FromVelocity_m(Entity parent, Entity entity, Vector3 velocity_m, DateTime atDateTime) { var parentMass = parent.GetDataBlob <MassVolumeDB>().Mass; var myMass = entity.GetDataBlob <MassVolumeDB>().Mass; //var epoch1 = parent.Manager.ManagerSubpulses.StarSysDateTime; //getting epoch from here is incorrect as the local datetime doesn't change till after the subpulse. //var parentPos = OrbitProcessor.GetAbsolutePosition_AU(parent.GetDataBlob<OrbitDB>(), atDateTime); //need to use the parent position at the epoch var posdb = entity.GetDataBlob <PositionDB>(); posdb.SetParent(parent); var ralitivePos = posdb.RelativePosition_m;//entity.GetDataBlob<PositionDB>().AbsolutePosition_AU - parentPos; if (ralitivePos.Length() > OrbitProcessor.GetSOI_m(parent)) { throw new Exception("Entity not in target SOI"); } //var sgp = GameConstants.Science.GravitationalConstant * (myMass + parentMass) / 3.347928976e33; var sgp_m = GMath.StandardGravitationalParameter(myMass + parentMass); var ke_m = OrbitMath.KeplerFromPositionAndVelocity(sgp_m, ralitivePos, velocity_m, atDateTime); OrbitDB orbit = new OrbitDB(parent) { SemiMajorAxis = ke_m.SemiMajorAxis, Eccentricity = ke_m.Eccentricity, Inclination = ke_m.Inclination, LongitudeOfAscendingNode = ke_m.LoAN, ArgumentOfPeriapsis = ke_m.AoP, MeanAnomalyAtEpoch = ke_m.MeanAnomalyAtEpoch, Epoch = atDateTime, _parentMass = parentMass, _myMass = myMass }; orbit.CalculateExtendedParameters(); var pos = OrbitProcessor.GetPosition_m(orbit, atDateTime); var d = (pos - ralitivePos).Length(); if (d > 1) { var e = new Event(atDateTime, "Positional difference of " + Stringify.Distance(d) + " when creating orbit from velocity"); e.Entity = entity; e.SystemGuid = entity.Manager.ManagerGuid; e.EventType = EventType.Opps; //e.Faction = entity.FactionOwner; StaticRefLib.EventLog.AddEvent(e); //other info: var keta = Angle.ToDegrees(ke_m.TrueAnomalyAtEpoch); var obta = Angle.ToDegrees(OrbitProcessor.GetTrueAnomaly(orbit, atDateTime)); var tadif = Angle.ToDegrees(Angle.DifferenceBetweenRadians(keta, obta)); var pos1 = OrbitProcessor.GetPosition_m(orbit, atDateTime); var pos2 = OrbitProcessor.GetPosition_m(orbit, ke_m.TrueAnomalyAtEpoch); var d2 = (pos1 - pos2).Length(); } return(orbit); }
/// <summary> /// Generates an entire group of stars for a starSystem. /// </summary> /// <remarks> /// Stars created with this method are sorted by mass. /// Stars created with this method are added to the newSystem's EntityManager. /// </remarks> /// <param name="system">The Star System the new stars belongs to.</param> /// <param name="numStars">The number of stars to create.</param> /// <returns>A mass-sorted list of entity ID's for the generated stars.</returns> public List <Entity> CreateStarsForSystem(StarSystem system, int numStars, DateTime currentDateTime) { // Argument Validation. if (system == null) { throw new ArgumentNullException("system"); } if (numStars <= 0) { throw new ArgumentOutOfRangeException("numStars", "numStars must be greater than 0."); } // List of stars we'll be creating. var stars = new List <Entity>(); while (stars.Count < numStars) { // Generate a SpectralType for the star. SpectralType starType; if (_galaxyGen.Settings.RealStarSystems) { starType = _galaxyGen.Settings.StarTypeDistributionForRealStars.Select(system.RNG.NextDouble()); } else { starType = _galaxyGen.Settings.StarTypeDistributionForFakeStars.Select(system.RNG.NextDouble()); } // We will use the one random number to select from all the spectral type ranges. Should give us saner numbers for stars. double randomSelection = system.RNG.NextDouble(); // Generate the star's datablobs. MassVolumeDB starMVDB = MassVolumeDB.NewFromMassAndRadius( GMath.SelectFromRange(_galaxyGen.Settings.StarMassBySpectralType[starType], randomSelection), GMath.SelectFromRange(_galaxyGen.Settings.StarRadiusBySpectralType[starType], randomSelection)); StarInfoDB starData = GenerateStarInfo(starMVDB, starType, randomSelection); // Initialize Position as 0,0,0. It will be updated when the star's orbit is calculated. PositionDB positionData = new PositionDB(0, 0, 0, system.Guid); var baseDataBlobs = new List <BaseDataBlob> { starMVDB, starData, positionData }; stars.Add(Entity.Create(system, Guid.Empty, baseDataBlobs)); } // The root star must be the most massive. Find it. Entity rootStar = stars[0]; double rootStarMass = rootStar.GetDataBlob <MassVolumeDB>().Mass; foreach (Entity currentStar in stars) { double currentStarMass = currentStar.GetDataBlob <MassVolumeDB>().Mass; if (rootStarMass < currentStarMass) { rootStar = currentStar; rootStarMass = rootStar.GetDataBlob <MassVolumeDB>().Mass; } } // Swap the root star to index 0. int rootIndex = stars.IndexOf(rootStar); Entity displacedStar = stars[0]; stars[rootIndex] = displacedStar; stars[0] = rootStar; // Generate orbits. Entity anchorStar = stars[0]; MassVolumeDB anchorMVDB = anchorStar.GetDataBlob <MassVolumeDB>(); Entity previousStar = stars[0]; previousStar.SetDataBlob(new OrbitDB()); int starIndex = 0; foreach (Entity currentStar in stars) { StarInfoDB currentStarInfo = currentStar.GetDataBlob <StarInfoDB>(); NameDB currentStarNameDB = new NameDB(system.NameDB.DefaultName + " " + (char)('A' + starIndex) + " " + currentStarInfo.SpectralType + currentStarInfo.SpectralSubDivision + currentStarInfo.LuminosityClass); currentStar.SetDataBlob(currentStarNameDB); if (previousStar == currentStar) { // This is the "Anchor Star" continue; } OrbitDB previousOrbit = previousStar.GetDataBlob <OrbitDB>(); StarInfoDB previousStarInfo = previousStar.GetDataBlob <StarInfoDB>(); double minDistance = _galaxyGen.Settings.OrbitalDistanceByStarSpectralType[previousStarInfo.SpectralType].Max + _galaxyGen.Settings.OrbitalDistanceByStarSpectralType[currentStarInfo.SpectralType].Max + previousOrbit.SemiMajorAxis; double sma = minDistance * Math.Pow(system.RNG.NextDouble(), 3); double eccentricity = Math.Pow(system.RNG.NextDouble() * 0.8, 3); OrbitDB currentOrbit = OrbitDB.FromAsteroidFormat(anchorStar, anchorMVDB.Mass, currentStar.GetDataBlob <MassVolumeDB>().Mass, sma, eccentricity, _galaxyGen.Settings.MaxBodyInclination * system.RNG.NextDouble(), system.RNG.NextDouble() * 360, system.RNG.NextDouble() * 360, system.RNG.NextDouble() * 360, currentDateTime); currentStar.SetDataBlob(currentOrbit); currentStar.GetDataBlob <PositionDB>().SetParent(currentOrbit.Parent); previousStar = currentStar; starIndex++; } return(stars); }
/// <summary> /// This was designed so that fast moving objects will get interpolated a lot more than slow moving objects /// so fast moving objects shouldn't loose positional acuracy when close to a planet, /// and slow moving objects won't have processor time wasted on them by calulcating too often. /// However this seems to be unstable and looses energy, unsure why. currently set it to just itterate/interpolate every second. /// so currently will be using more time to get through this than neccisary. /// </summary> /// <param name="entity">Entity.</param> /// <param name="deltaSeconds">Delta seconds.</param> public static void NewtonMove(Entity entity, int deltaSeconds) { NewtonMoveDB newtonMoveDB = entity.GetDataBlob <NewtonMoveDB>(); NewtonThrustAbilityDB newtonThrust = entity.GetDataBlob <NewtonThrustAbilityDB>(); PositionDB positionDB = entity.GetDataBlob <PositionDB>(); double mass_Kg = entity.GetDataBlob <MassVolumeDB>().Mass; double parentMass_kg = newtonMoveDB.ParentMass; var manager = entity.Manager; DateTime dateTimeFrom = newtonMoveDB.LastProcessDateTime; DateTime dateTimeNow = manager.ManagerSubpulses.StarSysDateTime; DateTime dateTimeFuture = dateTimeNow + TimeSpan.FromSeconds(deltaSeconds); double deltaT = (dateTimeFuture - dateTimeFrom).TotalSeconds; double secondsToItterate = deltaT; while (secondsToItterate > 0) { //double timeStep = Math.Max(secondsToItterate / speed_kms, 1); //timeStep = Math.Min(timeStep, secondsToItterate); double timeStepInSeconds = 1;//because the above seems unstable and looses energy. double distanceToParent_m = positionDB.GetDistanceTo_m(newtonMoveDB.SOIParent.GetDataBlob <PositionDB>()); distanceToParent_m = Math.Max(distanceToParent_m, 0.1); //don't let the distance be 0 (once collision is in this will likely never happen anyway) double gravForce = GameConstants.Science.GravitationalConstant * (mass_Kg * parentMass_kg / Math.Pow(distanceToParent_m, 2)); Vector3 gravForceVector = gravForce * -Vector3.Normalise(positionDB.RelativePosition_m); Vector3 totalDVFromGrav = (gravForceVector / mass_Kg) * timeStepInSeconds; double maxAccelFromThrust1 = newtonThrust.ExhaustVelocity * Math.Log(mass_Kg / (mass_Kg - newtonThrust.FuelBurnRate)); //per second double maxAccelFromThrust = newtonThrust.ThrustInNewtons / mass_Kg; //per second Vector3 manuverDV = newtonMoveDB.DeltaVForManuver_m; //how much dv needed to complete the manuver. double dryMass = mass_Kg - newtonThrust.FuelBurnRate * timeStepInSeconds; //how much our ship weighs after a timestep of fuel is used. //how much dv can we get in this timestep. double deltaVThisStep = OrbitMath.TsiolkovskyRocketEquation(mass_Kg, dryMass, newtonThrust.ExhaustVelocity); deltaVThisStep = Math.Min(manuverDV.Length(), deltaVThisStep); //don't use more Dv than what is called for. deltaVThisStep = Math.Min(newtonThrust.DeltaV, deltaVThisStep); //check we've got the deltaV to spend. Vector3 totalDVFromThrust = Vector3.Normalise(manuverDV) * deltaVThisStep; //remove the deltaV we're expending from the max (TODO: Remove fuel from cargo, change mass of ship) newtonThrust.DeltaV -= deltaVThisStep; //remove the vectorDV from the amount needed to fully complete the manuver. newtonMoveDB.DeltaVForManuver_m -= totalDVFromThrust; Vector3 totalDV = totalDVFromGrav + totalDVFromThrust; Vector3 newVelocity = totalDV + newtonMoveDB.CurrentVector_ms; newtonMoveDB.CurrentVector_ms = newVelocity; Vector3 deltaPos = (newtonMoveDB.CurrentVector_ms + newVelocity) / 2 * timeStepInSeconds; positionDB.RelativePosition_m += deltaPos; double sOIRadius = OrbitProcessor.GetSOI_m(newtonMoveDB.SOIParent); if (positionDB.RelativePosition_m.Length() >= sOIRadius) { Entity newParent; Vector3 parentRalitiveVector; //if our parent is a regular kepler object (normaly this is the case) if (newtonMoveDB.SOIParent.HasDataBlob <OrbitDB>()) { var orbitDB = newtonMoveDB.SOIParent.GetDataBlob <OrbitDB>(); newParent = orbitDB.Parent; var parentVelocity = OrbitProcessor.InstantaneousOrbitalVelocityVector_m(orbitDB, entity.StarSysDateTime); parentRalitiveVector = newtonMoveDB.CurrentVector_ms + parentVelocity; } else //if (newtonMoveDB.SOIParent.HasDataBlob<NewtonMoveDB>()) { //this will pretty much never happen. newParent = newtonMoveDB.SOIParent.GetDataBlob <NewtonMoveDB>().SOIParent; var parentVelocity = newtonMoveDB.SOIParent.GetDataBlob <NewtonMoveDB>().CurrentVector_ms; parentRalitiveVector = newtonMoveDB.CurrentVector_ms + parentVelocity; } parentMass_kg = newParent.GetDataBlob <MassVolumeDB>().Mass; Vector3 posRalitiveToNewParent = positionDB.AbsolutePosition_m - newParent.GetDataBlob <PositionDB>().AbsolutePosition_m; var dateTime = dateTimeNow + TimeSpan.FromSeconds(deltaSeconds - secondsToItterate); double sgp = GMath.StandardGravitationalParameter(parentMass_kg + mass_Kg); var kE = OrbitMath.KeplerFromPositionAndVelocity(sgp, posRalitiveToNewParent, parentRalitiveVector, dateTime); positionDB.SetParent(newParent); newtonMoveDB.ParentMass = parentMass_kg; newtonMoveDB.SOIParent = newParent; newtonMoveDB.CurrentVector_ms = parentRalitiveVector; } if (newtonMoveDB.DeltaVForManuver_m.Length() <= 0) //if we've completed the manuver. { var dateTime = dateTimeNow + TimeSpan.FromSeconds(deltaSeconds - secondsToItterate); double sgp = GMath.StandardGravitationalParameter(parentMass_kg + mass_Kg); KeplerElements kE = OrbitMath.KeplerFromPositionAndVelocity(sgp, positionDB.RelativePosition_m, newtonMoveDB.CurrentVector_ms, dateTime); var parentEntity = Entity.GetSOIParentEntity(entity, positionDB); if (kE.Eccentricity < 1) //if we're going to end up in a regular orbit around our new parent { var newOrbit = OrbitDB.FromKeplerElements( parentEntity, mass_Kg, kE, dateTime); entity.RemoveDataBlob <NewtonMoveDB>(); entity.SetDataBlob(newOrbit); positionDB.SetParent(parentEntity); var newPos = OrbitProcessor.GetPosition_m(newOrbit, dateTime); positionDB.RelativePosition_m = newPos; } break; } secondsToItterate -= timeStepInSeconds; } newtonMoveDB.LastProcessDateTime = dateTimeFuture; }
/// <summary> /// Kepler elements from velocity and position. /// </summary> /// <returns>a struct of Kepler elements.</returns> /// <param name="standardGravParam">Standard grav parameter.</param> /// <param name="position">Position ralitive to parent</param> /// <param name="velocity">Velocity ralitive to parent</param> public static KeplerElements KeplerFromPositionAndVelocity(double standardGravParam, Vector3 position, Vector3 velocity, DateTime epoch) { KeplerElements ke = new KeplerElements(); Vector3 angularVelocity = Vector3.Cross(position, velocity); Vector3 nodeVector = Vector3.Cross(new Vector3(0, 0, 1), angularVelocity); Vector3 eccentVector = EccentricityVector(standardGravParam, position, velocity); //Vector4 eccentVector2 = EccentricityVector2(standardGravParam, position, velocity); double eccentricity = eccentVector.Length(); double specificOrbitalEnergy = Math.Pow(velocity.Length(), 2) * 0.5 - standardGravParam / position.Length(); double semiMajorAxis; double p; //p is where the ellipse or hypobola crosses a line from the focal point 90 degrees from the sma if (Math.Abs(eccentricity) > 1) //hypobola { semiMajorAxis = -(-standardGravParam / (2 * specificOrbitalEnergy)); //in this case the sma is negitive p = semiMajorAxis * (1 - eccentricity * eccentricity); } else if (Math.Abs(eccentricity) < 1) //ellipse { semiMajorAxis = -standardGravParam / (2 * specificOrbitalEnergy); p = semiMajorAxis * (1 - eccentricity * eccentricity); } else //parabola { p = angularVelocity.Length() * angularVelocity.Length() / standardGravParam; semiMajorAxis = double.MaxValue; } /* * if (Math.Abs(eccentricity - 1.0) > 1e-15) * { * semiMajorAxis = -standardGravParam / (2 * specificOrbitalEnergy); * p = semiMajorAxis * (1 - eccentricity * eccentricity); * } * else //parabola * { * p = angularVelocity.Length() * angularVelocity.Length() / standardGravParam; * semiMajorAxis = double.MaxValue; * } */ double semiMinorAxis = EllipseMath.SemiMinorAxis(semiMajorAxis, eccentricity); double linierEccentricity = eccentricity * semiMajorAxis; double inclination = Math.Acos(angularVelocity.Z / angularVelocity.Length()); //should be 0 in 2d. or pi if counter clockwise orbit. if (double.IsNaN(inclination)) { inclination = 0; } double loANlen = nodeVector.X / nodeVector.Length(); double longdOfAN = 0; if (double.IsNaN(loANlen)) { loANlen = 0; } else { loANlen = GMath.Clamp(loANlen, -1, 1); } if (loANlen != 0) { longdOfAN = Math.Acos(loANlen); //RAAN or LoAN or Ω } double eccentricAnomoly = GetEccentricAnomalyFromStateVectors2(standardGravParam, semiMajorAxis, position, velocity); double trueAnomaly = TrueAnomalyFromEccentricAnomaly(eccentricity, eccentricAnomoly); double argOfPeriaps = ArgumentOfPeriapsis2(position, inclination, longdOfAN, trueAnomaly); var meanMotion = Math.Sqrt(standardGravParam / Math.Pow(semiMajorAxis, 3)); var meanAnomaly = eccentricAnomoly - eccentricity * Math.Sin(eccentricAnomoly); ke.SemiMajorAxis = semiMajorAxis; ke.SemiMinorAxis = semiMinorAxis; ke.Eccentricity = eccentricity; ke.Apoapsis = EllipseMath.Apoapsis(eccentricity, semiMajorAxis); ke.Periapsis = EllipseMath.Periapsis(eccentricity, semiMajorAxis); ke.LinierEccentricity = EllipseMath.LinierEccentricity(ke.Apoapsis, semiMajorAxis); ke.LoAN = longdOfAN; ke.AoP = argOfPeriaps; ke.Inclination = inclination; ke.MeanMotion = meanMotion; ke.MeanAnomalyAtEpoch = meanAnomaly; ke.TrueAnomalyAtEpoch = trueAnomaly; ke.Epoch = epoch; //TimeFromPeriapsis(semiMajorAxis, standardGravParam, meanAnomaly); //Epoch(semiMajorAxis, semiMinorAxis, eccentricAnomoly, OrbitalPeriod(standardGravParam, semiMajorAxis)); return(ke); }
public void ProcessEntity(Entity entity, int deltaSeconds) { var manager = entity.Manager; var moveDB = entity.GetDataBlob <TranslateMoveDB>(); var propulsionDB = entity.GetDataBlob <PropulsionDB>(); //var currentVector = propulsionDB.CurrentSpeed; var maxSpeed = propulsionDB.MaximumSpeed; var positionDB = entity.GetDataBlob <PositionDB>(); var currentPosition = positionDB.AbsolutePosition; //targetPosition taking the range (how close we want to get) into account. var targetPos = moveDB.TargetPosition * (1 - (moveDB.MoveRangeInKM / GameConstants.Units.KmPerAu) / moveDB.TargetPosition.Length()); var deltaVecToTarget = currentPosition - targetPos; var currentSpeed = GMath.GetVector(currentPosition, targetPos, maxSpeed); propulsionDB.CurrentVector = currentSpeed; moveDB.CurrentVector = currentSpeed; StaticDataStore staticData = entity.Manager.Game.StaticData; CargoStorageDB storedResources = entity.GetDataBlob <CargoStorageDB>(); Dictionary <Guid, double> fuelUsePerMeter = propulsionDB.FuelUsePerKM; double maxKMeters = ShipMovementProcessor.CalcMaxFuelDistance(entity); var nextTPos = currentPosition + (currentSpeed * deltaSeconds); var distanceToTargetAU = deltaVecToTarget.Length(); //in au var deltaVecToNextT = currentPosition - nextTPos; var fuelMaxDistanceAU = maxKMeters / GameConstants.Units.KmPerAu; Vector4 newPos = currentPosition; double distanceToNextTPos = deltaVecToNextT.Length(); double distanceToMove; if (fuelMaxDistanceAU < distanceToNextTPos) { distanceToMove = fuelMaxDistanceAU; double percent = fuelMaxDistanceAU / distanceToNextTPos; newPos = nextTPos + deltaVecToNextT * percent; Event usedAllFuel = new Event(manager.ManagerSubpulses.SystemLocalDateTime, "Used all Fuel", entity.GetDataBlob <OwnedDB>().OwnedByFaction, entity); usedAllFuel.EventType = EventType.FuelExhausted; manager.Game.EventLog.AddEvent(usedAllFuel); } else { distanceToMove = distanceToNextTPos; newPos = nextTPos; } if (distanceToTargetAU < distanceToMove) // moving would overtake target, just go directly to target { distanceToMove = distanceToTargetAU; propulsionDB.CurrentVector = new Vector4(0, 0, 0, 0); newPos = targetPos; moveDB.IsAtTarget = true; entity.RemoveDataBlob <TranslateMoveDB>(); } positionDB.AbsolutePosition = newPos; int kMetersMoved = (int)(distanceToMove * GameConstants.Units.KmPerAu); foreach (var item in propulsionDB.FuelUsePerKM) { var fuel = staticData.GetICargoable(item.Key); StorageSpaceProcessor.RemoveCargo(storedResources, fuel, (long)(item.Value * kMetersMoved)); } }