#include "design/hull.h" #include "str_util.h" #include "main/logging.h" #include "main/references.h" #include "compat/misc.h" #include "design/design.h" #include "design/subsystem.h" #include "design/subsystem.h" #include "threads.h" #include "vec3.h" #include "line3d.h" #include #include "files.h" #include #include #include #include static std::vector shipsets; static umap shipsetIndices; static std::vector hullDefinitions; static umap hullIndices; static unsigned computedHulls = 0; void computeHulls(unsigned amount) { unsigned offset = randomi(0, hullDefinitions.size()-1); for(unsigned n = 0, cnt = hullDefinitions.size(); n < cnt; ++n) { if(hullDefinitions[(offset+n)%cnt]->calculateImpacts()) { ++computedHulls; if(computedHulls >= hullDefinitions.size()) return; --amount; if(amount == 0) return; } } } bool isFinishedComputingHulls() { return computedHulls >= hullDefinitions.size(); } unsigned getHullCount() { return (unsigned)hullDefinitions.size(); } const HullDef* getHullDefinition(unsigned id) { if(id >= hullDefinitions.size()) return 0; return hullDefinitions[id]; } const HullDef* getHullDefinition(const std::string& name) { auto it = hullIndices.find(name); if(it == hullIndices.end()) return 0; return hullDefinitions[it->second]; } HullDef::HullDef() : id(-1), background(0), material(0), mesh(0), iconSheet(0), iconIndex(0), gridSize(16, 8), backgroundScale(1.0), modelScale(1.0), active(gridSize), exterior(gridSize), activeCount(0), exteriorCount(0), minSize(1.0), maxSize(-1.0), baseHull(this), shape(nullptr), special(false) { active.clear(false); exterior.clear(false); } HullDef::HullDef(const HullDef& other) { *this = other; } void clearHullDefinitions() { foreach(it, hullDefinitions) { auto* hull = *it; hull->baseHull = nullptr; hull->drop(); } hullDefinitions.clear(); hullIndices.clear(); computedHulls = 0; } void loadHullDefinitions(const std::string& filename) { std::vector defs; readHullDefinitions(filename, defs); foreach(it, defs) { (*it)->calculateDist(); (*it)->calculateExterior(); (*it)->id = (unsigned)hullDefinitions.size(); hullDefinitions.push_back(*it); hullIndices[(*it)->ident] = (*it)->id; } } bool meshIntersect(const Mesh& mesh, const line3dd& line, vec3d& output) { double closestDist = 1e100; vec3d closestIntersect; for(unsigned i = 0, cnt = mesh.faces.size(); i < cnt; ++i) { const Mesh::Face& face = mesh.faces[i]; vec3d v1 = vec3d(mesh.vertices[face.a].position); vec3d v2 = vec3d(mesh.vertices[face.b].position); vec3d v3 = vec3d(mesh.vertices[face.c].position); vec3d point; if(line.intersectTriangle(v1, v2, v3, point)) { double dist = line.start.distanceToSQ(point); if(dist < closestDist) { closestIntersect = point; closestDist = dist; } } } if(!closestIntersect.zero()) { output = closestIntersect; return true; } return false; } bool HullDef::calculateImpacts() { if(!impacts.empty()) return false; if(!this->mesh) return true; const Mesh& mesh = this->mesh->getMesh(); if(mesh.faces.empty()) return false; //double t = devices.driver->getAccurateTime(); std::vector lineImpacts; lineImpacts.resize(8 * 64); impacts.resize(8 * 64); //Trace the impact lines for(unsigned n = 0; n < 8; ++n) { double theta = (double)n / 8.0 * pi; for(unsigned i = 0; i < 64; ++i) { double phi = (double)i / 64.0 * twopi; double st = sin(theta); vec3d start = vec3d( st * cos(phi), cos(theta), st * sin(phi) ); line3dd ray(start, vec3d(0.0)); vec3d point; if(meshIntersect(mesh, ray, point)) lineImpacts[n*64 + i] = point; else lineImpacts[n*64 + i] = vec3d(); } } //Pre-calculate the closest impact points for angles for(unsigned n = 0; n < 8; ++n) { double theta = (double)n / 8.0 * pi; for(unsigned i = 0; i < 64; ++i) { double phi = (double)i / 64.0 * twopi; double st = sin(theta); vec3d point = vec3d( st * cos(phi), cos(theta), st * sin(phi) ); double d = 1e80; vec3d imp; for(unsigned j = 0, jcnt = lineImpacts.size(); j < jcnt; ++j) { double dist = lineImpacts[j].distanceToSQ(point); if(dist < d) { d = dist; imp = lineImpacts[j]; } } impacts[n*64 + i] = imp; } } //double tend = devices.driver->getAccurateTime(); //print("Impacts %s - %gms", ident.c_str(), (tend-t)*1000.0); return true; } vec3d HullDef::getImpact(const vec3d& offset, double radius, bool constant) const { if(baseHull != nullptr && baseHull != this) return baseHull->getImpact(offset, radius); if(impacts.size() == 0) return offset; vec3d uoffset = offset / radius; double theta = std::fmod(twopi + acos(uoffset.y / uoffset.getLength()), twopi); double phi = std::fmod(twopi + atan2(uoffset.z, uoffset.x), twopi); unsigned row = (unsigned)floor(theta / pi * 8.0 + 0.5) % 8; unsigned index; if(constant) index = ((unsigned)floor(phi / twopi * 64.0 + 0.5)) % 64; else index = ((unsigned)floor(phi / twopi * 64.0 + 0.5) + randomi(-1,1)) % 64; vec3d imp = impacts[row*64 + index]; if(imp.zero()) return offset; return imp * radius; } vec3d HullDef::getClosestImpact(const vec3d& offset) const { if(baseHull != nullptr && baseHull != this) return baseHull->getClosestImpact(offset); if(impacts.size() == 0) return offset; double d = 1e80; vec3d imp; for(unsigned j = 0, jcnt = impacts.size(); j < jcnt; ++j) { double dist = impacts[j].distanceToSQ(offset); if(dist < d) { d = dist; imp = impacts[j]; } } return imp; } void HullDef::calculateExterior() { //Top & Bottom Lines for(unsigned i = 0, cnt = gridSize.x; i < cnt; ++i) { if(i % 2 == 0) { calculateExterior(vec2u(i, 0), HEX_DownLeft); calculateExterior(vec2u(i, 0), HEX_Down); calculateExterior(vec2u(i, 0), HEX_DownRight); calculateExterior(vec2u(i, gridSize.y-1), HEX_Up); } else { calculateExterior(vec2u(i, 0), HEX_Down); calculateExterior(vec2u(i, gridSize.y-1), HEX_UpLeft); calculateExterior(vec2u(i, gridSize.y-1), HEX_Up); calculateExterior(vec2u(i, gridSize.y-1), HEX_UpRight); } } //Right & Left Lines for(unsigned i = 0, cnt = gridSize.y; i < cnt; ++i) { calculateExterior(vec2u(0, i), HEX_DownRight); calculateExterior(vec2u(0, i), HEX_UpRight); calculateExterior(vec2u(gridSize.x-1, i), HEX_DownLeft); calculateExterior(vec2u(gridSize.x-1, i), HEX_UpLeft); } } void HullDef::calculateExterior(vec2u pos, unsigned direction) { unsigned mask = (1<<((direction+3)%6)); do { if((mask & flagExteriorFaux) && !(exterior[pos] & (flagExteriorFaux | flagExteriorPass)) && active[pos]) break; exterior[pos] |= mask; if(active[pos]) { if(exterior[pos] & flagExteriorPass) continue; if(exterior[pos] & flagExteriorFaux) { mask |= flagExteriorFaux; continue; } break; } } while(exterior.advance(pos, (HexGridAdjacency)direction)); } void HullDef::calculateDist() { if(shape == nullptr || shape->format != FMT_RGBA) return; if(shapeMapped) return; //First, make it binary. This is a distance map, so every active //pixel should be 0 and every inactive should be full distance. for(unsigned x = 0; x < shape->width; ++x) { for(unsigned y = 0; y < shape->height; ++y) { Color& col = shape->get_rgba(x, y); if(col.a != 0) col.a = 0; else col.a = 0xff; } } //Calculate distances for(unsigned x = 0; x < shape->width; ++x) { for(unsigned y = 0; y < shape->height; ++y) { if(shape->get_rgba(x, y).a == 0) calculateDist(vec2u(x,y), 0); } } saveImage(shape, shapeMap.c_str()); } void HullDef::calculateDist(vec2u pos, int dist) { assert(dist <= shape->get_rgba(pos.x, pos.y).a); shape->get_rgba(pos.x, pos.y).a = std::min(dist, 255); int wmod = std::max(512 / (int)shape->width, 1); int hmod = std::max(512 / (int)shape->height, 1); if(pos.x > 0) { if(shape->get_rgba(pos.x-1, pos.y).a > dist+wmod) calculateDist(vec2u(pos.x-1, pos.y), dist+wmod); } if(pos.x < shape->width-1) { if(shape->get_rgba(pos.x+1, pos.y).a > dist+wmod) calculateDist(vec2u(pos.x+1, pos.y), dist+wmod); } if(pos.y > 0) { if(shape->get_rgba(pos.x, pos.y-1).a > dist+hmod) calculateDist(vec2u(pos.x, pos.y-1), dist+hmod); } if(pos.y < shape->height-1) { if(shape->get_rgba(pos.x, pos.y+1).a > dist+hmod) calculateDist(vec2u(pos.x, pos.y+1), dist+hmod); } } double HullDef::getMatchDistance(const vec2d& pos) const { if(shape == nullptr) return 128.0; return shape->getTexel(pos.x, pos.y).a; } double HullDef::getMatchDistance(void* descPtr) const { auto& desc = *(Design::Descriptor*)descPtr; double dist = 0.0; vec2u grid = desc.gridSize; unsigned count = grid.x * grid.y; uint8_t* cache; if(count < 2500) cache = (uint8_t*)alloca(count * sizeof(uint8_t)); else cache = (uint8_t*)malloc(count * sizeof(uint8_t)); memset(cache, 0, count * sizeof(uint8_t)); for(size_t i = 0, cnt = desc.systems.size(); i < cnt; ++i) { auto& sys = desc.systems[i]; for(size_t j = 0, jcnt = sys.hexes.size(); j < jcnt; ++j) { vec2u hex = sys.hexes[j]; if(hex.x < grid.x && hex.y < grid.y) { unsigned index = hex.y * grid.x + hex.x; cache[index] = 1; } } } for(unsigned x = 0; x < grid.x; ++x) { for(unsigned y = 0; y < grid.y; ++y) { vec2d pctPos = HexGrid<>::getEffectivePosition(vec2u(x, y)); pctPos.x += 0.75 * 0.5; pctPos.y += 0.5; pctPos.x /= ((double)grid.x) * 0.75; pctPos.y /= (double)grid.y; double d = getMatchDistance(pctPos); unsigned index = y * grid.x + x; if(cache[index]) dist += d*d; else dist += 0.5 * (255.0 - d); } } if(count >= 2500) free(cache); return dist; } bool HullDef::checkConnected() { HexGrid connected(active.width, active.height); connected.clear(false); bool found = false; for(unsigned x = 0; x < active.width && !found; ++x) { for(unsigned y = 0; y < active.height && !found; ++y) { if(active.get(x,y)) { fillConnected(connected, vec2u(x,y)); found = true; } } } for(unsigned x = 0; x < active.width; ++x) { for(unsigned y = 0; y < active.height; ++y) { if(active.get(x,y) && !connected.get(x,y)) return false; } } return true; } void HullDef::fillConnected(HexGrid& connected, vec2u pos) { connected[pos] = true; for(unsigned i = 0; i < 6; ++i) { vec2u hex = pos; if(active.advance(hex, (HexGridAdjacency)i)) { if(active[hex] && !connected[hex]) fillConnected(connected, hex); } } } bool HullDef::isExterior(const vec2u& hex) const { if(!exterior.valid(hex)) return false; return exterior.get(hex) & 0x00ffffff; } bool HullDef::isExteriorInDirection(const vec2u& hex, unsigned dir) const { if(!exterior.valid(hex)) return false; return exterior.get(hex) & (1<& hulls) { HullDef* def = 0; int x = 0, y = 0; DataHandler datahandler; datahandler.lineHandler([&](std::string& line) { if(!def) return; if(y >= def->gridSize.height) { error("Error: Too many rows for hull '%s'.", def->ident.c_str()); return; } x = 0; line = trim(line, "\r\n\t"); unsigned cnt = (unsigned)line.size(); for(unsigned i = 0; i < cnt; ++i) { if(x >= def->gridSize.width) { error("Error: Too many characters on row for hull '%s'.", def->ident.c_str()); break; } bool& active = def->active.get(x, y); int& exterior = def->exterior.get(x, y); switch(line[i]) { case '-': active = false; exterior = 0; ++x; break; case 'X': case 'x': active = true; exterior = 0; ++def->activeCount; ++x; break; case '#': active = true; exterior = 0xff; ++def->activeCount; ++def->exteriorCount; ++x; break; case ' ': break; default: error("Error: Invalid character '%c' on row for hull '%s'.", line[i], def->ident.c_str()); break; } } ++y; }); datahandler("Hull", [&](std::string& value) { def = new HullDef(); def->ident = value; def->name = "__"+def->ident+"__"; hulls.push_back(def); x = 0; y = 0; }); datahandler("Name", [&](std::string& value) { if(!def) return; def->name = devices.locale.localize(value); }); datahandler("Tags", [&](std::string& value) { if(!def) return; split(value, def->tags, ',', true); for(size_t i = 0, cnt = def->tags.size(); i < cnt; ++i) def->numTags.insert(getSysTagIndex(def->tags[i], true)); }); datahandler("Subsystem", [&](std::string& value) { if(!def) return; def->subsystems.push_back(value); }); datahandler("Background", [&](std::string& value) { if(!def) return; const render::RenderState* bg = &devices.library.getMaterial(value); if(!bg) { error("(Hull %s): Error: Unknown material %s.", def->ident.c_str(), value.c_str()); return; } def->background = bg; def->backgroundName = value; }); datahandler("BackgroundScale", [&](std::string& value) { if(!def) return; def->backgroundScale = toNumber(value); }); datahandler("ModelScale", [&](std::string& value) { if(!def) return; def->modelScale = toNumber(value); }); datahandler("Material", [&](std::string& value) { if(!def) return; const render::RenderState* mat = &devices.library.getMaterial(value); if(!mat) { error("(Hull %s): Error: Unknown material %s.", def->ident.c_str(), value.c_str()); return; } def->materialName = value; def->material = mat; }); datahandler("Model", [&](std::string& value) { if(!def) return; const render::RenderMesh* mesh = &devices.library.getMesh(value); if(!mesh) { if(devices.render) error("(Hull %s): Error: Unknown model %s.", def->ident.c_str(), value.c_str()); return; } def->meshName = value; def->mesh = mesh; }); datahandler("Shape", [&](std::string& value) { if(!def) return; std::string fname = devices.mods.resolve(value); def->shapeMap = fname+".map.png"; if(fileExists(def->shapeMap)) { def->shape = loadImage(def->shapeMap.c_str()); def->shapeMapped = true; } else { def->shape = loadImage(fname.c_str()); def->shapeMapped = false; } }); datahandler("GridSize", [&](std::string& value) { if(!def) return; std::vector args; split(value, args, ','); if(args.size() != 2) { error("(Hull %s): Error: Invalid grid size specification.", def->ident.c_str()); return; } def->gridSize = vec2i(toNumber(args[0]), toNumber(args[1])); def->active.resize(def->gridSize); def->active.clear(false); def->exterior.resize(def->gridSize); def->exterior.clear(0); }); datahandler("GridOffset", [&](std::string& value) { if(!def) return; std::vector args; split(value, args, ','); if(args.size() != 4) { error("(Hull %s): Error: Invalid grid offset specification.", def->ident.c_str()); return; } def->gridOffset = recti(toNumber(args[0]), toNumber(args[1]), toNumber(args[2]), toNumber(args[3])); }); datahandler("GuiIcon", [&](std::string& value) { if(def) { def->guiIcon = devices.library.getSprite(value); def->guiIconName = value; } }); datahandler("FleetIcon", [&](std::string& value) { if(def) { def->fleetIcon = devices.library.getSprite(value); def->fleetIconName = value; } }); datahandler("IconSheet", [&](std::string& value) { if(def) { def->iconSheet = &devices.library.getSpriteSheet(value); def->iconName = value; } }); datahandler("IconIndex", [&](std::string& value) { if(def) def->iconIndex = toNumber(value); }); datahandler("MinSize", [&](std::string& value) { if(def) def->minSize = toNumber(value); }); datahandler("MaxSize", [&](std::string& value) { if(def) def->maxSize = toNumber(value); }); datahandler("Special", [&](std::string& value) { if(def) def->special = toBool(value); }); datahandler.read(filename); } bool HullDef::hasTag(const std::string& tag) const { auto it = std::find(tags.begin(), tags.end(), tag); return it != tags.end(); } void writeHullDefinitions(const std::string& filename, std::vector& hulls) { std::ofstream file(filename); foreach(it, hulls) { const HullDef* def = *it; file << "Hull: " << def->ident << "\n"; file << "\tName: " << def->name << "\n"; file << "\tBackground: " << def->backgroundName << "\n"; if(def->backgroundScale != 1.0) file << "\tBackgroundScale: " << def->backgroundScale << "\n"; file << "\tMaterial: " << def->materialName << "\n"; file << "\tModel: " << def->meshName << "\n"; file << "\tGuiIcon: " << def->guiIconName << "\n"; if(!def->fleetIconName.empty()) file << "\tFleetIcon: " << def->fleetIconName << "\n"; file << "\tIconSheet: " << def->iconName << "\n"; file << "\tIconIndex: " << def->iconIndex << "\n"; file << "\n"; if(!def->tags.empty()) { file << "\tTags: "; bool first = true; for(auto t = def->tags.begin(), tend = def->tags.end(); t != tend; ++t) { if(!first) file << ", "; first = false; file << *t; } file << "\n\n"; } file << "\tGridSize: " << def->gridSize.x << ", " << def->gridSize.y << "\n"; file << "\tGridOffset: " << def->gridOffset.topLeft.x << "," << def->gridOffset.topLeft.y; file << ", " << def->gridOffset.botRight.x << "," << def->gridOffset.botRight.y << "\n"; file << "\n"; for(int y = 0; y < def->gridSize.y; ++y) { file << "\t"; for(int x = 0; x < def->gridSize.x; ++x) { if(x != 0) file << " "; if(def->active.get(x, y)) { if(def->exterior.get(x, y)) { file << "#"; } else { file << "X"; } } else { file << "-"; } } file << "\n"; } file << "\n"; } } unsigned Shipset::getHullCount() const { return hulls.size(); } ShipSkin* Shipset::getSkin(const std::string& name) const { auto it = skins.find(name); if(it == skins.end()) return nullptr; return it->second; } Shipset::~Shipset() { for(auto it = skins.begin(); it != skins.end(); ++it) delete it->second; } const HullDef* Shipset::getHull(unsigned index) const { if(index >= hulls.size()) return nullptr; return hulls[index]; } const HullDef* Shipset::getHull(const std::string& ident) const { for(auto i = hulls.begin(), end = hulls.end(); i != end; ++i) if((*i)->ident == ident) return *i; return nullptr; } bool Shipset::hasHull(const HullDef* hull) const { for(auto i = hulls.begin(), end = hulls.end(); i != end; ++i) if(*i == hull) return true; return false; } void loadShipset(const std::string& filename) { Shipset* set = nullptr; ShipSkin* skin = nullptr; DataHandler datahandler; datahandler("Shipset", [&](std::string& value) { if(shipsetIndices.find(value) != shipsetIndices.end()) error("Duplicate Shipset ID: %s", value.c_str()); set = new Shipset(); set->ident = value; set->available = true; set->id = (unsigned)shipsets.size(); shipsetIndices[value] = set->id; shipsets.push_back(set); }); datahandler("Name", [&](std::string& value) { if(set) set->name = value; }); datahandler("DLC", [&](std::string& value) { if(set) set->dlc = value; }); datahandler("Available", [&](std::string& value) { if(set) set->available = toBool(value, true); }); datahandler("Hull", [&](std::string& value) { if(set) { auto* hull = getHullDefinition(value); if(hull) set->hulls.push_back(hull); else error("Could not find hull %s for shipset %s", value.c_str(), set->ident.c_str()); } }); datahandler("Skin", [&](std::string& value) { if(set) { skin = new ShipSkin(); skin->ident = value; set->skins[value] = skin; } }); datahandler("Model", [&](std::string& value) { if(skin && set) { const render::RenderMesh* mesh = &devices.library.getMesh(value); if(!mesh) { if(devices.render) error("(%s Ship Skin %s): Error: Unknown model %s.", set->ident.c_str(), skin->ident.c_str(), value.c_str()); return; } skin->mesh = mesh; } }); datahandler("Material", [&](std::string& value) { if(skin && set) { const render::RenderState* mat = &devices.library.getMaterial(value); if(!mat) { if(devices.render) error("(%s Ship Skin %s): Error: Unknown material %s.", set->ident.c_str(), skin->ident.c_str(), value.c_str()); return; } skin->material = mat; } }); datahandler("Icon", [&](std::string& value) { if(skin && set) { skin->icon = devices.library.getSprite(value); } }); datahandler.read(filename); } void initAllShipset() { auto* set = new Shipset(); set->ident = "ALL"; set->available = false; set->id = (unsigned)shipsets.size(); for(size_t i = 0, cnt = shipsets.size(); i < cnt; ++i) { auto* other = shipsets[i]; if(!other->available) continue; for(size_t n = 0, ncnt = other->hulls.size(); n < ncnt; ++n) set->hulls.push_back(other->hulls[n]); } shipsetIndices[set->ident] = set->id; shipsets.push_back(set); } void clearShipsets() { shipsets.clear(); shipsets.shrink_to_fit(); shipsetIndices.clear(); } unsigned getShipsetCount() { return (unsigned)shipsets.size(); } const Shipset* getShipset(unsigned id) { if(id >= (unsigned)shipsets.size()) return nullptr; return shipsets[id]; } const Shipset* getShipset(const std::string& ident) { auto i = shipsetIndices.find(ident); if(i == shipsetIndices.end()) return nullptr; return shipsets[i->second]; }