extremetuxracer/src/textures.cpp

694 lines
17 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 "textures.h"
#include "spx.h"
#include "course.h"
#include "winsys.h"
#include "ogl.h"
#include <SDL/SDL_image.h>
#include <GL/glu.h>
#include <fstream>
#include <cctype>
static const GLshort fullsize_texture[] = {
0, 0,
1, 0,
1, 1,
0, 1
};
// --------------------------------------------------------------------
// class CImage
// --------------------------------------------------------------------
CImage::CImage() {
data = NULL;
nx = 0;
ny = 0;
depth = 0;
pitch = 0;
}
CImage::~CImage() {
DisposeData();
}
void CImage::DisposeData() {
delete[] data;
data = NULL;
}
bool CImage::LoadPng(const char *filepath, bool mirroring) {
SDL_Surface *sdlImage;
unsigned char *sdlData;
sdlImage = IMG_Load(filepath);
if (sdlImage == 0) {
Message("could not load image", filepath);
return false;
}
nx = sdlImage->w;
ny = sdlImage->h;
depth = sdlImage->format->BytesPerPixel;
pitch = sdlImage->pitch;
DisposeData();
data = new unsigned char[pitch * ny];
if (SDL_MUSTLOCK(sdlImage)) {
if (SDL_LockSurface(sdlImage) < 0) {
SDL_FreeSurface(sdlImage);
Message("mustlock error");
return false;
};
}
sdlData = (unsigned char *) sdlImage->pixels;
if (mirroring) {
for (int y=0; y<ny; y++) {
memcpy(data + y*pitch, sdlData + (ny-1-y)*pitch, pitch);
}
} else {
memcpy(data, sdlData, ny*pitch);
}
if (SDL_MUSTLOCK(sdlImage)) SDL_UnlockSurface(sdlImage);
SDL_FreeSurface(sdlImage);
return true;
}
bool CImage::LoadPng(const char *dir, const char *filename, bool mirroring) {
string path = dir;
path += SEP;
path += filename;
return LoadPng(path.c_str(), mirroring);
}
// ------------------ read framebuffer --------------------------------
bool CImage::ReadFrameBuffer_PPM() {
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
nx = viewport[2];
ny = viewport[3];
depth = 3;
DisposeData();
data = new unsigned char[nx * ny * depth];
glReadBuffer(GL_FRONT);
for (int i=0; i<viewport[3]; i++) {
glReadPixels(viewport[0], viewport[1] + viewport[3] - 1 - i,
viewport[2], 1, GL_RGB, GL_UNSIGNED_BYTE, data + viewport[2] * i * 3);
}
return true;
}
void CImage::ReadFrameBuffer_TGA() {
nx = Winsys.resolution.width;
ny = Winsys.resolution.height;
depth = 3;
DisposeData();
data = new unsigned char[nx * ny * depth];
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, nx, ny, GL_BGR, GL_UNSIGNED_BYTE, data);
}
void CImage::ReadFrameBuffer_BMP() {
nx = Winsys.resolution.width;
ny = Winsys.resolution.height;
depth = 4;
DisposeData();
data = new unsigned char[nx * ny * depth];
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, nx, ny, GL_BGRA, GL_UNSIGNED_BYTE, data);
}
// ---------------------------
void CImage::WritePPM(const char *filepath) {
if (data == NULL)
return;
std::ofstream file(filepath);
file << "P6\n# A Raw PPM file"
<< "\n# width\n" << nx
<< "\n# height\n" << ny
<< "\n# max component value\n255"<< std::endl;
file.write(reinterpret_cast<char*>(data), depth * nx * ny);
}
#ifdef _MSC_VER
#pragma pack(push, 1)
#endif
struct TTgaHeader {
int8_t tfType;
int8_t tfColorMapType;
int8_t tfImageType;
int8_t tfColorMapSpec[5];
int16_t tfOrigX;
int16_t tfOrigY;
int16_t tfWidth;
int16_t tfHeight;
int8_t tfBpp;
int8_t tfImageDes;
#ifdef _MSC_VER
};
#pragma pack(pop)
#else
} __attribute__((packed));
#endif
void CImage::WriteTGA(const char *filepath) {
if (data == NULL)
return;
TTgaHeader header;
header.tfType = 0;
header.tfColorMapType = 0;
header.tfImageType = 2;
for (int i=0; i<5; i++) header.tfColorMapSpec[i] = 0;
header.tfOrigX = 0;
header.tfOrigY = 0;
header.tfWidth = static_cast<int16_t>(nx);
header.tfHeight = static_cast<int16_t>(ny);
header.tfBpp = 24;
header.tfImageDes = 0;
std::ofstream out(filepath, std::ios_base::out|std::ios_base::binary);
out.write(reinterpret_cast<char*>(&header), sizeof(TTgaHeader));
out.write(reinterpret_cast<char*>(data), 3 * nx * ny);
}
#define BF_TYPE 0x4D42 // "MB"
#ifdef _MSC_VER
#pragma pack(push, 1)
#endif
struct TBmpHeader {
uint16_t bfType; // identifier of bmp format
uint32_t bfSize; // size of file, including the headers
uint16_t bfReserved1; // reserved, always 0
uint16_t bfReserved2; // reserved, always 0
uint32_t bfOffBits; // offset to bitmap data
#ifdef _MSC_VER
};
#else
} __attribute__((packed));
#endif
struct TBmpInfo {
uint32_t biSize; // size of info header, normally 40
int32_t biWidth; // width
int32_t biHeight; // height
uint16_t biPlanes; // number of color planes, normally 1
uint16_t biBitCount; // Number of bits per pixel (8 * depth)
uint32_t biCompression; // type of compression, normally 0 = no compr.
uint32_t biSizeImage; // size of data
int32_t biXPelsPerMeter; // normally 0
int32_t biYPelsPerMeter; // normally 0
uint32_t biClrUsed; // normally 0
uint32_t biClrImportant; // normally 0
#ifdef _MSC_VER
};
#pragma pack(pop)
#else
} __attribute__((packed));
#endif
void CImage::WriteBMP(const char *filepath) {
if (data == NULL)
return;
int infosize = 40;
int width = nx;
int height = ny;
int imgsize = nx * ny * depth;
int bitcnt = 8 * depth; // 24 or 32
unsigned int bitsize;
// (width * bitcnt + 7) / 8 = width * depth
if (imgsize == 0) bitsize = (width * bitcnt + 7) / 8 * height;
else bitsize = imgsize;
TBmpHeader header;
header.bfType = BF_TYPE;
header.bfSize = 14 + infosize + bitsize;
header.bfReserved1 = 0;
header.bfReserved2 = 0;
header.bfOffBits = sizeof(TBmpHeader) + sizeof(TBmpInfo);
TBmpInfo info;
info.biSize = infosize;
info.biWidth = width;
info.biHeight = height;
info.biPlanes = 1;
info.biBitCount = bitcnt;
info.biCompression = 0;
info.biSizeImage = imgsize;
info.biXPelsPerMeter = 0;
info.biYPelsPerMeter= 0;
info.biClrUsed = 0;
info.biClrImportant = 0;
std::ofstream out(filepath, std::ios_base::out|std::ios_base::binary);
if (!out) {
Message("could not open bmp file", filepath);
return;
}
out.write(reinterpret_cast<char*>(&header), sizeof(TBmpHeader));
out.write(reinterpret_cast<char*>(&info), sizeof(TBmpInfo));
out.write(reinterpret_cast<char*>(data), bitsize);
}
// --------------------------------------------------------------------
// class TTexture
// --------------------------------------------------------------------
TTexture::~TTexture() {
glDeleteTextures(1, &id);
}
bool TTexture::Load(const string& filename) {
CImage texImage;
if (texImage.LoadPng(filename.c_str(), true) == false)
return false;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
GLenum format;
if (texImage.depth == 3) format = GL_RGB;
else format = GL_RGBA;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D
(GL_TEXTURE_2D, 0, texImage.depth, texImage.nx,
texImage.ny, 0, format, GL_UNSIGNED_BYTE, texImage.data);
return true;
}
bool TTexture::Load(const string& dir, const string& filename) {
return Load(dir + SEP + filename);
}
bool TTexture::LoadMipmap(const string& filename, bool repeatable) {
CImage texImage;
if (texImage.LoadPng(filename.c_str(), true) == false)
return false;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
if (repeatable) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
} else {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
}
GLenum format;
if (texImage.depth == 3) format = GL_RGB;
else format = GL_RGBA;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
gluBuild2DMipmaps(GL_TEXTURE_2D, texImage.depth, texImage.nx, texImage.ny, format, GL_UNSIGNED_BYTE, texImage.data);
return true;
}
bool TTexture::LoadMipmap(const string& dir, const string& filename, bool repeatable) {
return LoadMipmap(dir + SEP + filename, repeatable);
}
void TTexture::Bind() {
glBindTexture(GL_TEXTURE_2D, id);
}
void TTexture::Draw() {
GLint w, h;
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, id);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
glColor4f(1.0, 1.0, 1.0, 1.0);
const GLshort vtx[] = {
0, 0,
w, 0,
w, h,
0, h
};
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(2, GL_SHORT, 0, vtx);
glTexCoordPointer(2, GL_SHORT, 0, fullsize_texture);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
void TTexture::Draw(int x, int y, float size, Orientation orientation) {
GLint w, h;
GLfloat width, height, top, bott, left, right;
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, id);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
width = w * size;
height = h * size;
if (orientation == OR_TOP) {
top = Winsys.resolution.height - y;
bott = top - height;
} else {
bott = y;
top = bott + height;
}
if (x >= 0) left = x;
else left = (Winsys.resolution.width - width) / 2;
right = left + width;
glColor4f(1.0, 1.0, 1.0, 1.0);
const GLfloat vtx[] = {
left, bott,
right, bott,
right, top,
left, top
};
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vtx);
glTexCoordPointer(2, GL_SHORT, 0, fullsize_texture);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
void TTexture::Draw(int x, int y, float width, float height, Orientation orientation) {
GLfloat top, bott, left, right;
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, id);
if (orientation == OR_TOP) {
top = Winsys.resolution.height - y;
bott = top - height;
} else {
bott = y;
top = bott + height;
}
if (x >= 0) left = x;
else left = (Winsys.resolution.width - width) / 2;
right = left + width;
glColor4f(1.0, 1.0, 1.0, 1.0);
const GLfloat vtx[] = {
left, bott,
right, bott,
right, top,
left, top
};
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vtx);
glTexCoordPointer(2, GL_SHORT, 0, fullsize_texture);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
void TTexture::DrawFrame(int x, int y, double w, double h, int frame, const TColor& col) {
if (id < 1)
return;
GLint ww = GLint(w);
GLint hh = GLint(h);
GLint xx = x;
GLint yy = Winsys.resolution.height - hh - y;
glBindTexture(GL_TEXTURE_2D, id);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
if (frame > 0) {
if (w < 1) glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &ww);
if (h < 1) glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &hh);
glColor(col, 1.0);
glDisable(GL_TEXTURE_2D);
const GLint vtx [] = {
xx - frame, yy - frame,
xx + ww + frame, yy - frame,
xx + ww + frame, yy + hh + frame,
xx - frame, yy + hh + frame
};
glVertexPointer(2, GL_INT, 0, vtx);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glEnable(GL_TEXTURE_2D);
}
glColor4f(1.0, 1.0, 1.0, 1.0);
const GLshort vtx[] = {
xx, yy,
xx + ww, yy,
xx + ww, yy + hh,
xx, yy + hh
};
glVertexPointer(2, GL_SHORT, 0, vtx);
glTexCoordPointer(2, GL_SHORT, 0, fullsize_texture);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
// --------------------------------------------------------------------
// class CTexture
// --------------------------------------------------------------------
CTexture Tex;
CTexture::CTexture() {
forientation = OR_TOP;
}
CTexture::~CTexture() {
FreeTextureList();
}
void CTexture::LoadTextureList() {
FreeTextureList();
CSPList list(200);
if (list.Load(param.tex_dir, "textures.lst")) {
for (size_t i=0; i<list.Count(); i++) {
const string& line = list.Line(i);
string name = SPStrN(line, "name");
int id = SPIntN(line, "id", -1);
CommonTex.resize(max(CommonTex.size(), (size_t)id+1));
string texfile = SPStrN(line, "file");
bool rep = SPBoolN(line, "repeat", false);
if (id >= 0) {
CommonTex[id] = new TTexture();
if (rep)
CommonTex[id]->LoadMipmap(param.tex_dir, texfile, rep);
else
CommonTex[id]->Load(param.tex_dir, texfile);
} else Message("wrong texture id in textures.lst");
}
} else Message("failed to load common textures");
}
void CTexture::FreeTextureList() {
for (size_t i=0; i<CommonTex.size(); i++) {
delete CommonTex[i];
}
CommonTex.clear();
}
TTexture* CTexture::GetTexture(size_t idx) const {
if (idx >= CommonTex.size()) return NULL;
return CommonTex[idx];
}
bool CTexture::BindTex(size_t idx) {
if (idx >= CommonTex.size()) return false;
CommonTex[idx]->Bind();
return true;
}
// ---------------------------- Draw ----------------------------------
void CTexture::Draw(size_t idx) {
if (CommonTex.size() > idx)
CommonTex[idx]->Draw();
}
void CTexture::Draw(size_t idx, int x, int y, float size) {
if (CommonTex.size() > idx)
CommonTex[idx]->Draw(x, y, size, forientation);
}
void CTexture::Draw(size_t idx, int x, int y, int width, int height) {
if (CommonTex.size() > idx)
CommonTex[idx]->Draw(x, y, width, height, forientation);
}
void CTexture::DrawFrame(size_t idx, int x, int y, double w, double h, int frame, const TColor& col) {
if (CommonTex.size() > idx)
CommonTex[idx]->DrawFrame(x, y, w, h, frame, col);
}
void CTexture::SetOrientation(Orientation orientation) {
forientation = orientation;
}
// -------------------------- numeric strings -------------------------
void CTexture::DrawNumChr(char c, int x, int y, int w, int h, const TColor& col) {
int idx;
if (isdigit(c)) {
char chrname[2] = {c, '\0'};
idx = atoi(chrname);
} else if (c == ':')
idx = 10;
else if (c == ' ')
idx = 11;
else
return;
// texture coords
float texw = 22.0 / 256.0;
float texleft = idx * texw;
float texright = (idx + 1) * texw;
const GLfloat tex[] = {
texleft, 0,
texright, 0,
texright, 1,
texleft, 1
};
const GLfloat vtx[] = {
x, Winsys.resolution.height - y - h,
x + w * 0.9, Winsys.resolution.height - y - h,
x + w * 0.9, Winsys.resolution.height - y,
x, Winsys.resolution.height - y
};
glVertexPointer(2, GL_FLOAT, 0, vtx);
glTexCoordPointer(2, GL_FLOAT, 0, tex);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
void CTexture::DrawNumStr(const string& s, int x, int y, float size, const TColor& col) {
if (!BindTex(NUMERIC_FONT)) {
Message("DrawNumStr: missing texture");
return;
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_TEXTURE_2D);
int qw = (int)(22 * size);
int qh = (int)(32 * size);
glColor(col);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
for (size_t i=0; i < s.size(); i++) {
DrawNumChr(s[i], x + (int)i*qw, y, qw, qh, col);
}
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
// --------------------------------------------------------------------
// screenshot
// --------------------------------------------------------------------
// 0 ppm, 1 tga, 2 bmp
#define SCREENSHOT_PROC 2
void ScreenshotN() {
CImage image;
string path = param.screenshot_dir;
path += SEP;
path += g_game.course->dir;
path += "_";
path += GetTimeString();
int type = SCREENSHOT_PROC;
switch (type) {
case 0:
path += ".ppm";
image.ReadFrameBuffer_PPM();
image.WritePPM(path.c_str());
break;
case 1:
path += ".tga";
image.ReadFrameBuffer_TGA();
image.WriteTGA(path.c_str());
break;
case 2:
path += ".bmp";
image.ReadFrameBuffer_BMP();
image.WriteBMP(path.c_str());
break;
}
}