2014-12-31 04:05:37 -08:00
|
|
|
// Copyright © 2008-2015 Pioneer Developers. See AUTHORS.txt for details
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
// 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;
|
|
|
|
|
2014-09-15 14:45:41 -07:00
|
|
|
SDLSurfacePtr background_general;
|
|
|
|
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
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)
|
|
|
|
{
|
2014-09-15 14:45:41 -07:00
|
|
|
assert(target);
|
|
|
|
if (!source) return;
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2014-09-15 14:45:41 -07:00
|
|
|
static const char BACKGROUND_GENERAL_PATH[] = "facegen/backgrounds/general.png";
|
|
|
|
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
void PartDb::Scan() {
|
|
|
|
Clear();
|
|
|
|
|
2014-09-15 14:45:41 -07:00
|
|
|
background_general = LoadSurfaceFromFile(BACKGROUND_GENERAL_PATH);
|
|
|
|
if (!background_general) {
|
|
|
|
Output("Failed to load image %s\n", BACKGROUND_GENERAL_PATH);
|
|
|
|
}
|
|
|
|
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
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()) {
|
2014-09-15 14:45:17 -07:00
|
|
|
if (!starts_with(dirs.Current().GetName(), "species_"))
|
|
|
|
continue;
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
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)) {
|
2015-11-29 08:08:48 -08:00
|
|
|
char *end = nullptr;
|
|
|
|
int gender_idx = strtol(name.c_str() + prefix_len, &end, 10);
|
|
|
|
Uint32 sel;
|
|
|
|
// HACK -- attempt to recognise `foo_3.png' style names
|
|
|
|
if (strcmp(end, ".png") == 0) {
|
|
|
|
sel = _make_selector(species_idx, race_idx, -1);
|
|
|
|
} else {
|
|
|
|
if (gender_idx < 0 || gender_idx >= MAX_GENDERS) {
|
|
|
|
Output("Gender out of range: %s\n", files.Current().GetPath().c_str());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
sel = _make_selector(species_idx, race_idx, gender_idx);
|
|
|
|
}
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2014-09-15 14:45:41 -07:00
|
|
|
_blit_image(faceIm, s_partdb->background_general.Get(), 0, 0);
|
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)
2014-09-14 07:30:56 -07:00
|
|
|
_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);
|
|
|
|
}
|
|
|
|
}
|