extremetuxracer/src/course.cpp

1013 lines
27 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 "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 <cmath>
#include <algorithm>
CCourse Course;
CCourse::CCourse() {
terrain = NULL;
elevation = NULL;
nmls = NULL;
vnc_array = NULL;
mirrored = false;
curr_course = NULL;
}
CCourse::~CCourse() {
FreeCourseList();
ResetCourse();
}
double CCourse::GetBaseHeight(double distance) const {
double slope = tan(ANGLES_TO_RADIANS(curr_course->angle));
double base_height;
base_height = -slope * distance -
base_height_value / 255.0 * curr_course->scale;
return base_height;
}
double CCourse::GetMaxHeight(double distance) const {
return GetBaseHeight(distance) + curr_course->scale;
}
void CCourse::GetDivisions(int *x, int *y) const {
*x = nx;
*y = ny;
}
const TPolyhedron& CCourse::GetPoly(size_t type) const {
return PolyArr[ObjTypes[type].poly];
}
TCourse* CCourse::GetCourse(const string& dir) {
return &CourseList[CourseIndex.at(dir)];
}
size_t CCourse::GetCourseIdx(const TCourse* course) const {
size_t idx = (course - &CourseList[0]);
if (idx >= CourseList.size())
return -1;
return idx;
}
void CCourse::CalcNormals() {
for (int y=0; y<ny; y++) {
for (int x=0; x<nx; x++) {
TVector3d nml(0.0, 0.0, 0.0);
TVector3d p0(XCD(x), ELEV(x,y), ZCD(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();
nmls [x + nx * y] = nml;
continue;
}
}
}
void CCourse::MakeCourseNormals() {
if (nmls != NULL) delete[] nmls;
try {
nmls = new TVector3d[nx * ny];
} catch (...) {
nmls = NULL;
Message("Allocation failed in MakeCourseNormals");
}
CalcNormals();
}
// --------------------------------------------------------------------
// FillGlArrays
// --------------------------------------------------------------------
void CCourse::FillGlArrays() {
TVector3d *normals = nmls;
if (vnc_array == NULL)
vnc_array = new GLubyte[STRIDE_GL_ARRAY * nx * ny];
for (int x=0; x<nx; x++) {
for (int y=0; y<ny; y++) {
int idx = STRIDE_GL_ARRAY * (y * nx + x);
FLOATVAL(0) = (GLfloat)x / (nx-1.0) * curr_course->size.x;
FLOATVAL(1) = elevation[(x) + nx*(y)];
FLOATVAL(2) = -(GLfloat)y / (ny-1.0) * curr_course->size.y;
const TVector3d& nml = normals[ x + y * nx ];
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.resize(6);
PolyArr[1].vertices[0] = TVector3d(0, 0, 0);
PolyArr[1].vertices[1] = TVector3d(0, 0.15, 0.5);
PolyArr[1].vertices[2] = TVector3d(0.5, 0.15, 0);
PolyArr[1].vertices[3] = TVector3d(0, 0.15, -0.5);
PolyArr[1].vertices[4] = TVector3d(-0.5, 0.15, 0);
PolyArr[1].vertices[5] = TVector3d(0, 1, 0);
PolyArr[1].polygons.resize(8);
for (size_t i = 0; i < 8; i++) {
PolyArr[1].polygons[i].vertices.resize(3);
}
PolyArr[1].polygons[0].vertices[0] = 0;
PolyArr[1].polygons[0].vertices[1] = 1;
PolyArr[1].polygons[0].vertices[2] = 4;
PolyArr[1].polygons[1].vertices[0] = 0;
PolyArr[1].polygons[1].vertices[1] = 2;
PolyArr[1].polygons[1].vertices[2] = 1;
PolyArr[1].polygons[2].vertices[0] = 0;
PolyArr[1].polygons[2].vertices[1] = 3;
PolyArr[1].polygons[2].vertices[2] = 2;
PolyArr[1].polygons[3].vertices[0] = 0;
PolyArr[1].polygons[3].vertices[1] = 4;
PolyArr[1].polygons[3].vertices[2] = 3;
PolyArr[1].polygons[4].vertices[0] = 1;
PolyArr[1].polygons[4].vertices[1] = 5;
PolyArr[1].polygons[4].vertices[2] = 4;
PolyArr[1].polygons[5].vertices[0] = 2;
PolyArr[1].polygons[5].vertices[1] = 5;
PolyArr[1].polygons[5].vertices[2] = 1;
PolyArr[1].polygons[6].vertices[0] = 3;
PolyArr[1].polygons[6].vertices[1] = 5;
PolyArr[1].polygons[6].vertices[2] = 2;
PolyArr[1].polygons[7].vertices[0] = 4;
PolyArr[1].polygons[7].vertices[1] = 5;
PolyArr[1].polygons[7].vertices[2] = 3;
}
void CCourse::FreeTerrainTextures() {
for (size_t i=0; i<TerrList.size(); i++) {
delete TerrList[i].texture;
TerrList[i].texture = NULL;
}
}
void CCourse::FreeObjectTextures() {
for (size_t i=0; i<ObjTypes.size(); i++) {
delete ObjTypes[i].texture;
ObjTypes[i].texture = NULL;
}
}
// --------------------------------------------------------------------
// LoadElevMap
// --------------------------------------------------------------------
bool CCourse::LoadElevMap() {
CImage img;
if (!img.LoadPng(CourseDir.c_str(), "elev.png", true)) {
Message("unable to open elev.png");
return false;
}
nx = img.nx;
ny = img.ny;
try {
elevation = new double[nx * ny];
} catch (...) {
Message("Allocation failed in LoadElevMap");
return false;
}
double slope = tan(ANGLES_TO_RADIANS(curr_course->angle));
int pad = 0;
for (int y=0; y<ny; y++) {
for (int x=0; x<nx; x++) {
elevation [(nx-1-x) + nx * (ny-1-y)] =
((img.data [(x+nx*y) * img.depth + pad]
- base_height_value) / 255.0) * curr_course->scale
- (double)(ny-1-y) / ny * curr_course->size.y * slope;
}
pad += (nx * img.depth) % 4;
}
return true;
}
// ====================================================================
// LoadItemList
// ====================================================================
void CCourse::LoadItemList() {
CSPList list(16000);
if (!list.Load(CourseDir, "items.lst")) {
Message("could not load items list");
return;
}
CollArr.clear();
NocollArr.clear();
for (size_t i=0; i<list.Count(); i++) {
const string& line = list.Line(i);
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)(nx - 1.0) * curr_course->size.x;
double zz = -(ny - z) / (double)(ny - 1.0) * curr_course->size.y;
string name = SPStrN(line, "name");
size_t type = ObjectIndex[name];
if (ObjTypes[type].texture == NULL && ObjTypes[type].drawable) {
string terrpath = param.obj_dir + SEP + ObjTypes[type].textureFile;
ObjTypes[type].texture = new TTexture();
ObjTypes[type].texture->LoadMipmap(terrpath, false);
}
if (ObjTypes[type].collidable)
CollArr.push_back(TCollidable(xx, FindYCoord(xx, zz), zz, height, diam, type));
else
NocollArr.push_back(TItem(xx, FindYCoord(xx, zz), zz, height, diam, ObjTypes[type]));
}
}
// -------------------- LoadObjectMap ---------------------------------
static int GetObject(unsigned char pixel[]) {
int r = pixel[0];
int g = pixel[1];
int b = pixel[2];
if (r<150 && b>200) return 0;
if (abs(r-194)<10 && abs(g-40)<10 && abs(b-40)<10) return 1;
if (abs(r-128)<10 && abs(g-128)<10 && b<10) return 2;
if (r>220 && g>220 && b<20) return 3;
if (r>220 && abs(g-128)<10 && b>220) return 4;
if (r>220 && g>220 && b>220) return 5;
if (r>220 && 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() {
CImage treeImg;
if (!treeImg.LoadPng(CourseDir.c_str(), "trees.png", true)) {
Message("unable to open trees.png");
return false;
}
int pad = 0;
int cnt = 0;
double height, diam;
CSPList savelist(10000);
CollArr.clear();
NocollArr.clear();
for (int y=0; y<ny; y++) {
for (int x=0; x<nx; x++) {
int imgidx = (x + nx * y) * treeImg.depth + pad;
int type = GetObject(&treeImg.data[imgidx]);
if (type >= 0) {
cnt++;
double xx = (nx - x) / (double)(nx - 1.0) * curr_course->size.x;
double zz = -(ny - y) / (double)(ny - 1.0) * curr_course->size.y;
if (ObjTypes[type].texture == NULL && ObjTypes[type].drawable) {
string terrpath = param.obj_dir + SEP + ObjTypes[type].textureFile;
ObjTypes[type].texture = new TTexture();
ObjTypes[type].texture->LoadMipmap(terrpath, 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.push_back(TCollidable(xx, FindYCoord(xx, zz), zz, height, diam, type));
else
NocollArr.push_back(TItem(xx, FindYCoord(xx, zz), zz, height, diam, ObjTypes[type]));
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 * treeImg.depth) % 4;
}
string itemfile = CourseDir + SEP "items.lst";
savelist.Save(itemfile); // Convert trees.png to items.lst
return true;
}
// --------------------------------------------------------------------
// LoadObjectTypes
// --------------------------------------------------------------------
bool CCourse::LoadObjectTypes() {
CSPList list(MAX_OBJECT_TYPES+10);
if (!list.Load(param.obj_dir, "object_types.lst")) {
Message("could not load object types");
return false;
}
ObjTypes.resize(list.Count());
for (size_t i=0; i<list.Count(); i++) {
const string& line = list.Line(i);
ObjTypes[i].name = SPStrN(line, "name");
ObjTypes[i].textureFile = ObjTypes[i].name;
ObjTypes[i].texture = NULL;
ObjTypes[i].drawable = SPBoolN(line, "draw", true);
if (ObjTypes[i].drawable) {
ObjTypes[i].textureFile = SPStrN(line, "texture");
}
ObjTypes[i].collectable = SPBoolN(line, "snap", -1) != 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(unsigned char pixel[]) const {
for (size_t i=0; i<TerrList.size(); i++) {
if (abs(pixel[0]-(int)(TerrList[i].col.r)) < 30
&& abs(pixel[1]-(int)(TerrList[i].col.g)) < 30
&& abs(pixel[2]-(int)(TerrList[i].col.b)) < 30) {
return (int)i;
}
}
return 0;
}
// --------------------------------------------------------------------
// LoadTerrainTypes
// --------------------------------------------------------------------
bool CCourse::LoadTerrainTypes() {
CSPList list(MAX_TERR_TYPES +10);
if (!list.Load(param.terr_dir, "terrains.lst")) {
Message("could not load terrain types");
return false;
}
TerrList.resize(list.Count());
for (size_t i=0; i<list.Count(); i++) {
const string& line = list.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", TColor3(1, 1, 1));
TerrList[i].friction = SPFloatN(line, "friction", 0.5);
TerrList[i].depth = SPFloatN(line, "depth", 0.01);
TerrList[i].particles = SPBoolN(line, "part", false);
TerrList[i].trackmarks = SPBoolN(line, "trackmarks", false);
TerrList[i].texture = NULL;
TerrList[i].shiny = SPBoolN(line, "shiny", false);
TerrList[i].vol_type = SPIntN(line, "vol_type", 1);
}
return true;
}
// --------------------------------------------------------------------
// LoadTerrainMap
// --------------------------------------------------------------------
bool CCourse::LoadTerrainMap() {
CImage terrImage;
if (!terrImage.LoadPng(CourseDir.c_str(), "terrain.png", true)) {
Message("unable to open terrain.png");
return false;
}
if (nx != terrImage.nx || ny != terrImage.ny) {
Message("wrong terrain size");
}
try {
terrain = new char[nx * ny];
} catch (...) {
Message("Allocation failed in LoadTerrainMap");
}
int pad = 0;
for (int y=0; y<ny; y++) {
for (int x=0; x<nx; x++) {
int imgidx = (x+nx*y) * terrImage.depth + pad;
int arridx = (nx-1-x) + nx * (ny-1-y);
int terr = GetTerrain(&terrImage.data[imgidx]);
terrain[arridx] = terr;
if (TerrList[terr].texture == NULL) {
TerrList[terr].texture = new TTexture();
TerrList[terr].texture->LoadMipmap(param.terr_dir, TerrList[terr].textureFile, true);
}
}
pad += (nx * terrImage.depth) % 4;
}
return true;
}
// --------------------------------------------------------------------
// LoadCourseList
// --------------------------------------------------------------------
bool CCourse::LoadCourseList() {
CSPList list(128);
if (!list.Load(param.common_course_dir, "courses.lst")) {
Message("could not load courses.lst");
return false;
}
CSPList paramlist(48);
CourseList.resize(list.Count());
for (size_t i=0; i<list.Count(); i++) {
const string& line1 = list.Line(i);
CourseList[i].name = SPStrN(line1, "name", "noname");
CourseList[i].dir = SPStrN(line1, "dir", "nodir");
string desc = SPStrN(line1, "desc");
FT.AutoSizeN(2);
vector<string> desclist = FT.MakeLineList(desc.c_str(), 335 * Winsys.scale - 16.0);
size_t cnt = min<size_t>(desclist.size(), MAX_DESCRIPTION_LINES);
CourseList[i].num_lines = cnt;
for (size_t ll=0; ll<cnt; ll++) {
CourseList[i].desc[ll] = desclist[ll];
}
string coursepath = param.common_course_dir + SEP + CourseList[i].dir;
if (DirExists(coursepath.c_str())) {
// preview
string previewfile = coursepath + SEP "preview.png";
CourseList[i].preview = new TTexture();
if (!CourseList[i].preview->LoadMipmap(previewfile, false)) {
Message("couldn't load previewfile");
// texid = Tex.TexID (NO_PREVIEW);
}
// params
string paramfile = coursepath + SEP "course.dim";
if (!paramlist.Load(paramfile)) {
Message("could not load course.dim");
}
const string& line2 = paramlist.Line(0);
CourseList[i].author = SPStrN(line2, "author", "unknown");
CourseList[i].size.x = SPFloatN(line2, "width", 100);
CourseList[i].size.y = SPFloatN(line2, "length", 1000);
CourseList[i].play_size.x = SPFloatN(line2, "play_width", 90);
CourseList[i].play_size.y = SPFloatN(line2, "play_length", 900);
CourseList[i].angle = SPFloatN(line2, "angle", 10);
CourseList[i].scale = SPFloatN(line2, "scale", 10);
CourseList[i].start.x = SPFloatN(line2, "startx", 50);
CourseList[i].start.y = SPFloatN(line2, "starty", 5);
CourseList[i].env = Env.GetEnvIdx(SPStrN(line2, "env", "etr"));
CourseList[i].music_theme = Music.GetThemeIdx(SPStrN(line2, "theme", "normal"));
CourseList[i].use_keyframe = SPBoolN(line2, "use_keyframe", false);
CourseList[i].finish_brake = SPFloatN(line2, "finish_brake", 20);
paramlist.Clear(); // the list is used several times
}
}
list.MakeIndex(CourseIndex, "dir");
return true;
}
void CCourse::FreeCourseList() {
for (size_t i=0; i<CourseList.size(); i++) {
delete CourseList[i].preview;
}
CourseList.clear();
}
// ===================================================================
// LoadCourse
// ===================================================================
void CCourse::ResetCourse() {
if (nmls != NULL) {delete[] nmls; nmls = NULL;}
if (vnc_array != NULL) {delete[] vnc_array; vnc_array = NULL;}
if (elevation != NULL) {delete[] elevation; elevation = NULL;}
if (terrain != NULL) {delete[] terrain; terrain = NULL;}
FreeTerrainTextures();
FreeObjectTextures();
ResetQuadtree();
curr_course = NULL;
mirrored = false;
}
bool CCourse::LoadCourse(TCourse* course) {
if (course != curr_course || g_game.force_treemap) {
ResetCourse();
curr_course = course;
CourseDir = param.common_course_dir + SEP + 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;
}
// ................................................................
string itemfile = CourseDir + SEP "items.lst";
bool itemsexists = FileExists(itemfile);
const CControl *ctrl = g_game.player->ctrl;
if (itemsexists && !g_game.force_treemap)
LoadItemList();
else
LoadAndConvertObjectMap();
g_game.force_treemap = false;
// ................................................................
init_track_marks();
InitQuadtree(
elevation, 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;
}
size_t CCourse::GetEnv() const {
return curr_course->env;
}
// --------------------------------------------------------------------
// mirror course
// --------------------------------------------------------------------
void CCourse::MirrorCourseData() {
for (int y=0; y<ny; y++) {
for (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);
swap(terrain[idx1], terrain[idx2]);
idx1 = (x) + nx*(y);
idx2 = (nx-1-x) + nx*(y);
swap(nmls[idx1], nmls[idx2]);
nmls[idx1].x *= -1;
nmls[idx2].x *= -1;
}
}
for (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 (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(elevation, 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, int *x0, int *y0, int *x1, 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 = (int)(xidx); // floor(xidx)
*x1 = (int)(xidx + 0.9999); // ceil(xidx)
*y0 = (int)(yidx); // floor(yidx)
*y1 = (int)(yidx + 0.9999); // 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;
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.);
yidx = -z / curr_course->size.y * ((double) ny - 1.);
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 {
double *elevation = Course.elevation;
int x0, x1, y0, y1;
GetIndicesForPoint(x, z, &x0, &y0, &x1, &y1);
TVector2i idx0, idx1, idx2;
double u, v;
FindBarycentricCoords(x, z, &idx0, &idx1, &idx2, &u, &v);
const TVector3d& n0 = Course.nmls[ idx0.x + nx * idx0.y ];
const TVector3d& n1 = Course.nmls[ idx1.x + nx * idx1.y ];
const TVector3d& n2 = Course.nmls[ idx2.x + nx * idx2.y ];
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 = min(u, min(v, 1. - u - v));
double interp_factor = 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;
double *elevation = Course.elevation;
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);
char *terrain = Course.terrain;
for (size_t i=0; i<Course.TerrList.size(); i++) {
weights[i] = 0;
if (terrain [idx0.x + nx*idx0.y ] == i) weights[i] += u;
if (terrain [idx1.x + nx*idx1.y ] == i) weights[i] += v;
if (terrain [idx2.x + nx*idx2.y ] == 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);
char *terrain = Course.terrain;
for (size_t i=0; i<Course.TerrList.size(); i++) {
double wheight = 0.0;
if (terrain [idx0.x + nx*idx0.y] == i) wheight += u;
if (terrain [idx1.x + nx*idx1.y] == i) wheight += v;
if (terrain [idx2.x + nx*idx2.y] == 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;
}