extremetuxracer/src/course.cpp

1017 lines
28 KiB
C++

/* --------------------------------------------------------------------
EXTREME TUXRACER
Copyright (C) 1999-2001 Jasmin F. Patry (Tuxracer)
Copyright (C) 2010 Extreme Tuxracer Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
---------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#include <etr_config.h>
#endif
#include "bh.h"
#include "depot.h"
#include "course.h"
#include "textures.h"
#include "ogl.h"
#include "audio.h"
#include "track_marks.h"
#include "spx.h"
#include "quadtree.h"
#include "env.h"
#include "game_ctrl.h"
#include "font.h"
#include "physics.h"
#include "winsys.h"
#include "translation.h"
#include <cmath>
#include <algorithm>
#include <iterator>
void TCourse::SetDescription(const std::string& description) {
FT.AutoSizeN(2);
std::vector<std::string> desclist = FT.MakeLineList(description.c_str(), 335.f * Winsys.scale - 16.f);
std::size_t cnt = std::min<std::size_t>(desclist.size(), MAX_DESCRIPTION_LINES);
num_lines = cnt;
for (std::size_t ll = 0; ll < cnt; ll++) {
desc[ll] = desclist[ll];
}
}
void TCourse::SetTranslatedData(const std::string& line2) {
std::string description = SPStrN(line2, "desc-" + Trans.languages[param.language].lang);
std::string trans_name = SPStrN(line2, "name-" + Trans.languages[param.language].lang);
if (description.empty()) // No translated description - fallback to default
description = SPStrN(line2, "desc");
if (!description.empty())
SetDescription(description);
if (!trans_name.empty())
name = trans_name;
}
CCourse Course;
CCourse::CCourse()
: curr_course(nullptr)
, nx(0), ny(0)
, base_height_value(0)
, mirrored(false)
, currentCourseList(nullptr)
, vnc_array(nullptr) {
}
CCourse::~CCourse() {
for (std::unordered_map<std::string, CCourseList>::iterator i = CourseLists.begin(); i != CourseLists.end(); ++i)
i->second.Free();
ResetCourse();
}
double CCourse::GetBaseHeight(double distance) const {
double slope = std::tan(ANGLES_TO_RADIANS(curr_course->angle));
return -slope * distance -
base_height_value / 255.0 * curr_course->scale;
}
double CCourse::GetMaxHeight(double distance) const {
return GetBaseHeight(distance) + curr_course->scale;
}
const TPolyhedron& CCourse::GetPoly(std::size_t type) const {
return PolyArr[ObjTypes[type].poly];
}
TCourse* CCourse::GetCourse(const std::string& group, const std::string& dir) {
return &CourseLists.at(group)[dir];
}
std::size_t CCourse::GetCourseIdx(const TCourse* course) const {
std::size_t idx = (course - &(*currentCourseList)[0]);
if (idx >= currentCourseList->size())
return -1;
return idx;
}
void CCourse::CalcNormals() {
for (unsigned int y = 0; y < ny; y++) {
for (unsigned int x = 0; x < nx; x++) {
TVector3d nml(0.0, 0.0, 0.0);
TVector3d p0 = NMLPOINT(x,y);
if ((x + y) % 2 == 0) {
if (x > 0 && y > 0) {
TVector3d p1 = NMLPOINT(x, y-1);
TVector3d p2 = NMLPOINT(x-1,y-1);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v2, v1);
n.Norm();
nml += n;
p1 = NMLPOINT(x-1, y-1);
p2 = NMLPOINT(x-1, y);
v1 = p1 - p0;
v2 = p2 - p0;
n = CrossProduct(v2, v1);
n.Norm();
nml += n;
}
if (x > 0 && y < ny-1) {
TVector3d p1 = NMLPOINT(x-1,y);
TVector3d p2 = NMLPOINT(x-1,y+1);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v2, v1);
n.Norm();
nml += n;
p1 = NMLPOINT(x-1,y+1);
p2 = NMLPOINT(x ,y+1);
v1 = p1 - p0;
v2 = p2 - p0;
n = CrossProduct(v2, v1);
n.Norm();
nml += n;
}
if (x < nx-1 && y > 0) {
TVector3d p1 = NMLPOINT(x+1,y);
TVector3d p2 = NMLPOINT(x+1,y-1);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v2, v1);
n.Norm();
nml += n;
p1 = NMLPOINT(x+1,y-1);
p2 = NMLPOINT(x ,y-1);
v1 = p1 - p0;
v2 = p2 - p0;
n = CrossProduct(v2, v1);
n.Norm();
nml += n;
}
if (x < nx-1 && y < ny-1) {
TVector3d p1 = NMLPOINT(x+1,y);
TVector3d p2 = NMLPOINT(x+1,y+1);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v1, v2);
n.Norm();
nml += n;
p1 = NMLPOINT(x+1,y+1);
p2 = NMLPOINT(x ,y+1);
v1 = p1 - p0;
v2 = p2 - p0;
n = CrossProduct(v1, v2);
n.Norm();
nml += n;
}
} else {
if (x > 0 && y > 0) {
TVector3d p1 = NMLPOINT(x, y-1);
TVector3d p2 = NMLPOINT(x-1,y);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v2, v1);
n.Norm();
nml += n;
}
if (x > 0 && y < ny-1) {
TVector3d p1 = NMLPOINT(x-1,y);
TVector3d p2 = NMLPOINT(x ,y+1);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v2, v1);
n.Norm();
nml += n;
}
if (x < nx-1 && y > 0) {
TVector3d p1 = NMLPOINT(x+1,y);
TVector3d p2 = NMLPOINT(x ,y-1);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v2, v1);
n.Norm();
nml += n;
}
if (x < nx-1 && y < ny-1) {
TVector3d p1 = NMLPOINT(x+1,y);
TVector3d p2 = NMLPOINT(x ,y+1);
TVector3d v1 = p1 - p0;
TVector3d v2 = p2 - p0;
TVector3d n = CrossProduct(v1, v2);
n.Norm();
nml += n;
}
}
nml.Norm();
Fields[x + nx * y].nml = nml;
continue;
}
}
}
void CCourse::MakeCourseNormals() {
CalcNormals();
}
// --------------------------------------------------------------------
// FillGlArrays
// --------------------------------------------------------------------
void CCourse::FillGlArrays() {
if (vnc_array == nullptr)
vnc_array = new GLubyte[STRIDE_GL_ARRAY * nx * ny];
for (unsigned int x = 0; x < nx; x++) {
for (unsigned int y = 0; y < ny; y++) {
int idx = STRIDE_GL_ARRAY * (y * nx + x);
FLOATVAL(0) = (GLfloat)x / (nx-1.f) * curr_course->size.x;
FLOATVAL(1) = Fields[x + nx*y].elevation;
FLOATVAL(2) = -(GLfloat)y / (ny-1.f) * curr_course->size.y;
const TVector3d& nml = Fields[ x + y * nx ].nml;
FLOATVAL(4) = nml.x;
FLOATVAL(5) = nml.y;
FLOATVAL(6) = nml.z;
FLOATVAL(7) = 1.0f;
BYTEVAL(0) = 255;
BYTEVAL(1) = 255;
BYTEVAL(2) = 255;
BYTEVAL(3) = 255;
}
}
}
void CCourse::MakeStandardPolyhedrons() {
PolyArr.resize(2);
// polyhedron "none"
// poyhedron "tree"
PolyArr[1].vertices = {
TVector3d(0, 0, 0),
TVector3d(0, 0.15, 0.5),
TVector3d(0.5, 0.15, 0),
TVector3d(0, 0.15, -0.5),
TVector3d(-0.5, 0.15, 0),
TVector3d(0, 1, 0)
};
PolyArr[1].polygons = {
{ { 0, 1, 4 } },
{ { 0, 2, 1 } },
{ { 0, 3, 2 } },
{ { 0, 4, 3 } },
{ { 1, 5, 4 } },
{ { 2, 5, 1 } },
{ { 3, 5, 2 } },
{ { 4, 5, 3 } },
};
}
void CCourse::FreeTerrainTextures() {
for (std::size_t i=0; i<TerrList.size(); i++) {
delete TerrList[i].texture;
TerrList[i].texture = nullptr;
}
}
void CCourse::FreeObjectTextures() {
for (std::size_t i=0; i<ObjTypes.size(); i++) {
delete ObjTypes[i].texture;
ObjTypes[i].texture = nullptr;
}
}
// --------------------------------------------------------------------
// LoadElevMap
// --------------------------------------------------------------------
bool CCourse::LoadElevMap() {
sf::Image img;
auto path = DepotMustResolveFile(CourseDir + "elev.png");
if (!img.loadFromFile(path)) {
Message("unable to open elev.png");
return false;
}
img.flipVertically();
// Get size of course from elevation map
nx = img.getSize().x;
ny = img.getSize().y;
Fields.resize(nx*ny);
double slope = std::tan(ANGLES_TO_RADIANS(curr_course->angle));
int pad = 0;
int depth = 4;
const uint8_t* data = img.getPixelsPtr();
for (unsigned int y = 0; y < ny; y++) {
for (unsigned int x = 0; x < nx; x++) {
Fields[(nx - 1 - x) + nx * (ny - 1 - y)].elevation =
((data[(x + nx*y) * depth + pad]
- base_height_value) / 255.0) * curr_course->scale
- (double)(ny-1-y) / ny * curr_course->size.y * slope;
}
pad += (nx * depth) % 4;
}
return true;
}
// ====================================================================
// LoadItemList
// ====================================================================
void CCourse::LoadItemList() {
if (ObjTypes.empty()) {
Message("No object types loaded.");
return;
}
CSPList list;
if (!list.LoadDepot(CourseDir + "items.lst")) {
Message("could not load items list");
return;
}
CollArr.clear();
NocollArr.clear();
for (CSPList::const_iterator line = list.cbegin(); line != list.cend(); ++line) {
int x = SPIntN(*line, "x", 0);
int z = SPIntN(*line, "z", 0);
double height = SPFloatN(*line, "height", 1);
double diam = SPFloatN(*line, "diam", 1);
double xx = (nx - x) / (double)((double)nx - 1.0) * curr_course->size.x;
double zz = -(int)(ny - z) / (double)((double)ny - 1.0) * curr_course->size.y;
std::string name = SPStrN(*line, "name");
std::size_t type = ObjectIndex[name];
if (ObjTypes[type].texture == nullptr && ObjTypes[type].drawable) {
ObjTypes[type].texture = new TTexture();
ObjTypes[type].texture->LoadDepot("//data/objects/" + ObjTypes[type].textureFile, false);
}
if (ObjTypes[type].collidable)
CollArr.emplace_back(xx, FindYCoord(xx, zz), zz, height, diam, type);
else
NocollArr.emplace_back(xx, FindYCoord(xx, zz), zz, height, diam, ObjTypes[type]);
}
std::sort(CollArr.begin(), CollArr.end(), [](const TCollidable& l, const TCollidable& r) -> bool {
return l.tree_type < r.tree_type;
});
}
// -------------------- LoadObjectMap ---------------------------------
static int GetObject(const unsigned char* pixel) {
int r = pixel[0];
int g = pixel[1];
int b = pixel[2];
if (r<150 && b>200) return 0;
if (std::abs(r-194)<10 && std::abs(g-40)<10 && std::abs(b-40)<10) return 1;
if (std::abs(r-128)<10 && std::abs(g-128)<10 && b<10) return 2;
if (r>220 && g>220 && b<20) return 3;
if (r>220 && std::abs(g-128)<10 && b>220) return 4;
if (r>220 && g>220 && b>220) return 5;
if (r>220 && std::abs(g-96)<10 && b<40) return 6;
if (r<40 && g >220 && b<80) return 7;
return -1;
}
#define TREE_MIN 2.0
#define TREE_MAX 5.0
#define BARREN_MIN 4.0
#define BARREN_MAX 8.0
#define SHRUB_MIN 1.0
#define SHRUB_MAX 2.0
const double sizefact[6] = {0.5, 0.5, 0.7, 1.0, 1.4, 2.0};
const double varfact[6] = {1.0, 1.0, 1.22, 1.41, 1.73, 2.0};
const double diamfact = 1.4;
static void CalcRandomTrees(double baseheight, double basediam, double &height, double &diam) {
double hhh = baseheight * sizefact[g_game.treesize];
double minsiz = hhh / varfact[g_game.treevar];
double maxsiz = hhh * varfact[g_game.treevar];
height = XRandom(minsiz, maxsiz);
diam = XRandom(height/diamfact, height);
}
//bool CCourse::LoadAndConvertObjectMap() {
// sf::Image treeImg;
//
// if (!treeImg.loadFromDepot(CourseDir + "trees.png")) {
// Message("unable to open trees.png");
// return false;
// }
// treeImg.flipVertically();
//
// int pad = 0;
// int cnt = 0;
// int depth = 4;
// const unsigned char* data = (unsigned char*)treeImg.getPixelsPtr();
// double height, diam;
// CSPList savelist;
//
// CollArr.clear();
// NocollArr.clear();
// for (unsigned int y = 0; y < ny; y++) {
// for (unsigned int x = 0; x < nx; x++) {
// int imgidx = (x + nx * y) * depth + pad;
// int type = GetObject(&data[imgidx]);
// if (type >= 0) {
// cnt++;
// double xx = (nx - x) / (double)((double)nx - 1.0) * curr_course->size.x;
// double zz = -(int)(ny - y) / (double)((double)ny - 1.0) * curr_course->size.y;
// if (ObjTypes[type].texture == nullptr && ObjTypes[type].drawable) {
// ObjTypes[type].texture = new TTexture();
// ObjTypes[type].texture->LoadDepot("//data/objects/" + ObjTypes[type].textureFile, false);
// }
//
// // set random height and diam - see constants above
// switch (type) {
// case 5:
// CalcRandomTrees(2.5, 2.5, height, diam);
// break;
// case 6:
// CalcRandomTrees(3, 3, height, diam);
// break;
// case 7:
// CalcRandomTrees(1.2, 1.2, height, diam);
// break;
//
// case 2:
// case 3:
// height = 6.0;
// diam = 9.0;
// break;
//
// default:
// height = 1;
// diam = 1;
// break;
// }
//
// if (ObjTypes[type].collidable)
// CollArr.emplace_back(xx, FindYCoord(xx, zz), zz, height, diam, type);
// else
// NocollArr.emplace_back(xx, FindYCoord(xx, zz), zz, height, diam, ObjTypes[type]);
//
// std::string line = "*[name]";
// line += ObjTypes[type].name;
// SPSetIntN(line, "x", x);
// SPSetIntN(line, "z", y);
// SPSetFloatN(line, "height", height, 1);
// SPSetFloatN(line, "diam", diam, 1);
// savelist.Add(line);
// }
// }
// pad += (nx * depth) % 4;
// }
// std::string itemfile = CourseDir + SEP "items.lst";
// savelist.Save(itemfile); // Convert trees.png to items.lst
// return true;
//}
// --------------------------------------------------------------------
// LoadObjectTypes
// --------------------------------------------------------------------
bool CCourse::LoadObjectTypes() {
CSPList list;
if (!list.LoadDepot("//data/objects/object_types.lst")) {
Message("could not load object types");
return false;
}
ObjTypes.resize(list.size());
std::size_t i = 0;
for (CSPList::const_iterator line = list.cbegin(); line != list.cend(); ++line, i++) {
ObjTypes[i].name = SPStrN(*line, "name");
ObjTypes[i].textureFile = ObjTypes[i].name;
ObjTypes[i].texture = nullptr;
ObjTypes[i].drawable = SPBoolN(*line, "draw", true);
if (ObjTypes[i].drawable) {
ObjTypes[i].textureFile = SPStrN(*line, "texture");
}
ObjTypes[i].collectable = SPBoolN(*line, "snap", true) != 0;
if (ObjTypes[i].collectable == 0) {
ObjTypes[i].collectable = -1;
}
ObjTypes[i].collidable = SPBoolN(*line, "coll", false);
ObjTypes[i].reset_point = SPBoolN(*line, "reset", false);
ObjTypes[i].use_normal = SPBoolN(*line, "usenorm", false);
if (ObjTypes[i].use_normal) {
ObjTypes[i].normal = SPVector3(*line, "norm", TVector3d(0, 1, 0));
ObjTypes[i].normal.Norm();
}
ObjTypes[i].poly = 1;
}
list.MakeIndex(ObjectIndex, "name");
return true;
}
// ====================================================================
// Terrain
// ====================================================================
int CCourse::GetTerrain(const unsigned char* pixel) const {
for (std::size_t i=0; i<TerrList.size(); i++) {
if (std::abs(pixel[0]-TerrList[i].col.r) < 30
&& std::abs(pixel[1]-TerrList[i].col.g) < 30
&& std::abs(pixel[2]-TerrList[i].col.b) < 30) {
return (int)i;
}
}
return 0;
}
// --------------------------------------------------------------------
// LoadTerrainTypes
// --------------------------------------------------------------------
bool CCourse::LoadTerrainTypes() {
CSPList list;
if (!list.LoadDepot("//data/terrains/terrains.lst")) {
Message("could not load terrain types");
return false;
}
TerrList.resize(list.size());
std::size_t i = 0;
for (CSPList::const_iterator line = list.cbegin(); line != list.cend(); ++line, i++) {
TerrList[i].textureFile = SPStrN(*line, "texture");
TerrList[i].sound = Sound.GetSoundIdx(SPStrN(*line, "sound"));
TerrList[i].starttex = SPIntN(*line, "starttex", -1);
TerrList[i].tracktex = SPIntN(*line, "tracktex", -1);
TerrList[i].stoptex = SPIntN(*line, "stoptex", -1);
TerrList[i].col = SPColor3N(*line, "col", sf::Color::White);
TerrList[i].friction = SPFloatN(*line, "friction", 0.5f);
TerrList[i].depth = SPFloatN(*line, "depth", 0.01f);
TerrList[i].particles = SPBoolN(*line, "part", false);
TerrList[i].trackmarks = SPBoolN(*line, "trackmarks", false);
TerrList[i].texture = nullptr;
TerrList[i].shiny = SPBoolN(*line, "shiny", false);
TerrList[i].vol_type = SPIntN(*line, "vol_type", 1);
}
return true;
}
// --------------------------------------------------------------------
// LoadTerrainMap
// --------------------------------------------------------------------
bool CCourse::LoadTerrainMap() {
sf::Image terrImage;
auto path = DepotMustResolveFile(CourseDir + "terrain.png");
if (!terrImage.loadFromFile(path)) {
Message("unable to open terrain.png");
return false;
}
terrImage.flipVertically();
if (nx != terrImage.getSize().x || ny != terrImage.getSize().y) {
Message("wrong terrain size");
}
int depth = 4;
const unsigned char* data = (const unsigned char*) terrImage.getPixelsPtr();
int pad = 0;
for (unsigned int y = 0; y < ny; y++) {
for (unsigned int x = 0; x < nx; x++) {
int imgidx = (x+nx*y) * depth + pad;
int arridx = (nx-1-x) + nx * (ny-1-y);
int terr = GetTerrain(&data[imgidx]);
Fields[arridx].terrain = terr;
if (TerrList[terr].texture == nullptr) {
TerrList[terr].texture = new TTexture();
TerrList[terr].texture->LoadDepot("//data/terrains/" + TerrList[terr].textureFile, true);
}
}
pad += (nx * depth) % 4;
}
return true;
}
// --------------------------------------------------------------------
// CCourseList
// --------------------------------------------------------------------
bool CCourseList::Load(const std::string& dir) {
CSPList list;
if (!list.LoadDepot(dir + "courses.lst")) {
Message("could not load courses.lst");
return false;
}
CSPList paramlist;
courses.resize(list.size());
std::size_t i = 0;
for (CSPList::const_iterator line1 = list.cbegin(); line1 != list.cend(); ++line1, i++) {
courses[i].name = SPStrN(*line1, "name");
courses[i].dir = SPStrN(*line1, "dir", "nodir");
std::string coursepath = dir + courses[i].dir + "/";
std::string previewfile = coursepath + "preview.png";
courses[i].preview = new TTexture();
if (!courses[i].preview->LoadDepot(previewfile, false)) {
Message("couldn't load previewfile");
}
// params
std::string paramfile = coursepath + "course.dim";
if (!paramlist.LoadDepot(paramfile)) {
Message("could not load course.dim");
}
const std::string& line2 = paramlist.front();
courses[i].author = SPStrN(line2, "author", Trans.Text(109));
courses[i].size.x = SPFloatN(line2, "width", 100);
courses[i].size.y = SPFloatN(line2, "length", 1000);
courses[i].play_size.x = SPFloatN(line2, "play_width", 90);
courses[i].play_size.y = SPFloatN(line2, "play_length", 900);
courses[i].angle = SPFloatN(line2, "angle", 10);
courses[i].scale = SPFloatN(line2, "scale", 10);
courses[i].start.x = SPFloatN(line2, "startx", 50);
courses[i].start.y = SPFloatN(line2, "starty", 5);
courses[i].env = Env.GetEnvIdx(SPStrN(line2, "env", "etr"));
courses[i].music_theme = Music.GetThemeIdx(SPStrN(line2, "theme", "normal"));
courses[i].use_keyframe = SPBoolN(line2, "use_keyframe", false);
courses[i].finish_brake = SPFloatN(line2, "finish_brake", 20);
if (paramlist.size() >= 2)
courses[i].SetTranslatedData(paramlist.back());
paramlist.clear(); // the list is used several times
}
list.MakeIndex(index, "dir");
return true;
}
void CCourseList::Free() {
for (std::size_t i = 0; i < courses.size(); i++) {
delete courses[i].preview;
}
courses.clear();
}
void CCourse::FreeCourseList() {
for (std::unordered_map<std::string, CCourseList>::iterator i = CourseLists.begin(); i != CourseLists.end(); ++i)
i->second.Free();
}
bool CCourse::LoadCourseList() {
CSPList list;
if (!list.LoadDepot("//data/courses/groups.lst")) {
Message("could not load groups.lst");
return false;
}
for (CSPList::const_iterator line = list.cbegin(); line != list.cend(); ++line) {
std::string dir = SPStrN(*line, "dir", "nodir");
CourseLists[dir].Load("//data/courses/" + dir + "/");
CourseLists[dir].name = dir;
}
currentCourseList = &CourseLists["default"];
return true;
}
CCourseList* CCourse::getGroup(std::size_t index) {
std::unordered_map<std::string, CCourseList>::iterator i = CourseLists.begin();
std::advance(i, index);
return &i->second;
}
// ===================================================================
// LoadCourse
// ===================================================================
void CCourse::ResetCourse() {
Fields.clear();
delete[] vnc_array;
vnc_array = nullptr;
FreeTerrainTextures();
FreeObjectTextures();
ResetQuadtree();
curr_course = nullptr;
mirrored = false;
}
bool CCourse::LoadCourse(TCourse* course) {
if (course != curr_course || g_game.force_treemap) {
ResetCourse();
curr_course = course;
CourseDir = "//data/courses/" + currentCourseList->name + "/" + curr_course->dir + "/";
start_pt.x = course->start.x;
start_pt.y = -course->start.y;
base_height_value = 127;
g_game.use_keyframe = course->use_keyframe;
g_game.finish_brake = course->finish_brake;
if (!LoadElevMap()) {
Message("could not load course elev map");
return false;
}
MakeCourseNormals();
FillGlArrays();
if (!LoadTerrainMap()) {
Message("could not load course terrain map");
return false;
}
// ................................................................
std::string itemfile = DepotMustResolveFile(CourseDir + "items.lst");
bool itemsexists = FileExists(itemfile);
const CControl *ctrl = g_game.player->ctrl;
if (itemsexists && !g_game.force_treemap)
LoadItemList();
else {
Message("Cannot convert");
exit(1);
}
g_game.force_treemap = false;
// ................................................................
init_track_marks();
InitQuadtree(
&Fields[0], nx, ny,
curr_course->size.x / (nx - 1.0),
-curr_course->size.y / (ny - 1.0),
ctrl->viewpos,
param.course_detail_level);
}
if (g_game.mirrorred != mirrored) {
MirrorCourse();
mirrored = g_game.mirrorred;
}
return true;
}
std::size_t CCourse::GetEnv() const {
return curr_course->env;
}
// --------------------------------------------------------------------
// mirror course
// --------------------------------------------------------------------
void CCourse::MirrorCourseData() {
for (unsigned int y = 0; y < ny; y++) {
for (unsigned int x = 0; x < nx / 2; x++) {
double tmp = ELEV(x,y);
ELEV(x,y) = ELEV(nx-1-x, y);
ELEV(nx-1-x,y) = tmp;
int idx1 = (x+1) + nx*(y);
int idx2 = (nx-1-x) + nx*(y);
std::swap(Fields[idx1].terrain, Fields[idx2].terrain);
idx1 = (x) + nx*(y);
idx2 = (nx-1-x) + nx*(y);
std::swap(Fields[idx1].nml, Fields[idx2].nml);
Fields[idx1].nml.x *= -1;
Fields[idx2].nml.x *= -1;
}
}
for (std::size_t i=0; i<CollArr.size(); i++) {
CollArr[i].pt.x = curr_course->size.x - CollArr[i].pt.x;
CollArr[i].pt.y = FindYCoord(CollArr[i].pt.x, CollArr[i].pt.z);
}
for (std::size_t i=0; i<NocollArr.size(); i++) {
NocollArr[i].pt.x = curr_course->size.x - NocollArr[i].pt.x;
NocollArr[i].pt.y = FindYCoord(NocollArr[i].pt.x, NocollArr[i].pt.z);
}
FillGlArrays();
ResetQuadtree();
if (nx > 0 && ny > 0) {
const CControl *ctrl = g_game.player->ctrl;
InitQuadtree(&Fields[0], nx, ny, curr_course->size.x/(nx-1),
- curr_course->size.y/(ny-1), ctrl->viewpos, param.course_detail_level);
}
start_pt.x = curr_course->size.x - start_pt.x;
}
void CCourse::MirrorCourse() {
MirrorCourseData();
init_track_marks();
}
// ********************************************************************
// from phys_sim:
// ********************************************************************
void CCourse::GetIndicesForPoint(double x, double z, unsigned int* x0, unsigned int* y0, unsigned int* x1, unsigned int* y1) const {
double xidx = x / curr_course->size.x * ((double) nx - 1.);
double yidx = -z / curr_course->size.y * ((double) ny - 1.);
if (xidx < 0) xidx = 0;
else if (xidx > nx-1) xidx = nx-1;
if (yidx < 0) yidx = 0;
else if (yidx > ny-1) yidx = ny-1;
*x0 = (unsigned int)std::floor(xidx);
*x1 = (unsigned int)std::ceil(xidx);
*y0 = (unsigned int)std::floor(yidx);
*y1 = (unsigned int)std::ceil(yidx);
if (*x0 == *x1) {
if (*x1 < nx - 1)(*x1)++;
else (*x0)--;
}
if (*y0 == *y1) {
if (*y1 < ny - 1)(*y1)++;
else (*y0)--;
}
}
void CCourse::FindBarycentricCoords(double x, double z, TVector2i *idx0,
TVector2i *idx1, TVector2i *idx2, double *u, double *v) const {
double xidx, yidx;
unsigned int x0, x1, y0, y1;
double dx, ex, dz, ez, qx, qz, invdet;
GetIndicesForPoint(x, z, &x0, &y0, &x1, &y1);
xidx = x / curr_course->size.x * ((double) nx - 1.0);
yidx = -z / curr_course->size.y * ((double) ny - 1.0);
if ((x0 + y0) % 2 == 0) {
if (yidx - y0 < xidx - x0) {
*idx0 = TVector2i(x0, y0);
*idx1 = TVector2i(x1, y0);
*idx2 = TVector2i(x1, y1);
} else {
*idx0 = TVector2i(x1, y1);
*idx1 = TVector2i(x0, y1);
*idx2 = TVector2i(x0, y0);
}
} else {
if (yidx - y0 + xidx - x0 < 1) {
*idx0 = TVector2i(x0, y0);
*idx1 = TVector2i(x1, y0);
*idx2 = TVector2i(x0, y1);
} else {
*idx0 = TVector2i(x1, y1);
*idx1 = TVector2i(x0, y1);
*idx2 = TVector2i(x1, y0);
}
}
dx = idx0->x - idx2->x;
dz = idx0->y - idx2->y;
ex = idx1->x - idx2->x;
ez = idx1->y - idx2->y;
qx = xidx - idx2->x;
qz = yidx - idx2->y;
invdet = 1 / (dx * ez - dz * ex);
*u = (qx * ez - qz * ex) * invdet;
*v = (qz * dx - qx * dz) * invdet;
}
#define COURSE_VERTX(_x, _y) TVector3d ( (double)(_x)/(nx-1.)*curr_course->size.x, \
ELEV((_x),(_y)), -(double)(_y)/(ny-1.)*curr_course->size.y )
TVector3d CCourse::FindCourseNormal(double x, double z) const {
TVector2i idx0, idx1, idx2;
double u, v;
FindBarycentricCoords(x, z, &idx0, &idx1, &idx2, &u, &v);
const TVector3d& n0 = Course.Fields[idx0.x + nx * idx0.y].nml;
const TVector3d& n1 = Course.Fields[idx1.x + nx * idx1.y].nml;
const TVector3d& n2 = Course.Fields[idx2.x + nx * idx2.y].nml;
TVector3d p0 = COURSE_VERTX(idx0.x, idx0.y);
TVector3d p1 = COURSE_VERTX(idx1.x, idx1.y);
TVector3d p2 = COURSE_VERTX(idx2.x, idx2.y);
TVector3d smooth_nml = u * n0 +
v * n1 +
(1.-u-v) * n2;
TVector3d tri_nml = CrossProduct(p1 - p0, p2 - p0);
tri_nml.Norm();
double min_bary = std::min(u, std::min(v, 1. - u - v));
double interp_factor = std::min(min_bary / NORM_INTERPOL, 1.0);
TVector3d interp_nml = interp_factor * tri_nml + (1.-interp_factor) * smooth_nml;
interp_nml.Norm();
return interp_nml;
}
double CCourse::FindYCoord(double x, double z) const {
static double last_x, last_z, last_y;
static bool cache_full = false;
if (cache_full && last_x == x && last_z == z) return last_y;
TVector2i idx0, idx1, idx2;
double u, v;
FindBarycentricCoords(x, z, &idx0, &idx1, &idx2, &u, &v);
TVector3d p0 = COURSE_VERTX(idx0.x, idx0.y);
TVector3d p1 = COURSE_VERTX(idx1.x, idx1.y);
TVector3d p2 = COURSE_VERTX(idx2.x, idx2.y);
double ycoord = u * p0.y + v * p1.y + (1. - u - v) * p2.y;
last_x = x;
last_z = z;
last_y = ycoord;
cache_full = true;
return ycoord;
}
void CCourse::GetSurfaceType(double x, double z, double weights[]) const {
TVector2i idx0, idx1, idx2;
double u, v;
FindBarycentricCoords(x, z, &idx0, &idx1, &idx2, &u, &v);
for (std::size_t i=0; i<Course.TerrList.size(); i++) {
weights[i] = 0;
if (Course.Fields[idx0.x + nx*idx0.y].terrain == i) weights[i] += u;
if (Course.Fields[idx1.x + nx*idx1.y].terrain == i) weights[i] += v;
if (Course.Fields[idx2.x + nx*idx2.y].terrain == i) weights[i] += 1.0 - u - v;
}
}
int CCourse::GetTerrainIdx(double x, double z, double level) const {
TVector2i idx0, idx1, idx2;
double u, v;
FindBarycentricCoords(x, z, &idx0, &idx1, &idx2, &u, &v);
for (std::size_t i=0; i<Course.TerrList.size(); i++) {
double wheight = 0.0;
if (Course.Fields[idx0.x + nx*idx0.y].terrain == i) wheight += u;
if (Course.Fields[idx1.x + nx*idx1.y].terrain == i) wheight += v;
if (Course.Fields[idx2.x + nx*idx2.y].terrain == i) wheight += 1.0 - u - v;
if (wheight > level) return (int)i;
}
return -1;
}
TPlane CCourse::GetLocalCoursePlane(TVector3d pt) const {
TPlane plane;
pt.y = FindYCoord(pt.x, pt.z);
plane.nml = FindCourseNormal(pt.x, pt.z);
plane.d = -DotProduct(plane.nml, pt);
return plane;
}