269 lines
9.4 KiB
C++
269 lines
9.4 KiB
C++
// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#include "Planet.h"
|
|
|
|
#include "Color.h"
|
|
#include "GeoSphere.h"
|
|
#include "galaxy/SystemBody.h"
|
|
#include "graphics/Graphics.h"
|
|
#include "graphics/Material.h"
|
|
#include "graphics/RenderState.h"
|
|
#include "graphics/Renderer.h"
|
|
#include "graphics/Texture.h"
|
|
#include "perlin.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#include "win32/WinMath.h"
|
|
#endif // _MSC_VER
|
|
|
|
using namespace Graphics;
|
|
|
|
static const Graphics::AttributeSet RING_VERTEX_ATTRIBS = Graphics::ATTRIB_POSITION | Graphics::ATTRIB_UV0;
|
|
|
|
Planet::Planet(SystemBody *sbody) :
|
|
TerrainBody(sbody),
|
|
m_ringVertices(RING_VERTEX_ATTRIBS),
|
|
m_ringState(nullptr)
|
|
{
|
|
InitParams(sbody);
|
|
}
|
|
|
|
Planet::Planet(const Json &jsonObj, Space *space) :
|
|
TerrainBody(jsonObj, space),
|
|
m_ringVertices(RING_VERTEX_ATTRIBS),
|
|
m_ringState(nullptr)
|
|
{
|
|
const SystemBody *sbody = GetSystemBody();
|
|
assert(sbody);
|
|
InitParams(sbody);
|
|
}
|
|
|
|
void Planet::InitParams(const SystemBody *sbody)
|
|
{
|
|
double specificHeatCp;
|
|
double gasMolarMass;
|
|
if (sbody->GetSuperType() == SystemBody::SUPERTYPE_GAS_GIANT) {
|
|
specificHeatCp = 12950.0; // constant pressure specific heat, for a combination of hydrogen and helium
|
|
gasMolarMass = 0.0023139903;
|
|
} else {
|
|
specificHeatCp = 1000.5; // constant pressure specific heat, for the combination of gasses that make up air
|
|
// XXX using earth's molar mass of air...
|
|
gasMolarMass = 0.02897;
|
|
}
|
|
const double GAS_CONSTANT = 8.3144621;
|
|
const double PA_2_ATMOS = 1.0 / 101325.0;
|
|
|
|
// surface gravity = G*M/planet radius^2
|
|
m_surfaceGravity_g = G * sbody->GetMass() / (sbody->GetRadius() * sbody->GetRadius());
|
|
const double lapseRate_L = m_surfaceGravity_g / specificHeatCp; // deg/m
|
|
const double surfaceTemperature_T0 = sbody->GetAverageTemp(); //K
|
|
|
|
double surfaceDensity, h;
|
|
Color c;
|
|
sbody->GetAtmosphereFlavor(&c, &surfaceDensity); // kg / m^3
|
|
surfaceDensity /= gasMolarMass; // convert to moles/m^3
|
|
|
|
//P = density*R*T=(n/V)*R*T
|
|
const double surfaceP_p0 = PA_2_ATMOS * ((surfaceDensity)*GAS_CONSTANT * surfaceTemperature_T0); // in atmospheres
|
|
if (surfaceP_p0 < 0.002)
|
|
h = 0;
|
|
else {
|
|
//*outPressure = p0*(1-l*h/T0)^(g*M/(R*L);
|
|
// want height for pressure 0.001 atm:
|
|
// h = (1 - exp(RL/gM * log(P/p0))) * T0 / l
|
|
double RLdivgM = (GAS_CONSTANT * lapseRate_L) / (m_surfaceGravity_g * gasMolarMass);
|
|
h = (1.0 - exp(RLdivgM * log(0.001 / surfaceP_p0))) * surfaceTemperature_T0 / lapseRate_L;
|
|
// double h2 = (1.0 - pow(0.001/surfaceP_p0, RLdivgM)) * surfaceTemperature_T0 / lapseRate_L;
|
|
// double P = surfaceP_p0*pow((1.0-lapseRate_L*h/surfaceTemperature_T0),1/RLdivgM);
|
|
}
|
|
m_atmosphereRadius = h + sbody->GetRadius();
|
|
|
|
SetPhysRadius(std::max(m_atmosphereRadius, GetMaxFeatureRadius() + 1000));
|
|
// NB: Below abandoned due to docking problems with low altitude orbiting space stations
|
|
// SetPhysRadius(std::max(m_atmosphereRadius, std::max(GetMaxFeatureRadius() * 2.0 + 2000, sbody->GetRadius() * 1.05)));
|
|
if (sbody->HasRings()) {
|
|
SetClipRadius(sbody->GetRadius() * sbody->GetRings().maxRadius.ToDouble());
|
|
} else {
|
|
SetClipRadius(GetPhysRadius());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* dist = distance from centre
|
|
* returns pressure in earth atmospheres
|
|
* function is slightly different from the isothermal earth-based approximation used in shaders,
|
|
* but it isn't visually noticeable.
|
|
*/
|
|
void Planet::GetAtmosphericState(double dist, double *outPressure, double *outDensity) const
|
|
{
|
|
PROFILE_SCOPED()
|
|
#if 0
|
|
static bool atmosphereTableShown = false;
|
|
if (!atmosphereTableShown) {
|
|
atmosphereTableShown = true;
|
|
for (double h = -1000; h <= 100000; h = h+1000.0) {
|
|
double p = 0.0, d = 0.0;
|
|
GetAtmosphericState(h+this->GetSystemBody()->GetRadius(),&p,&d);
|
|
Output("height(m): %f, pressure(hpa): %f, density: %f\n", h, p*101325.0/100.0, d);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// This model has no atmosphere beyond the adiabetic limit
|
|
// Note: some code duplicated in InitParams(). Check if changing.
|
|
if (dist >= m_atmosphereRadius) {
|
|
*outDensity = 0.0;
|
|
*outPressure = 0.0;
|
|
return;
|
|
}
|
|
|
|
double surfaceDensity;
|
|
double specificHeatCp;
|
|
double gasMolarMass;
|
|
const SystemBody *sbody = this->GetSystemBody();
|
|
if (sbody->GetSuperType() == SystemBody::SUPERTYPE_GAS_GIANT) {
|
|
specificHeatCp = 12950.0; // constant pressure specific heat, for a combination of hydrogen and helium
|
|
gasMolarMass = 0.0023139903;
|
|
} else {
|
|
specificHeatCp = 1000.5; // constant pressure specific heat, for the combination of gasses that make up air
|
|
// XXX using earth's molar mass of air...
|
|
gasMolarMass = 0.02897;
|
|
}
|
|
const double GAS_CONSTANT = 8.3144621;
|
|
const double PA_2_ATMOS = 1.0 / 101325.0;
|
|
|
|
// lapse rate http://en.wikipedia.org/wiki/Adiabatic_lapse_rate#Dry_adiabatic_lapse_rate
|
|
// the wet adiabatic rate can be used when cloud layers are incorporated
|
|
// fairly accurate in the troposphere
|
|
const double lapseRate_L = m_surfaceGravity_g / specificHeatCp; // deg/m
|
|
|
|
const double height_h = (dist - sbody->GetRadius()); // height in m
|
|
const double surfaceTemperature_T0 = sbody->GetAverageTemp(); //K
|
|
|
|
Color c;
|
|
sbody->GetAtmosphereFlavor(&c, &surfaceDensity); // kg / m^3
|
|
// convert to moles/m^3
|
|
surfaceDensity /= gasMolarMass;
|
|
|
|
//P = density*R*T=(n/V)*R*T
|
|
const double surfaceP_p0 = PA_2_ATMOS * ((surfaceDensity)*GAS_CONSTANT * surfaceTemperature_T0); // in atmospheres
|
|
|
|
// height below zero should not occur
|
|
if (height_h < 0.0) {
|
|
*outPressure = surfaceP_p0;
|
|
*outDensity = surfaceDensity * gasMolarMass;
|
|
return;
|
|
}
|
|
|
|
//*outPressure = p0*(1-l*h/T0)^(g*M/(R*L);
|
|
*outPressure = surfaceP_p0 * pow((1 - lapseRate_L * height_h / surfaceTemperature_T0), (m_surfaceGravity_g * gasMolarMass / (GAS_CONSTANT * lapseRate_L))); // in ATM since p0 was in ATM
|
|
// ^^g used is abs(g)
|
|
// temperature at height
|
|
double temp = surfaceTemperature_T0 - lapseRate_L * height_h;
|
|
|
|
*outDensity = (*outPressure / (PA_2_ATMOS * GAS_CONSTANT * temp)) * gasMolarMass;
|
|
}
|
|
|
|
void Planet::GenerateRings(Graphics::Renderer *renderer)
|
|
{
|
|
const SystemBody *sbody = GetSystemBody();
|
|
|
|
m_ringVertices.Clear();
|
|
|
|
// generate the ring geometry
|
|
const float inner = sbody->GetRings().minRadius.ToFloat();
|
|
const float outer = sbody->GetRings().maxRadius.ToFloat();
|
|
int segments = 200;
|
|
for (int i = 0; i <= segments; ++i) {
|
|
const float a = (2.0f * float(M_PI)) * (float(i) / float(segments));
|
|
const float ca = cosf(a);
|
|
const float sa = sinf(a);
|
|
m_ringVertices.Add(vector3f(inner * sa, 0.0f, inner * ca), vector2f(float(i), 0.0f));
|
|
m_ringVertices.Add(vector3f(outer * sa, 0.0f, outer * ca), vector2f(float(i), 1.0f));
|
|
}
|
|
|
|
// generate the ring texture
|
|
// NOTE: texture width must be > 1 to avoid graphical glitches with Intel GMA 900 systems
|
|
// this is something to do with mipmapping (probably mipmap generation going wrong)
|
|
// (if the texture is generated without mipmaps then a 1xN texture works)
|
|
const int RING_TEXTURE_WIDTH = 4;
|
|
const int RING_TEXTURE_LENGTH = 256;
|
|
std::unique_ptr<Color, FreeDeleter> buf(
|
|
static_cast<Color *>(malloc(RING_TEXTURE_WIDTH * RING_TEXTURE_LENGTH * 4)));
|
|
|
|
const float ringScale = (outer - inner) * sbody->GetRadius() / 1.5e7f;
|
|
|
|
Random rng(GetSystemBody()->GetSeed() + 4609837);
|
|
Color baseCol = sbody->GetRings().baseColor;
|
|
double noiseOffset = 2048.0 * rng.Double();
|
|
for (int i = 0; i < RING_TEXTURE_LENGTH; ++i) {
|
|
const float alpha = (float(i) / float(RING_TEXTURE_LENGTH)) * ringScale;
|
|
const float n = 0.25 +
|
|
0.60 * noise(vector3d(5.0 * alpha, noiseOffset, 0.0)) +
|
|
0.15 * noise(vector3d(10.0 * alpha, noiseOffset, 0.0));
|
|
|
|
const float LOG_SCALE = 1.0f / sqrtf(sqrtf(log1p(1.0f)));
|
|
const float v = LOG_SCALE * sqrtf(sqrtf(log1p(n)));
|
|
|
|
Color color;
|
|
color.r = v * baseCol.r;
|
|
color.g = v * baseCol.g;
|
|
color.b = v * baseCol.b;
|
|
color.a = ((v * 0.25f) + 0.75f) * baseCol.a;
|
|
|
|
Color *row = buf.get() + i * RING_TEXTURE_WIDTH;
|
|
for (int j = 0; j < RING_TEXTURE_WIDTH; ++j) {
|
|
row[j] = color;
|
|
}
|
|
}
|
|
|
|
// first and last pixel are forced to zero, to give a slightly smoother ring edge
|
|
{
|
|
Color *row;
|
|
row = buf.get();
|
|
std::fill_n(row, RING_TEXTURE_WIDTH, Color::BLACK);
|
|
row = buf.get() + (RING_TEXTURE_LENGTH - 1) * RING_TEXTURE_WIDTH;
|
|
std::fill_n(row, RING_TEXTURE_WIDTH, Color::BLACK);
|
|
}
|
|
|
|
const vector3f texSize(RING_TEXTURE_WIDTH, RING_TEXTURE_LENGTH, 0.0f);
|
|
const Graphics::TextureDescriptor texDesc(
|
|
Graphics::TEXTURE_RGBA_8888, texSize, Graphics::LINEAR_REPEAT, true, true, true, 0, Graphics::TEXTURE_2D);
|
|
|
|
m_ringTexture.Reset(renderer->CreateTexture(texDesc));
|
|
m_ringTexture->Update(
|
|
static_cast<void *>(buf.get()), texSize,
|
|
Graphics::TEXTURE_RGBA_8888);
|
|
|
|
Graphics::MaterialDescriptor desc;
|
|
desc.effect = Graphics::EFFECT_PLANETRING;
|
|
desc.lighting = true;
|
|
desc.textures = 1;
|
|
m_ringMaterial.reset(renderer->CreateMaterial(desc));
|
|
m_ringMaterial->texture0 = m_ringTexture.Get();
|
|
|
|
Graphics::RenderStateDesc rsd;
|
|
rsd.blendMode = Graphics::BLEND_ALPHA_PREMULT;
|
|
rsd.cullMode = Graphics::CULL_NONE;
|
|
m_ringState = renderer->CreateRenderState(rsd);
|
|
}
|
|
|
|
void Planet::DrawGasGiantRings(Renderer *renderer, const matrix4x4d &modelView)
|
|
{
|
|
assert(GetSystemBody()->HasRings());
|
|
|
|
if (!m_ringTexture)
|
|
GenerateRings(renderer);
|
|
|
|
renderer->SetTransform(matrix4x4f(modelView));
|
|
renderer->DrawTriangles(&m_ringVertices, m_ringState, m_ringMaterial.get(), TRIANGLE_STRIP);
|
|
}
|
|
|
|
void Planet::SubRender(Renderer *r, const matrix4x4d &viewTran, const vector3d &camPos)
|
|
{
|
|
if (GetSystemBody()->HasRings()) {
|
|
DrawGasGiantRings(r, viewTran);
|
|
}
|
|
}
|