1017 lines
28 KiB
C++
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;
|
|
}
|