392 lines
12 KiB
C++
392 lines
12 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 "CityOnPlanet.h"
|
|
|
|
#include "FileSystem.h"
|
|
#include "Frame.h"
|
|
#include "Game.h"
|
|
#include "ModelCache.h"
|
|
#include "Pi.h"
|
|
#include "Planet.h"
|
|
#include "SpaceStation.h"
|
|
#include "collider/Geom.h"
|
|
#include "graphics/Frustum.h"
|
|
#include "scenegraph/Animation.h"
|
|
#include "scenegraph/ModelSkin.h"
|
|
#include "scenegraph/SceneGraph.h"
|
|
|
|
static const unsigned int DEFAULT_NUM_BUILDINGS = 1000;
|
|
static const double START_SEG_SIZE = CityOnPlanet::RADIUS;
|
|
static const double START_SEG_SIZE_NO_ATMO = CityOnPlanet::RADIUS / 5.0f;
|
|
|
|
using SceneGraph::Model;
|
|
|
|
CityOnPlanet::citybuildinglist_t CityOnPlanet::s_buildingList = {
|
|
"city_building",
|
|
800,
|
|
2000,
|
|
0,
|
|
0,
|
|
};
|
|
|
|
CityOnPlanet::cityflavourdef_t CityOnPlanet::cityflavour[CITYFLAVOURS];
|
|
|
|
void CityOnPlanet::AddStaticGeomsToCollisionSpace()
|
|
{
|
|
// reset data structures
|
|
m_enabledBuildings.clear();
|
|
m_buildingCounts.resize(s_buildingList.numBuildings);
|
|
for (Uint32 i = 0; i < s_buildingList.numBuildings; i++) {
|
|
m_buildingCounts[i] = 0;
|
|
}
|
|
|
|
// Generate the new building list
|
|
int skipMask;
|
|
switch (Pi::detail.cities) {
|
|
case 0: skipMask = 0xf; break;
|
|
case 1: skipMask = 0x7; break;
|
|
case 2: skipMask = 0x3; break;
|
|
case 3: skipMask = 0x1; break;
|
|
default:
|
|
skipMask = 0;
|
|
break;
|
|
}
|
|
Uint32 numVisibleBuildings = 0;
|
|
for (unsigned int i = 0; i < m_buildings.size(); i++) {
|
|
if (!(i & skipMask)) {
|
|
++numVisibleBuildings;
|
|
}
|
|
}
|
|
|
|
// we know how many building we'll be adding, reserve space up front
|
|
m_enabledBuildings.reserve(numVisibleBuildings);
|
|
for (unsigned int i = 0; i < m_buildings.size(); i++) {
|
|
if (i & skipMask) {
|
|
} else {
|
|
Frame *f = Frame::GetFrame(m_frame);
|
|
f->AddStaticGeom(m_buildings[i].geom);
|
|
m_enabledBuildings.push_back(m_buildings[i]);
|
|
// Update building types
|
|
++(m_buildingCounts[m_buildings[i].instIndex]);
|
|
}
|
|
}
|
|
|
|
// reset the reset flag
|
|
m_detailLevel = Pi::detail.cities;
|
|
}
|
|
|
|
void CityOnPlanet::RemoveStaticGeomsFromCollisionSpace()
|
|
{
|
|
m_enabledBuildings.clear();
|
|
for (unsigned int i = 0; i < m_buildings.size(); i++) {
|
|
Frame *f = Frame::GetFrame(m_frame);
|
|
f->RemoveStaticGeom(m_buildings[i].geom);
|
|
}
|
|
}
|
|
|
|
// Get all model file names under buildings/
|
|
// This is temporary. Buildings should be defined in BuildingSet data files, or something.
|
|
//static
|
|
void CityOnPlanet::EnumerateNewBuildings(std::set<std::string> &filenames)
|
|
{
|
|
const std::string fullpath = FileSystem::JoinPathBelow("models", "buildings");
|
|
for (FileSystem::FileEnumerator files(FileSystem::gameDataFiles, fullpath, FileSystem::FileEnumerator::Recurse); !files.Finished(); files.Next()) {
|
|
const std::string &name = files.Current().GetName();
|
|
if (ends_with_ci(name, ".model")) {
|
|
filenames.insert(name.substr(0, name.length() - 6));
|
|
} else if (ends_with_ci(name, ".sgm")) {
|
|
filenames.insert(name.substr(0, name.length() - 4));
|
|
}
|
|
}
|
|
}
|
|
|
|
//static
|
|
void CityOnPlanet::LookupBuildingListModels(citybuildinglist_t *list)
|
|
{
|
|
std::vector<Model *> models;
|
|
|
|
//get test newmodels - to be replaced with building set definitions
|
|
{
|
|
std::set<std::string> filenames; // set so we get unique names
|
|
EnumerateNewBuildings(filenames);
|
|
for (auto it = filenames.begin(), itEnd = filenames.end(); it != itEnd; ++it) {
|
|
// find/load the model
|
|
Model *model = Pi::modelCache->FindModel(*it);
|
|
|
|
// good to use
|
|
models.push_back(model);
|
|
}
|
|
}
|
|
assert(!models.empty());
|
|
Output("Got %d buildings of tag %s\n", static_cast<int>(models.size()), list->modelTagName);
|
|
list->buildings = new citybuilding_t[models.size()];
|
|
list->numBuildings = models.size();
|
|
|
|
int i = 0;
|
|
for (auto m = models.begin(), itEnd = models.end(); m != itEnd; ++m, i++) {
|
|
list->buildings[i].instIndex = i;
|
|
list->buildings[i].resolvedModel = *m;
|
|
list->buildings[i].idle = (*m)->FindAnimation("idle");
|
|
list->buildings[i].collMesh = (*m)->CreateCollisionMesh();
|
|
const Aabb &aabb = list->buildings[i].collMesh->GetAabb();
|
|
const double maxx = std::max(fabs(aabb.max.x), fabs(aabb.min.x));
|
|
const double maxy = std::max(fabs(aabb.max.z), fabs(aabb.min.z));
|
|
list->buildings[i].xzradius = sqrt(maxx * maxx + maxy * maxy);
|
|
Output(" - %s: %f\n", (*m)->GetName().c_str(), list->buildings[i].xzradius);
|
|
}
|
|
Output("End of buildings.\n");
|
|
}
|
|
|
|
void CityOnPlanet::Init()
|
|
{
|
|
PROFILE_SCOPED()
|
|
/* Resolve city model numbers since it is a bit expensive */
|
|
LookupBuildingListModels(&s_buildingList);
|
|
}
|
|
|
|
void CityOnPlanet::Uninit()
|
|
{
|
|
delete[] s_buildingList.buildings;
|
|
}
|
|
|
|
// Need a reliable way to sort the models rather than using their address in memory we use their name which should be unique.
|
|
bool setcomp(SceneGraph::Model *mlhs, SceneGraph::Model *mrhs) { return mlhs->GetName() < mrhs->GetName(); }
|
|
bool (*fn_pt)(SceneGraph::Model *mlhs, SceneGraph::Model *mrhs) = setcomp;
|
|
|
|
struct ModelNameComparator {
|
|
bool operator()(const SceneGraph::Model *lhs, const SceneGraph::Model *rhs) const
|
|
{
|
|
return lhs->GetName() < rhs->GetName();
|
|
}
|
|
};
|
|
|
|
//static
|
|
void CityOnPlanet::SetCityModelPatterns(const SystemPath &path)
|
|
{
|
|
Uint32 _init[5] = { path.systemIndex, Uint32(path.sectorX), Uint32(path.sectorY), Uint32(path.sectorZ), UNIVERSE_SEED };
|
|
Random rand(_init, 5);
|
|
|
|
typedef std::set<SceneGraph::Model *, ModelNameComparator> ModelSet;
|
|
typedef ModelSet::iterator TSetIter;
|
|
ModelSet modelSet;
|
|
{
|
|
for (unsigned int j = 0; j < s_buildingList.numBuildings; j++) {
|
|
SceneGraph::Model *m = s_buildingList.buildings[j].resolvedModel;
|
|
modelSet.insert(m);
|
|
}
|
|
}
|
|
|
|
SceneGraph::ModelSkin skin;
|
|
for (TSetIter it = modelSet.begin(), itEnd = modelSet.end(); it != itEnd; ++it) {
|
|
SceneGraph::Model *m = (*it);
|
|
if (!m->SupportsPatterns()) continue;
|
|
skin.SetRandomColors(rand);
|
|
skin.Apply(m);
|
|
if (m->SupportsPatterns())
|
|
m->SetPattern(rand.Int32(0, m->GetNumPatterns() - 1));
|
|
}
|
|
}
|
|
|
|
CityOnPlanet::~CityOnPlanet()
|
|
{
|
|
// frame may be null (already removed from
|
|
for (unsigned int i = 0; i < m_buildings.size(); i++) {
|
|
Frame *f = Frame::GetFrame(m_frame);
|
|
f->RemoveStaticGeom(m_buildings[i].geom);
|
|
delete m_buildings[i].geom;
|
|
}
|
|
}
|
|
|
|
CityOnPlanet::CityOnPlanet(Planet *planet, SpaceStation *station, const Uint32 seed)
|
|
{
|
|
// beware, these are not used in this function, but are used in subroutines!
|
|
m_planet = planet;
|
|
m_frame = planet->GetFrame();
|
|
m_detailLevel = Pi::detail.cities;
|
|
|
|
m_buildings.clear();
|
|
m_buildings.reserve(DEFAULT_NUM_BUILDINGS);
|
|
|
|
const Aabb &aabb = station->GetAabb();
|
|
const matrix4x4d &m = station->GetOrient();
|
|
const vector3d p = station->GetPosition();
|
|
|
|
const vector3d mx = m * vector3d(1, 0, 0);
|
|
const vector3d mz = m * vector3d(0, 0, 1);
|
|
|
|
Random rand;
|
|
rand.seed(seed);
|
|
|
|
int population = planet->GetSystemBody()->GetPopulation();
|
|
int cityradius;
|
|
|
|
if (planet->GetSystemBody()->HasAtmosphere()) {
|
|
population *= 1000;
|
|
cityradius = (population < 200) ? 200 : ((population > START_SEG_SIZE) ? START_SEG_SIZE : population);
|
|
} else {
|
|
population *= 100;
|
|
cityradius = (population < 250) ? 250 : ((population > START_SEG_SIZE_NO_ATMO) ? START_SEG_SIZE_NO_ATMO : population);
|
|
}
|
|
|
|
citybuildinglist_t *buildings = &s_buildingList;
|
|
vector3d cent = p;
|
|
const int cellsize_i = 80;
|
|
const double cellsize = double(cellsize_i); // current widest building = 92
|
|
const double bodyradius = planet->GetSystemBody()->GetRadius(); // cache for bodyradius value
|
|
|
|
static const int gmid = (cityradius / cellsize_i);
|
|
static const int gsize = gmid * 2;
|
|
|
|
assert((START_SEG_SIZE / cellsize_i) < 100);
|
|
assert((START_SEG_SIZE_NO_ATMO / cellsize_i) < 100);
|
|
uint8_t cellgrid[200][200];
|
|
std::memset(cellgrid, 0, sizeof(cellgrid));
|
|
|
|
// calculate the size of the station model
|
|
const int x1 = floor(aabb.min.x / cellsize);
|
|
const int x2 = ceil(aabb.max.x / cellsize);
|
|
const int z1 = floor(aabb.min.z / cellsize);
|
|
const int z2 = ceil(aabb.max.z / cellsize);
|
|
|
|
// Clear the cells where the station is
|
|
for (int x = 0; x <= gsize; x++) {
|
|
for (int z = 0; z <= gsize; z++) {
|
|
const int zz = z - gmid;
|
|
const int xx = x - gmid;
|
|
if (zz > z1 && zz < z2 && xx > x1 && xx < x2)
|
|
cellgrid[x][z] = 1;
|
|
}
|
|
}
|
|
|
|
// precalc orientation transforms (to rotate buildings to face north/south/east/west)
|
|
matrix4x4d orientcalc[4];
|
|
orientcalc[0] = m * matrix4x4d::RotateYMatrix(M_PI * 0.5 * 0);
|
|
orientcalc[1] = m * matrix4x4d::RotateYMatrix(M_PI * 0.5 * 1);
|
|
orientcalc[2] = m * matrix4x4d::RotateYMatrix(M_PI * 0.5 * 2);
|
|
orientcalc[3] = m * matrix4x4d::RotateYMatrix(M_PI * 0.5 * 3);
|
|
|
|
const double maxdist = pow(gmid + 0.333, 2);
|
|
for (int x = 0; x <= gsize; x++) {
|
|
const double distx = pow((x - gmid), 2);
|
|
for (int z = 0; z <= gsize; z++) {
|
|
if (cellgrid[x][z] > 0) {
|
|
// This cell has been allocated for something already
|
|
continue;
|
|
}
|
|
const double distz = pow((z - gmid), 2);
|
|
if ((distz + distx) > maxdist)
|
|
continue;
|
|
|
|
// fewer and fewer buildings the further from center you get
|
|
if ((distx + distz) * (1.0 / maxdist) > rand.Double())
|
|
continue;
|
|
|
|
cent = p + mz * ((z - gmid) * cellsize) + mx * ((x - gmid) * cellsize);
|
|
cent = cent.Normalized();
|
|
|
|
const double height = planet->GetTerrainHeight(cent);
|
|
if ((height - bodyradius) < 0) // don't position below sealevel
|
|
continue;
|
|
|
|
cent = cent * height;
|
|
|
|
// quickly get a random building
|
|
const citybuilding_t &bt = buildings->buildings[rand.Int32(buildings->numBuildings)];
|
|
const CollMesh *cmesh = bt.collMesh.Get(); // collision mesh
|
|
|
|
// rotate the building to face a random direction
|
|
const int32_t orient = rand.Int32(4);
|
|
// FIXME: geoms need a userdata to tell gameplay code what we actually hit.
|
|
// We don't want to create a separate Body for each instance of the buildings, so we
|
|
// scam the code by pretending we're part of the host planet.
|
|
Geom *geom = new Geom(cmesh->GetGeomTree(), orientcalc[orient], cent, GetPlanet());
|
|
|
|
// add it to the list of buildings to render
|
|
m_buildings.push_back({ bt.instIndex, float(cmesh->GetRadius()), orient, cent, geom });
|
|
}
|
|
}
|
|
|
|
Aabb buildAABB;
|
|
for (std::vector<BuildingDef>::const_iterator iter = m_buildings.begin(), itEND = m_buildings.end(); iter != itEND; ++iter) {
|
|
buildAABB.Update((*iter).pos - p);
|
|
}
|
|
m_realCentre = buildAABB.min + ((buildAABB.max - buildAABB.min) * 0.5);
|
|
m_clipRadius = buildAABB.GetRadius();
|
|
AddStaticGeomsToCollisionSpace();
|
|
}
|
|
|
|
void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustum, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform)
|
|
{
|
|
// Early frustum test of whole city.
|
|
const vector3d stationPos = viewTransform * (station->GetPosition() + m_realCentre);
|
|
//modelview seems to be always identity
|
|
if (!frustum.TestPoint(stationPos, m_clipRadius))
|
|
return;
|
|
|
|
matrix4x4d rot[4];
|
|
matrix4x4f rotf[4];
|
|
rot[0] = station->GetOrient();
|
|
|
|
// change detail level if necessary
|
|
const bool bDetailChanged = m_detailLevel != Pi::detail.cities;
|
|
if (bDetailChanged) {
|
|
RemoveStaticGeomsFromCollisionSpace();
|
|
AddStaticGeomsToCollisionSpace();
|
|
}
|
|
|
|
rot[0] = viewTransform * rot[0];
|
|
for (int i = 1; i < 4; i++) {
|
|
rot[i] = rot[0] * matrix4x4d::RotateYMatrix(M_PI * 0.5 * double(i));
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
for (int e = 0; e < 16; e++) {
|
|
rotf[i][e] = float(rot[i][e]);
|
|
}
|
|
}
|
|
|
|
// update any idle animations
|
|
for (Uint32 i = 0; i < s_buildingList.numBuildings; i++) {
|
|
SceneGraph::Animation *pAnim = s_buildingList.buildings[i].idle;
|
|
if (pAnim) {
|
|
pAnim->SetProgress(fmod(pAnim->GetProgress() + (Pi::game->GetTimeStep() / pAnim->GetDuration()), 1.0));
|
|
pAnim->Interpolate();
|
|
}
|
|
}
|
|
|
|
Uint32 uCount = 0;
|
|
std::vector<Uint32> instCount;
|
|
std::vector<std::vector<matrix4x4f>> transform;
|
|
instCount.resize(s_buildingList.numBuildings);
|
|
transform.resize(s_buildingList.numBuildings);
|
|
memset(&instCount[0], 0, sizeof(Uint32) * s_buildingList.numBuildings);
|
|
for (Uint32 i = 0; i < s_buildingList.numBuildings; i++) {
|
|
transform[i].reserve(m_buildingCounts[i]);
|
|
}
|
|
|
|
for (std::vector<BuildingDef>::const_iterator iter = m_enabledBuildings.begin(), itEND = m_enabledBuildings.end(); iter != itEND; ++iter) {
|
|
const vector3d pos = viewTransform * (*iter).pos;
|
|
const vector3f posf(pos);
|
|
if (!frustum.TestPoint(pos, (*iter).clipRadius))
|
|
continue;
|
|
|
|
matrix4x4f _rot(rotf[(*iter).rotation]);
|
|
_rot.SetTranslate(posf);
|
|
|
|
// increment the instance count and store the transform
|
|
instCount[(*iter).instIndex]++;
|
|
transform[(*iter).instIndex].push_back(_rot);
|
|
|
|
++uCount;
|
|
}
|
|
|
|
// render the building models using instancing
|
|
for (Uint32 i = 0; i < s_buildingList.numBuildings; i++) {
|
|
if (!transform[i].empty())
|
|
s_buildingList.buildings[i].resolvedModel->Render(transform[i]);
|
|
}
|
|
|
|
r->GetStats().AddToStatCount(Graphics::Stats::STAT_BUILDINGS, uCount);
|
|
r->GetStats().AddToStatCount(Graphics::Stats::STAT_CITIES, 1);
|
|
}
|