rewrite FaceGenManager and rename it to 'FaceParts'

FaceParts improvements relative to FaceGenManager:

  - Random face generation (part selection) is separated from display.
  - Individual parts can be set manually with the remaining components
    randomised.  Part selection should be stable for a given seed, so
    fixing one component and regenerating with the same seed should not
    change the other components.
  - Somewhat shorter, simpler code.
  - The number of parts provided for males and females no longer has
    to match.
  - Any individual part image can be tied to some arbitrary combination
    of species, races and genders (e.g., you could have some clothing
    or some accessories [eg, tattoos] that are tied to a particular gender
    or tied to a particular race/gender combination, etc, and other
    items that can be used by any race and gender)

FaceParts also gets rid of the face backgrounds, because they're not
really necessary, the only backgrounds we currently have are ugly, and
it didn't make much sense to me that backgrounds are tied to particular
species (they should be tied to ships & stations, I think)
master
John Bartholomew 2014-09-14 15:30:56 +01:00
parent 5841ab488b
commit 9e6ee14131
10 changed files with 403 additions and 541 deletions

View File

@ -1,462 +0,0 @@
// Copyright © 2008-2014 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "libs.h"
#include "FaceGenManager.h"
#include "Lang.h"
#include "Pi.h"
#include "FileSystem.h"
#include "SDLWrappers.h"
#include "StringF.h"
namespace
{
void LoadImage(const std::string &filename, std::vector<SDLSurfacePtr> &vec) {
SDLSurfacePtr pSurf = LoadSurfaceFromFile(filename);
if(!pSurf)
Output("Failed to load image %s\n", filename.c_str());
vec.push_back(pSurf);
}
static const Uint32 FACE_WIDTH = 295;
static const Uint32 FACE_HEIGHT = 285;
static const Uint32 NUM_GENDERS = 2;
enum Flags {
GENDER_RAND = 0,
GENDER_MALE = (1<<0),
GENDER_FEMALE = (1<<1),
GENDER_MASK = 0x03, // <enum skip>
ARMOUR = (1<<2),
};
static void _blit_image(const SDLSurfacePtr &s, SDLSurfacePtr &is, int xoff, int yoff)
{
// XXX what should this do if the image couldn't be loaded?
if (! is) { return; }
SDL_Rect destrec = { 0, 0, 0, 0 };
destrec.x = ((FACE_WIDTH - is->w) / 2) + xoff;
destrec.y = yoff;
SDL_BlitSurface(is.Get(), 0, s.Get(), &destrec);
}
Sint32 GetNumMatching(const std::string &match, const std::vector<FileSystem::FileInfo>& fileList) {
Sint32 num_matching = 0;
for (std::vector<FileSystem::FileInfo>::const_iterator it = fileList.begin(), itEnd = fileList.end(); it!=itEnd; ++it) {
if (starts_with((*it).GetName(), match)) {
++num_matching;
}
}
return num_matching;
}
Sint32 GetNumRaceItem(const Sint32 speciesIdx, const Sint32 race, const char* item) {
char filename[1024];
snprintf(filename, sizeof(filename), "facegen/species_%d/race_%d/%s", speciesIdx, race, item);
std::vector<FileSystem::FileInfo> fileList;
FileSystem::gameDataFiles.ReadDirectory(filename, fileList);
char itemMask[256];
snprintf(itemMask, sizeof(itemMask), "%s_0_", item);
return GetNumMatching(itemMask, fileList);
}
}
class Race
{
public:
Race(const Sint32 speciesIdx, const Sint32 race)
{
m_numHeads = GetNumRaceItem(speciesIdx, race, "head");
m_numEyes = GetNumRaceItem(speciesIdx, race, "eyes");
m_numNoses = GetNumRaceItem(speciesIdx, race, "nose");
m_numMouths = GetNumRaceItem(speciesIdx, race, "mouth");
m_numHairstyles = GetNumRaceItem(speciesIdx, race, "hair");
// reserve space for them all
m_heads.reserve(m_numHeads * NUM_GENDERS);
m_eyes.reserve(m_numEyes * NUM_GENDERS);
m_noses.reserve(m_numNoses * NUM_GENDERS);
m_mouths.reserve(m_numMouths * NUM_GENDERS);
m_hairstyles.reserve(m_numHairstyles * NUM_GENDERS);
char filename[256];
// load the images
for(Uint32 gender = 0; gender < NUM_GENDERS; ++gender) {
for(Sint32 head = 0; head < m_numHeads; ++head) {
snprintf(filename, sizeof(filename), "facegen/species_%d/race_%d/head/head_%d_%d.png", speciesIdx, race, gender, head);
LoadImage(std::string(filename), m_heads);
}
for(Sint32 eyes = 0; eyes < m_numEyes; ++eyes) {
snprintf(filename, sizeof(filename), "facegen/species_%d/race_%d/eyes/eyes_%d_%d.png", speciesIdx, race, gender, eyes);
LoadImage(std::string(filename), m_eyes);
}
for(Sint32 nose = 0; nose < m_numNoses; ++nose) {
snprintf(filename, sizeof(filename), "facegen/species_%d/race_%d/nose/nose_%d_%d.png", speciesIdx, race, gender, nose);
LoadImage(std::string(filename), m_noses);
}
for(Sint32 mouth = 0; mouth < m_numMouths; ++mouth) {
snprintf(filename, sizeof(filename), "facegen/species_%d/race_%d/mouth/mouth_%d_%d.png", speciesIdx, race, gender, mouth);
LoadImage(std::string(filename), m_mouths);
}
for(Sint32 hair = 0; hair < m_numHairstyles; ++hair) {
snprintf(filename, sizeof(filename), "facegen/species_%d/race_%d/hair/hair_%d_%d.png", speciesIdx, race, gender, hair);
LoadImage(std::string(filename), m_hairstyles);
}
}
}
Sint8 NumHeads() const { return m_numHeads; }
Sint8 NumEyes() const { return m_numEyes; }
Sint8 NumNoses() const { return m_numNoses; }
Sint8 NumMouths() const { return m_numMouths; }
Sint8 NumHairstyles() const { return m_numHairstyles; }
SDLSurfacePtr Head(const Sint32 index, const Sint32 gender) const {
assert(index<m_numHeads);
return m_heads[(index+(gender*m_numHeads))];
}
SDLSurfacePtr Eyes(const Sint32 index, const Sint32 gender) const {
assert(index<m_numEyes);
return m_eyes[(index+(gender*m_numEyes))];
}
SDLSurfacePtr Nose(const Sint32 index, const Sint32 gender) const {
assert(index<m_numNoses);
return m_noses[(index+(gender*m_numNoses))];
}
SDLSurfacePtr Mouth(const Sint32 index, const Sint32 gender) const {
assert(index<m_numMouths);
return m_mouths[(index+(gender*m_numMouths))];
}
SDLSurfacePtr Hairstyle(const Sint32 index, const Sint32 gender) const {
assert(index<m_numHairstyles);
return m_hairstyles[(index+(gender*m_numHairstyles))];
}
private:
// private methods
private:
// private members
Sint8 m_numHeads;
Sint8 m_numEyes;
Sint8 m_numNoses;
Sint8 m_numMouths;
Sint8 m_numHairstyles;
std::vector<SDLSurfacePtr> m_heads;
std::vector<SDLSurfacePtr> m_eyes;
std::vector<SDLSurfacePtr> m_noses;
std::vector<SDLSurfacePtr> m_mouths;
std::vector<SDLSurfacePtr> m_hairstyles;
};
class Species
{
public:
Species(const Sint32 speciesIdx)
{
char filename[1024];
snprintf(filename, sizeof(filename), "facegen/species_%d", speciesIdx);
std::vector<FileSystem::FileInfo> output;
FileSystem::gameDataFiles.ReadDirectory(filename, output);
Uint32 num_races = GetNumMatching("race_", output);
m_races.reserve(num_races);
char tempRace[32];
for (Uint32 index = 0; index < num_races; ++index) {
snprintf(tempRace, 32, "race_%d", index);
m_races.push_back(new Race(speciesIdx, index));
}
{
snprintf(filename, sizeof(filename), "facegen/species_%d/clothes", speciesIdx);
std::vector<FileSystem::FileInfo> clothes;
FileSystem::gameDataFiles.ReadDirectory(filename, clothes);
m_numClothes = GetNumMatching("cloth_0_", clothes);
m_numArmour = GetNumMatching("armour_", clothes);
}
{
snprintf(filename, sizeof(filename), "facegen/species_%d/accessories", speciesIdx);
std::vector<FileSystem::FileInfo> accessories;
FileSystem::gameDataFiles.ReadDirectory(filename, accessories);
m_numAccessories = GetNumMatching("acc_", accessories);
}
{
snprintf(filename, sizeof(filename), "facegen/species_%d/backgrounds", speciesIdx);
std::vector<FileSystem::FileInfo> backgrounds;
FileSystem::gameDataFiles.ReadDirectory(filename, backgrounds);
m_numBackground = GetNumMatching("background_", backgrounds);
}
m_clothes.reserve(m_numClothes * NUM_GENDERS);
m_armour.reserve(m_numArmour); // unisex
m_accessories.reserve(m_numAccessories); // unisex
m_background.reserve(m_numBackground); // unisex
// load the images
for(Uint32 gender = 0; gender < NUM_GENDERS; ++gender) {
for(Sint32 cloth = 0; cloth < m_numClothes; ++cloth) {
snprintf(filename, sizeof(filename), "facegen/species_%d/clothes/cloth_%d_%d.png", speciesIdx, gender, cloth);
LoadImage(std::string(filename), m_clothes);
}
}
for(Sint32 armour = 0; armour < m_numArmour; ++armour) {
snprintf(filename, sizeof(filename), "facegen/species_%d/clothes/armour_%d.png", speciesIdx, armour);
LoadImage(std::string(filename), m_armour);
}
for(Sint32 accessories = 0; accessories < m_numAccessories; ++accessories) {
snprintf(filename, sizeof(filename), "facegen/species_%d/accessories/acc_%d.png", speciesIdx, accessories);
LoadImage(std::string(filename), m_accessories);
}
for(Sint32 background = 0; background < m_numBackground; ++background) {
snprintf(filename, sizeof(filename), "facegen/species_%d/backgrounds/background_%d.png", speciesIdx, background);
LoadImage(std::string(filename), m_background);
}
}
~Species() {
for (std::vector<Race*>::iterator it = m_races.begin(), itEnd = m_races.end(); it != itEnd; ++it) {
delete (*it);
}
m_races.clear();
}
Sint32 NumGenders() const {
return NUM_GENDERS;
}
Sint32 NumRaces() const {
return m_races.size();
}
Sint32 NumHeads(const Sint32 raceIdx) const
{
assert(Uint32(raceIdx) < m_races.size());
return m_races[raceIdx]->NumHeads();
}
Sint32 NumEyes(const Sint32 raceIdx) const
{
assert(Uint32(raceIdx) < m_races.size());
return m_races[raceIdx]->NumEyes();
}
Sint32 NumNoses(const Sint32 raceIdx) const
{
assert(Uint32(raceIdx) < m_races.size());
return m_races[raceIdx]->NumNoses();
}
Sint32 NumMouths(const Sint32 raceIdx) const
{
assert(Uint32(raceIdx) < m_races.size());
return m_races[raceIdx]->NumMouths();
}
Sint32 NumHairstyles(const Sint32 raceIdx) const
{
assert(Uint32(raceIdx) < m_races.size());
return m_races[raceIdx]->NumHairstyles();
}
// generic attributes
Sint8 NumClothes() const { return m_numClothes; }
Sint8 NumArmour() const { return m_numArmour; }
Sint8 NumAccessories() const { return m_numAccessories; }
Sint8 NumBackground() const { return m_numBackground; }
void GetImagesForCharacter(FaceGenManager::TQueryResult& res, const int race, const int gender, const int head, const int eyes,
const int nose, const int mouth, const int hair, const int clothes, const int armour,
const int accessories, const int background) const
{
res.mHead = m_races[race]->Head(head, gender);
res.mEyes = m_races[race]->Eyes(eyes, gender);
res.mNose = m_races[race]->Nose(nose, gender);
res.mMouth = m_races[race]->Mouth(mouth, gender);
res.mHairstyle = m_races[race]->Hairstyle(hair, gender);
res.mClothes = m_clothes[clothes];
res.mArmour = m_armour[armour];
res.mAccessories = m_accessories[accessories];
res.mBackground = m_background[background];
}
private:
Sint8 m_numClothes;
Sint8 m_numArmour;
Sint8 m_numAccessories;
Sint8 m_numBackground;
std::vector<Race*> m_races;
std::vector<SDLSurfacePtr> m_clothes;
std::vector<SDLSurfacePtr> m_armour;
std::vector<SDLSurfacePtr> m_accessories;
std::vector<SDLSurfacePtr> m_background;
};
//static
std::vector<Species*> FaceGenManager::m_species;
//static
void FaceGenManager::Init()
{
std::vector<FileSystem::FileInfo> output;
FileSystem::gameDataFiles.ReadDirectory("facegen", output);
Uint32 num_species = 0;
for (std::vector<FileSystem::FileInfo>::const_iterator it = output.begin(), itEnd = output.end(); it!=itEnd; ++it) {
if (starts_with((*it).GetName(), "species_")) {
++num_species;
}
}
char tempSpecies[32];
for (Uint32 index = 0; index < num_species; ++index) {
snprintf(tempSpecies, 32, "species_%u", index);
m_species.push_back(new Species(index));
}
Output("Face Generation source images loaded.\n");
}
//static
void FaceGenManager::Destroy()
{
for (std::vector<Species*>::iterator it = m_species.begin(), itEnd = m_species.end(); it != itEnd; ++it) {
delete (*it);
}
m_species.clear();
}
//static
Sint32 FaceGenManager::NumSpecies() {
return m_species.size();
}
//static
Sint32 FaceGenManager::NumGenders(const Sint32 speciesIdx) {
return m_species[speciesIdx]->NumGenders();
}
//static
Sint32 FaceGenManager::NumRaces(const Sint32 speciesIdx) {
return m_species[speciesIdx]->NumRaces();
}
//static
Sint32 FaceGenManager::NumHeads(const Sint32 speciesIdx, const Sint32 raceIdx)
{
assert(Uint32(speciesIdx) < m_species.size());
return m_species[speciesIdx]->NumHeads(raceIdx);
}
//static
Sint32 FaceGenManager::NumEyes(const Sint32 speciesIdx, const Sint32 raceIdx)
{
assert(Uint32(speciesIdx) < m_species.size());
return m_species[speciesIdx]->NumEyes(raceIdx);
}
//static
Sint32 FaceGenManager::NumNoses(const Sint32 speciesIdx, const Sint32 raceIdx)
{
assert(Uint32(speciesIdx) < m_species.size());
return m_species[speciesIdx]->NumNoses(raceIdx);
}
//static
Sint32 FaceGenManager::NumMouths(const Sint32 speciesIdx, const Sint32 raceIdx)
{
assert(Uint32(speciesIdx) < m_species.size());
return m_species[speciesIdx]->NumMouths(raceIdx);
}
//static
Sint32 FaceGenManager::NumHairstyles(const Sint32 speciesIdx, const Sint32 raceIdx)
{
assert(Uint32(speciesIdx) < m_species.size());
return m_species[speciesIdx]->NumHairstyles(raceIdx);
}
// generic attributes
//static
Sint8 FaceGenManager::NumClothes(const Sint32 speciesIdx) {
return m_species[speciesIdx]->NumClothes();
}
//static
Sint8 FaceGenManager::NumArmour(const Sint32 speciesIdx) {
return m_species[speciesIdx]->NumArmour();
}
//static
Sint8 FaceGenManager::NumAccessories(const Sint32 speciesIdx) {
return m_species[speciesIdx]->NumAccessories();
}
//static
Sint8 FaceGenManager::NumBackground(const Sint32 speciesIdx) {
return m_species[speciesIdx]->NumBackground();
}
//static
void FaceGenManager::GetImagesForCharacter(TQueryResult& res, const Sint32 speciesIdx, const int race, const int gender,
const int head, const int eyes, const int nose, const int mouth, const int hair, const int clothes, const int armour,
const int accessories, const int background)
{
m_species[speciesIdx]->GetImagesForCharacter(res, race, gender, head, eyes,
nose, mouth, hair, clothes, armour, accessories, background);
}
//static
void FaceGenManager::BlitFaceIm(SDLSurfacePtr &faceim, Sint8 &genderOut, const Uint32 flags, const Uint32 seed)
{
Random rand(seed);
const int species = (m_species.size()==1) ? 0 : rand.Int32(0,m_species.size()-1);
const int race = rand.Int32(0,NumRaces(species)-1);
int gender;
switch (flags & GENDER_MASK) {
case GENDER_MALE:
gender = 0;
break;
case GENDER_FEMALE:
gender = 1;
break;
case GENDER_RAND:
default:
gender = rand.Int32(0,NumGenders(species)-1);
break;
}
genderOut = gender;
const int head = rand.Int32(0,NumHeads(species,race)-1);
const int eyes = rand.Int32(0,NumEyes(species,race)-1);
const int nose = rand.Int32(0,NumNoses(species,race)-1);
const int mouth = rand.Int32(0,NumMouths(species,race)-1);
const int hair = rand.Int32(0,NumHairstyles(species,race)-1);
const int clothes = rand.Int32(0,NumClothes(species)-1);
const int armour = rand.Int32(0,NumArmour(species)-1);
const int accessories = rand.Int32(0,NumAccessories(species)-1);
const int background = rand.Int32(0,NumBackground(species)-1);
FaceGenManager::TQueryResult res;
FaceGenManager::GetImagesForCharacter(res, species, race, gender, head, eyes,
nose, mouth, hair, clothes, armour, accessories, background);
_blit_image(faceim, res.mBackground, 0, 0);
_blit_image(faceim, res.mHead, 0, 0);
if (!(flags & ARMOUR)) {
_blit_image(faceim, res.mClothes, 0, 135);
}
_blit_image(faceim, res.mEyes, 0, 41);
_blit_image(faceim, res.mNose, 1, 89);
_blit_image(faceim, res.mMouth, 0, 155);
if (!(flags & ARMOUR)) {
if (rand.Int32(0,1)>0)
_blit_image(faceim, res.mAccessories, 0, 0);
_blit_image(faceim, res.mHairstyle, 0, 0);
}
else {
_blit_image(faceim, res.mArmour, 0, 0);
}
}

View File

@ -1,58 +0,0 @@
// Copyright © 2008-2014 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
// DEPRECATED due to new ui system
#ifndef _FACEGENMANAGER
#define _FACEGENMANAGER
#include "SDLWrappers.h"
// fwd decl'
class Species;
class Race;
class FaceGenManager
{
public:
struct TQueryResult {
SDLSurfacePtr mHead;
SDLSurfacePtr mEyes;
SDLSurfacePtr mNose;
SDLSurfacePtr mMouth;
SDLSurfacePtr mHairstyle;
SDLSurfacePtr mClothes;
SDLSurfacePtr mArmour;
SDLSurfacePtr mAccessories;
SDLSurfacePtr mBackground;
};
static void Init();
static void Destroy();
// species & race dependent attributes
static Sint32 NumSpecies();
static Sint32 NumGenders(const Sint32 speciesIdx);
static Sint32 NumRaces(const Sint32 speciesIdx);
static Sint32 NumHeads(const Sint32 speciesIdx, const Sint32 raceIdx);
static Sint32 NumEyes(const Sint32 speciesIdx, const Sint32 raceIdx);
static Sint32 NumNoses(const Sint32 speciesIdx, const Sint32 raceIdx);
static Sint32 NumMouths(const Sint32 speciesIdx, const Sint32 raceIdx);
static Sint32 NumHairstyles(const Sint32 speciesIdx, const Sint32 raceIdx);
// species generic attributes
static Sint8 NumClothes(const Sint32 speciesIdx);
static Sint8 NumArmour(const Sint32 speciesIdx);
static Sint8 NumAccessories(const Sint32 speciesIdx);
static Sint8 NumBackground(const Sint32 speciesIdx);
static void GetImagesForCharacter( TQueryResult& out, const Sint32 speciesIdx, const int race, const int gender, const int head, const int eyes,
const int nose, const int mouth, const int hair, const int clothes, const int armour,
const int accessories, const int background );
static void BlitFaceIm( SDLSurfacePtr &faceim, Sint8 &genderOut, const Uint32 flags, const Uint32 seed );
private:
static std::vector<Species*> m_species;
};
#endif

316
src/FaceParts.cpp Normal file
View File

@ -0,0 +1,316 @@
// Copyright © 2008-2014 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "FaceParts.h"
#include "FileSystem.h"
#include "SDLWrappers.h"
#include "utils.h"
#include "libs.h"
namespace {
static const int MAX_GENDERS = 6;
static const int MAX_RACES = 16;
static const int MAX_SPECIES = 10;
static const Uint32 GENDER_SHIFT = 0;
static const Uint32 GENDER_MASK = ((1u << MAX_GENDERS) - 1u) << GENDER_SHIFT;
static const Uint32 RACE_SHIFT = GENDER_SHIFT + MAX_GENDERS;
static const Uint32 RACE_MASK = ((1u << MAX_RACES) - 1u) << RACE_SHIFT;
static const Uint32 SPECIES_SHIFT = RACE_SHIFT + MAX_RACES;
static const Uint32 SPECIES_MASK = ((1u << MAX_SPECIES) - 1u) << SPECIES_SHIFT;
// You can never have too many static_asserts, right?
static_assert(((MAX_GENDERS + MAX_RACES + MAX_SPECIES) == 32), "unused bits in the face part selector");
static_assert(((GENDER_MASK | RACE_MASK | SPECIES_MASK) == UINT32_MAX), "unused bits in the face part selector");
static_assert(((GENDER_MASK & RACE_MASK) == 0u), "face part selector: overlap between gender and race mask");
static_assert(((GENDER_MASK & SPECIES_MASK) == 0u), "face part selector: overlap between gender and species mask");
static_assert(((RACE_MASK & SPECIES_MASK) == 0u), "face part selector: overlap between race and species mask");
struct Part {
Uint32 selector; // a bitmask indicating which species, races and genders can use this part
SDLSurfacePtr part;
Part(): selector(0u) {}
Part(const Uint32 sel, SDLSurfacePtr im): selector(sel), part(im) {}
};
struct SpeciesInfo {
int num_races;
int num_genders;
SpeciesInfo(int nraces, int ngenders): num_races(nraces), num_genders(ngenders) {}
};
class PartDb {
public:
std::vector<SpeciesInfo> species;
std::vector<Part> heads;
std::vector<Part> eyes;
std::vector<Part> noses;
std::vector<Part> mouths;
std::vector<Part> hairstyles;
std::vector<Part> accessories;
std::vector<Part> clothes;
std::vector<Part> armour;
void Clear();
void Scan();
private:
void ScanSpecies(const std::string &dir, int species_idx);
void ScanParts(std::vector<Part> &output, const int species_idx, const int race_idx, const std::string &path, const char *prefix);
void ScanGenderedParts(std::vector<Part> &output, const int species_idx, const int race_idx, const std::string &path, const char *prefix);
};
static Uint32 _make_selector(int species, int race, int gender) {
assert(species < MAX_SPECIES);
assert(race < MAX_RACES);
assert(gender < MAX_GENDERS);
Uint32 mask = 0u;
if (species < 0) { mask |= SPECIES_MASK; } else { mask |= (1u << (species + SPECIES_SHIFT)); }
if (race < 0) { mask |= RACE_MASK; } else { mask |= (1u << (race + RACE_SHIFT)); }
if (gender < 0) { mask |= GENDER_MASK; } else { mask |= (1u << (gender + GENDER_SHIFT)); }
return mask;
}
static int _count_parts(const std::vector<Part> &parts, const Uint32 selector) {
int count = 0;
for (const auto &part : parts) {
if ((selector & part.selector) == selector) ++count;
}
return count;
}
static SDL_Surface *_get_part(const std::vector<Part> &parts, const Uint32 selector, int index) {
for (const auto &part : parts) {
if ((selector & part.selector) == selector) {
if (!index) { return part.part.Get(); }
--index;
}
}
return nullptr;
}
static void _blit_image(SDL_Surface *target, SDL_Surface *source, int xoff, int yoff)
{
assert(source);
SDL_Rect destrec = { 0, 0, 0, 0 };
destrec.x = ((FaceParts::FACE_WIDTH - source->w) / 2) + xoff;
destrec.y = yoff;
SDL_BlitSurface(source, 0, target, &destrec);
}
static PartDb *s_partdb;
} // anonymous namespace
namespace fs = FileSystem;
void PartDb::Clear() {
species.clear();
heads.clear();
eyes.clear();
noses.clear();
mouths.clear();
hairstyles.clear();
accessories.clear();
clothes.clear();
armour.clear();
}
void PartDb::Scan() {
Clear();
int species_count = 0;
const auto flags = fs::FileEnumerator::IncludeDirs | fs::FileEnumerator::ExcludeFiles;
for (fs::FileEnumerator dirs(fs::gameDataFiles, "facegen", flags); !dirs.Finished(); dirs.Next()) {
if (species_count >= MAX_SPECIES) {
Output("FaceParts: reached the limit on the number of species\n");
break;
}
ScanSpecies(dirs.Current().GetPath(), species_count);
++species_count;
}
}
void PartDb::ScanSpecies(const std::string &basedir, const int species_idx) {
int race_count = 0;
const auto flags = fs::FileEnumerator::IncludeDirs | fs::FileEnumerator::ExcludeFiles;
for (fs::FileEnumerator dirs(fs::gameDataFiles, basedir, flags); !dirs.Finished(); dirs.Next()) {
const std::string &path = dirs.Current().GetPath();
const std::string &name = dirs.Current().GetName();
if (name == "accessories") {
ScanParts(this->accessories, species_idx, -1, path, "acc_");
} else if (name == "clothes") {
ScanGenderedParts(this->clothes, species_idx, -1, path, "cloth_");
ScanParts(this->armour, species_idx, -1, path, "armour_");
} else if (starts_with(name, "race_")) {
if (race_count >= MAX_RACES) {
Output("FaceParts: reached the limit on the number of races\n");
continue; // continue to ensure 'accessories' and 'clothes' dirs can still be scanned
}
const int race_idx = race_count++;
ScanGenderedParts(this->heads, species_idx, race_idx, fs::JoinPath(path, "head"), "head_");
ScanGenderedParts(this->eyes, species_idx, race_idx, fs::JoinPath(path, "eyes"), "eyes_");
ScanGenderedParts(this->noses, species_idx, race_idx, fs::JoinPath(path, "nose"), "nose_");
ScanGenderedParts(this->mouths, species_idx, race_idx, fs::JoinPath(path, "mouth"), "mouth_");
ScanGenderedParts(this->hairstyles, species_idx, race_idx, fs::JoinPath(path, "hair"), "hair_");
} else {
Output("FaceParts: unknown directory '%s'\n", path.c_str());
}
}
species.push_back(SpeciesInfo(race_count, 2)); // XXX currently we hardcode genders = 2
}
void PartDb::ScanParts(std::vector<Part> &output, const int species_idx, const int race_idx, const std::string &path, const char *prefix) {
const Uint32 selector = _make_selector(species_idx, race_idx, -1);
for (fs::FileEnumerator files(fs::gameDataFiles, path); !files.Finished(); files.Next()) {
const std::string &name = files.Current().GetName();
if (starts_with(name, prefix)) {
SDLSurfacePtr im = LoadSurfaceFromFile(files.Current().GetPath());
if (im) {
output.push_back(Part(selector, im));
} else {
Output("Failed to load image %s\n", files.Current().GetPath().c_str());
}
}
}
}
void PartDb::ScanGenderedParts(std::vector<Part> &output, const int species_idx, const int race_idx, const std::string &path, const char *prefix) {
const int prefix_len = strlen(prefix);
for (fs::FileEnumerator files(fs::gameDataFiles, path); !files.Finished(); files.Next()) {
const std::string &name = files.Current().GetName();
if (starts_with(name, prefix)) {
int gender_idx = (name[prefix_len] - '0');
assert(gender_idx == 0 || gender_idx == 1); // currently we enforce two genders
const Uint32 sel = _make_selector(species_idx, race_idx, gender_idx);
SDLSurfacePtr im = LoadSurfaceFromFile(files.Current().GetPath());
if (im) {
output.push_back(Part(sel, im));
} else {
Output("Failed to load image %s\n", files.Current().GetPath().c_str());
}
}
}
}
const int FaceParts::FACE_WIDTH = 295;
const int FaceParts::FACE_HEIGHT = 285;
void FaceParts::Init()
{
s_partdb = new PartDb;
s_partdb->Scan();
Output("Face Generation source images loaded.\n");
}
void FaceParts::Uninit()
{
delete s_partdb;
s_partdb = nullptr;
}
int FaceParts::NumSpecies() {
return s_partdb->species.size();
}
int FaceParts::NumGenders(const int speciesIdx) {
assert(speciesIdx >= 0 && speciesIdx < NumSpecies());
return s_partdb->species[speciesIdx].num_genders;
}
int FaceParts::NumRaces(const int speciesIdx) {
assert(speciesIdx >= 0 && speciesIdx < NumSpecies());
return s_partdb->species[speciesIdx].num_races;
}
int FaceParts::NumHeads(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->heads, _make_selector(speciesIdx, raceIdx, genderIdx));
}
int FaceParts::NumEyes(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->eyes, _make_selector(speciesIdx, raceIdx, genderIdx));
}
int FaceParts::NumNoses(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->noses, _make_selector(speciesIdx, raceIdx, genderIdx));
}
int FaceParts::NumMouths(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->mouths, _make_selector(speciesIdx, raceIdx, genderIdx));
}
int FaceParts::NumHairstyles(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->hairstyles, _make_selector(speciesIdx, raceIdx, genderIdx));
}
int FaceParts::NumClothes(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->clothes, _make_selector(speciesIdx, raceIdx, genderIdx));
}
int FaceParts::NumAccessories(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->accessories, _make_selector(speciesIdx, raceIdx, genderIdx));
}
int FaceParts::NumArmour(const int speciesIdx, const int raceIdx, const int genderIdx) {
return _count_parts(s_partdb->armour, _make_selector(speciesIdx, raceIdx, genderIdx));
}
static void _pick(Random &rng, int &inout_value, const int limit) {
assert(limit > 0);
// we always run the RNG, even if the result is not needed, because that way
// the output (index) for a particular component should be fixed for a given seed,
// independent of changes to other components
const Uint32 rng_value = (rng.Int32() % limit);
if (inout_value < 0) {
inout_value = rng_value;
assert(inout_value >= 0 && inout_value < limit);
} else {
inout_value = (inout_value < limit ? inout_value : limit - 1);
}
}
void FaceParts::PickFaceParts(FaceDescriptor &inout_face, const Uint32 seed) {
Random rand(seed);
_pick(rand, inout_face.species, NumSpecies());
_pick(rand, inout_face.race, NumRaces(inout_face.species));
_pick(rand, inout_face.gender, NumGenders(inout_face.species));
const Uint32 selector = _make_selector(inout_face.species, inout_face.race, inout_face.gender);
_pick(rand, inout_face.head, _count_parts(s_partdb->heads, selector));
_pick(rand, inout_face.eyes, _count_parts(s_partdb->eyes, selector));
_pick(rand, inout_face.nose, _count_parts(s_partdb->noses, selector));
_pick(rand, inout_face.mouth, _count_parts(s_partdb->mouths, selector));
_pick(rand, inout_face.hairstyle, _count_parts(s_partdb->hairstyles, selector));
const bool has_accessories = (rand.Int32() & 1);
_pick(rand, inout_face.accessories, _count_parts(s_partdb->accessories, selector));
if (!has_accessories) { inout_face.accessories = 0; }
_pick(rand, inout_face.clothes, _count_parts(s_partdb->clothes, selector));
_pick(rand, inout_face.armour, _count_parts(s_partdb->armour, selector));
}
void FaceParts::BuildFaceImage(SDL_Surface *faceIm, const FaceDescriptor &face, bool armoured) {
const Uint32 selector = _make_selector(face.species, face.race, face.gender);
_blit_image(faceIm, _get_part(s_partdb->heads, selector, face.head), 0, 0);
if (!armoured) {
_blit_image(faceIm, _get_part(s_partdb->clothes, selector, face.clothes), 0, 135);
}
_blit_image(faceIm, _get_part(s_partdb->eyes, selector, face.eyes), 0, 41);
_blit_image(faceIm, _get_part(s_partdb->noses, selector, face.nose), 1, 89);
_blit_image(faceIm, _get_part(s_partdb->mouths, selector, face.mouth), 0, 155);
if (!armoured) {
_blit_image(faceIm, _get_part(s_partdb->accessories, selector, face.accessories), 0, 0);
_blit_image(faceIm, _get_part(s_partdb->hairstyles, selector, face.hairstyle), 0, 0);
} else {
_blit_image(faceIm, _get_part(s_partdb->armour, selector, face.armour), 0, 0);
}
}

60
src/FaceParts.h Normal file
View File

@ -0,0 +1,60 @@
// Copyright © 2008-2014 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#ifndef FACE_PARTS_H
#define FACE_PARTS_H
#include <SDL_stdinc.h>
// FaceParts deals with:
// - Scanning the data/facegen/ directory and loading all the face part images
// - Generating random faces from a particular seed and constraints
// - Building a combined face image from a face descriptor
struct SDL_Surface;
namespace FaceParts {
extern const int FACE_WIDTH;
extern const int FACE_HEIGHT;
// describes a face
// components can be set to -1 to indicate that the attribute should be chosen randomly,
// or set to a non-negative integer to specify a particular part
struct FaceDescriptor {
// selectors
int species = -1;
int race = -1;
int gender = -1;
// parts
int head = -1;
int eyes = -1;
int nose = -1;
int mouth = -1;
int hairstyle = -1;
int accessories = -1;
int clothes = -1;
int armour = -1;
};
void Init();
void Uninit();
int NumSpecies();
int NumGenders(const int speciesIdx);
int NumRaces(const int speciesIdx);
int NumHeads(const int speciesIdx, const int raceIdx, const int genderIdx);
int NumEyes(const int speciesIdx, const int raceIdx, const int genderIdx);
int NumNoses(const int speciesIdx, const int raceIdx, const int genderIdx);
int NumMouths(const int speciesIdx, const int raceIdx, const int genderIdx);
int NumHairstyles(const int speciesIdx, const int raceIdx, const int genderIdx);
int NumClothes(const int speciesIdx, const int raceIdx, const int genderIdx);
int NumAccessories(const int speciesIdx, const int raceIdx, const int genderIdx);
int NumArmour(const int speciesIdx, const int raceIdx, const int genderIdx);
void PickFaceParts(FaceDescriptor &inout_face, const Uint32 seed);
void BuildFaceImage(SDL_Surface *faceIm, const FaceDescriptor &face, bool armoured);
}
#endif

View File

@ -36,7 +36,7 @@ noinst_HEADERS = \
DynamicBody.h \
Easing.h \
EnumStrings.h \
FaceGenManager.h \
FaceParts.h \
Factions.h \
FileSystem.h \
FontCache.h \
@ -167,7 +167,7 @@ pioneer_SOURCES = \
DeathView.cpp \
DynamicBody.cpp \
EnumStrings.cpp \
FaceGenManager.cpp \
FaceParts.cpp \
Factions.cpp \
FileSourceZip.cpp \
FileSystem.cpp \
@ -426,7 +426,7 @@ modelcompiler_SOURCES = \
DeathView.cpp \
DynamicBody.cpp \
EnumStrings.cpp \
FaceGenManager.cpp \
FaceParts.cpp \
Factions.cpp \
FileSourceZip.cpp \
FileSystem.cpp \

View File

@ -7,7 +7,7 @@
#include "CargoBody.h"
#include "CityOnPlanet.h"
#include "DeathView.h"
#include "FaceGenManager.h"
#include "FaceParts.h"
#include "Factions.h"
#include "FileSystem.h"
#include "Frame.h"
@ -510,7 +510,7 @@ void Pi::Init(const std::map<std::string,std::string> &options, bool no_gui)
draw_progress(gauge, label, 0.3f);
FaceGenManager::Init();
FaceParts::Init();
draw_progress(gauge, label, 0.4f);
@ -693,7 +693,7 @@ void Pi::Quit()
SpaceStation::Uninit();
CityOnPlanet::Uninit();
BaseSphere::Uninit();
FaceGenManager::Destroy();
FaceParts::Uninit();
Graphics::Uninit();
Pi::ui.Reset(0);
LuaUninit();

View File

@ -5,6 +5,7 @@
#define _SDLWRAPPERS_H
#include "SmartPtr.h"
#include <SDL_surface.h>
namespace FileSystem { class FileSource; }

View File

@ -5,15 +5,12 @@
#include "FileSystem.h"
#include "SDLWrappers.h"
#include "graphics/TextureBuilder.h"
#include "FaceGenManager.h"
#include "FaceParts.h"
using namespace UI;
namespace GameUI {
static const Uint32 FACE_WIDTH = 295;
static const Uint32 FACE_HEIGHT = 285;
RefCountedPtr<Graphics::Material> Face::s_material;
Face::Face(Context *context, Uint32 flags, Uint32 seed) : Single(context), m_preferredSize(INT_MAX)
@ -23,10 +20,18 @@ Face::Face(Context *context, Uint32 flags, Uint32 seed) : Single(context), m_pre
m_flags = flags;
m_seed = seed;
SDLSurfacePtr faceim = SDLSurfacePtr::WrapNew(SDL_CreateRGBSurface(SDL_SWSURFACE, FACE_WIDTH, FACE_HEIGHT, 24, 0xff, 0xff00, 0xff0000, 0));
SDLSurfacePtr faceim = SDLSurfacePtr::WrapNew(SDL_CreateRGBSurface(SDL_SWSURFACE, FaceParts::FACE_WIDTH, FaceParts::FACE_HEIGHT, 24, 0xff, 0xff00, 0xff0000, 0));
Sint8 gender=0;
FaceGenManager::BlitFaceIm(faceim, gender, flags, seed);
FaceParts::FaceDescriptor face;
switch (flags & GENDER_MASK) {
case RAND: face.gender = -1; break;
case MALE: face.gender = 0; break;
case FEMALE: face.gender = 1; break;
default: assert(0); break;
}
FaceParts::PickFaceParts(face, m_seed);
FaceParts::BuildFaceImage(faceim.Get(), face, (flags & ARMOUR));
m_texture.reset(Graphics::TextureBuilder(faceim, Graphics::LINEAR_CLAMP, true, true).CreateTexture(GetContext()->GetRenderer()));
@ -36,7 +41,7 @@ Face::Face(Context *context, Uint32 flags, Uint32 seed) : Single(context), m_pre
s_material.Reset(GetContext()->GetRenderer()->CreateMaterial(matDesc));
}
m_preferredSize = UI::Point(FACE_WIDTH, FACE_HEIGHT);
m_preferredSize = UI::Point(FaceParts::FACE_WIDTH, FaceParts::FACE_HEIGHT);
SetSizeControlFlags(UI::Widget::PRESERVE_ASPECT);
}
@ -87,7 +92,7 @@ Face *Face::SetHeightLines(Uint32 lines)
{
const Text::TextureFont *font = GetContext()->GetFont(GetFont()).Get();
const float height = font->GetHeight() * lines;
m_preferredSize = UI::Point(height * float(FACE_WIDTH) / float(FACE_HEIGHT), height);
m_preferredSize = UI::Point(height * float(FaceParts::FACE_WIDTH) / float(FaceParts::FACE_HEIGHT), height);
GetContext()->RequestLayout();
return this;
}

View File

@ -184,7 +184,7 @@
<ClCompile Include="..\..\src\DynamicBody.cpp" />
<ClCompile Include="..\..\src\EnumStrings.cpp" />
<ClCompile Include="..\..\src\enum_table.cpp" />
<ClCompile Include="..\..\src\FaceGenManager.cpp" />
<ClCompile Include="..\..\src\FaceParts.cpp" />
<ClCompile Include="..\..\src\Factions.cpp" />
<ClCompile Include="..\..\src\FileSourceZip.cpp" />
<ClCompile Include="..\..\src\FileSystem.cpp" />
@ -340,7 +340,7 @@
<ClInclude Include="..\..\src\DynamicBody.h" />
<ClInclude Include="..\..\src\EnumStrings.h" />
<ClInclude Include="..\..\src\enum_table.h" />
<ClInclude Include="..\..\src\FaceGenManager.h" />
<ClInclude Include="..\..\src\FaceParts.h" />
<ClInclude Include="..\..\src\Factions.h" />
<ClInclude Include="..\..\src\FileSourceZip.h" />
<ClInclude Include="..\..\src\FileSystem.h" />
@ -464,4 +464,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -384,7 +384,7 @@
<ClCompile Include="..\..\contrib\PicoDDS\PicoDDS.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\src\FaceGenManager.cpp">
<ClCompile Include="..\..\src\FaceParts.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\src\SpeedLines.cpp">
@ -812,7 +812,7 @@
<ClInclude Include="..\..\contrib\PicoDDS\PicoDDS.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="..\..\src\FaceGenManager.h">
<ClInclude Include="..\..\src\FaceParts.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="..\..\src\Shields.h">
@ -854,4 +854,4 @@
<Filter>src\win32</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>