celestia/src/celengine/texture.cpp

1092 lines
30 KiB
C++

// texture.cpp
//
// Copyright (C) 2001-2003, Chris Laurel
//
// 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.
#ifndef TARGET_OS_MAC
#define JPEG_SUPPORT
#define PNG_SUPPORT
#endif
#ifdef TARGET_OS_MAC
#include <unistd.h>
#include "CGBuffer.h"
#ifndef PNG_SUPPORT
#include <Quicktime/ImageCompression.h>
#include <QuickTime/QuickTimeComponents.h>
#endif
#endif
#include <cmath>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstdio>
#include <cassert>
#ifndef _WIN32
#ifndef TARGET_OS_MAC
#include <config.h>
#endif /* ! TARGET_OS_MAC */
#endif /* ! _WIN32 */
#include <celutil/filetype.h>
#include <celutil/debug.h>
#include <celutil/util.h>
#include <GL/glew.h>
#include "celestia.h"
#include <Eigen/Core>
#ifdef JPEG_SUPPORT
#ifndef PNG_SUPPORT
#include "setjmp.h"
#endif // PNG_SUPPORT
extern "C" {
#ifdef _WIN32
#include "jpeglib.h"
#else
#include <jpeglib.h>
#endif
}
#endif // JPEG_SUPPORT
#ifdef PNG_SUPPORT // PNG_SUPPORT
#ifdef TARGET_OS_MAC
#include "../../macosx/png.h"
#else
#include "png.h"
#endif // TARGET_OS_MAC
// Define png_jmpbuf() in case we are using a pre-1.0.6 version of libpng
#ifndef png_jmpbuf
#define png_jmpbuf(png_ptr) png_ptr->jmpbuf
#endif // PNG_SUPPORT
// Define various expansion transformations for old versions of libpng
#if PNG_LIBPNG_VER < 10004
#define png_set_palette_to_rgb(p) png_set_expand(p)
#define png_set_gray_1_2_4_to_8(p) png_set_expand(p)
#define png_set_tRNS_to_alpha(p) png_set_expand(p)
#endif // PNG_LIBPNG_VER < 10004
#endif // PNG_SUPPORT
#include "texture.h"
#include "virtualtex.h"
using namespace Eigen;
using namespace std;
static bool texCapsInitialized = false;
struct TextureCaps
{
bool compressionSupported;
bool clampToEdgeSupported;
bool clampToBorderSupported;
bool autoMipMapSupported;
bool maxLevelSupported;
GLint maxTextureSize;
bool nonPow2Supported;
GLint preferredAnisotropy;
};
static TextureCaps texCaps;
static bool testMaxLevel()
{
unsigned char texels[64];
glEnable(GL_TEXTURE_2D);
// Test whether GL_TEXTURE_MAX_LEVEL is supported . . .
glTexImage2D(GL_TEXTURE_2D,
0,
GL_LUMINANCE,
8, 8,
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
texels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
float maxLev = -1.0f;
glGetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, &maxLev);
glDisable(GL_TEXTURE_2D);
return maxLev == 2;
}
static const TextureCaps& GetTextureCaps()
{
if (!texCapsInitialized)
{
texCapsInitialized = true;
texCaps.compressionSupported = (GLEW_ARB_texture_compression == GL_TRUE);
#ifdef GL_VERSION_1_2
texCaps.clampToEdgeSupported = true;
#else
texCaps.clampToEdgeSupported = ExtensionSupported("GL_EXT_texture_edge_clamp");
#endif // GL_VERSION_1_2
texCaps.clampToBorderSupported = (GLEW_ARB_texture_border_clamp == GL_TRUE);
texCaps.autoMipMapSupported = (GLEW_SGIS_generate_mipmap == GL_TRUE);
texCaps.maxLevelSupported = testMaxLevel();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texCaps.maxTextureSize);
texCaps.nonPow2Supported = (GLEW_ARB_texture_non_power_of_two == GL_TRUE);
texCaps.preferredAnisotropy = 1;
if (GLEW_EXT_texture_filter_anisotropic)
{
GLint maxAnisotropy = 1;
glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
// Cap the preferred level texture anisotropy to 8; eventually, we should allow
// the user to control this.
texCaps.preferredAnisotropy = min(8, maxAnisotropy);
}
}
return texCaps;
}
static int getInternalFormat(int format)
{
switch (format)
{
case GL_RGBA:
case GL_BGRA_EXT:
return 4;
case GL_RGB:
case GL_BGR_EXT:
return 3;
case GL_LUMINANCE_ALPHA:
return 2;
case GL_ALPHA:
case GL_INTENSITY:
case GL_LUMINANCE:
return 1;
case GL_DSDT_NV:
return format;
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return format;
default:
return 0;
}
}
#if 0
// Required in order to support on-the-fly compression; currently, this
// feature is disabled.
static int getCompressedInternalFormat(int format)
{
switch (format)
{
case GL_RGB:
case GL_BGR_EXT:
return GL_COMPRESSED_RGB_ARB;
case GL_RGBA:
case GL_BGRA_EXT:
return GL_COMPRESSED_RGBA_ARB;
case GL_ALPHA:
return GL_COMPRESSED_ALPHA_ARB;
case GL_LUMINANCE:
return GL_COMPRESSED_LUMINANCE_ARB;
case GL_LUMINANCE_ALPHA:
return GL_COMPRESSED_LUMINANCE_ALPHA_ARB;
case GL_INTENSITY:
return GL_COMPRESSED_INTENSITY_ARB;
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return format;
default:
return 0;
}
}
#endif
static int getCompressedBlockSize(int format)
{
return format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16;
}
static GLenum GetGLTexAddressMode(Texture::AddressMode addressMode)
{
const TextureCaps& caps = GetTextureCaps();
switch (addressMode)
{
case Texture::Wrap:
return GL_REPEAT;
case Texture::EdgeClamp:
return caps.clampToEdgeSupported ? GL_CLAMP_TO_EDGE : GL_CLAMP;
case Texture::BorderClamp:
if (caps.clampToBorderSupported)
return GL_CLAMP_TO_BORDER_ARB;
else
return caps.clampToEdgeSupported ? GL_CLAMP_TO_EDGE : GL_CLAMP;
}
return 0;
}
static void SetBorderColor(Color borderColor, GLenum target)
{
float bc[4] = { borderColor.red(), borderColor.green(),
borderColor.blue(), borderColor.alpha() };
glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, bc);
}
// Load a prebuilt set of mipmaps; assumes that the image contains
// a complete set of mipmap levels.
static void LoadMipmapSet(Image& img, GLenum target)
{
int internalFormat = getInternalFormat(img.getFormat());
for (int mip = 0; mip < img.getMipLevelCount(); mip++)
{
unsigned int mipWidth = max((uint) img.getWidth() >> mip, 1u);
unsigned int mipHeight = max((uint) img.getHeight() >> mip, 1u);
if (img.isCompressed())
{
glCompressedTexImage2DARB(target,
mip,
internalFormat,
mipWidth, mipHeight,
0,
img.getMipLevelSize(mip),
img.getMipLevel(mip));
}
else
{
glTexImage2D(target,
mip,
internalFormat,
mipWidth, mipHeight,
0,
(GLenum) img.getFormat(),
GL_UNSIGNED_BYTE,
img.getMipLevel(mip));
}
}
}
// Load a texture without any mipmaps
static void LoadMiplessTexture(Image& img, GLenum target)
{
int internalFormat = getInternalFormat(img.getFormat());
if (img.isCompressed())
{
glCompressedTexImage2DARB(target,
0,
internalFormat,
img.getWidth(), img.getHeight(),
0,
img.getMipLevelSize(0),
img.getMipLevel(0));
}
else
{
glTexImage2D(target,
0,
internalFormat,
img.getWidth(), img.getHeight(),
0,
(GLenum) img.getFormat(),
GL_UNSIGNED_BYTE,
img.getMipLevel(0));
}
}
static int ilog2(unsigned int x)
{
int n = -1;
while (x != 0)
{
x >>= 1;
n++;
}
return n;
}
static int CalcMipLevelCount(int w, int h)
{
return max(ilog2(w), ilog2(h)) + 1;
}
Texture::Texture(int w, int h, int d) :
width(w),
height(h),
depth(d)
{
}
int Texture::getLODCount() const
{
return 1;
}
int Texture::getUTileCount(int /*unused*/) const
{
return 1;
}
int Texture::getVTileCount(int /*unused*/) const
{
return 1;
}
int Texture::getWTileCount(int /*unused*/) const
{
return 1;
}
void Texture::setBorderColor(Color /*unused*/)
{
}
int Texture::getWidth() const
{
return width;
}
int Texture::getHeight() const
{
return height;
}
int Texture::getDepth() const
{
return depth;
}
unsigned int Texture::getFormatOptions() const
{
return formatOptions;
}
void Texture::setFormatOptions(unsigned int opts)
{
formatOptions = opts;
}
ImageTexture::ImageTexture(Image& img,
AddressMode addressMode,
MipMapMode mipMapMode) :
Texture(img.getWidth(), img.getHeight()),
glName(0)
{
glGenTextures(1, (GLuint*) &glName);
glBindTexture(GL_TEXTURE_2D, glName);
bool mipmap = mipMapMode != NoMipMaps;
bool precomputedMipMaps = false;
// Use precomputed mipmaps only if a complete set is supplied
int mipLevelCount = img.getMipLevelCount();
if (mipmap && mipLevelCount == CalcMipLevelCount(img.getWidth(), img.getHeight()))
{
precomputedMipMaps = true;
}
// We can't automatically generate mipmaps for compressed textures.
// If a precomputed mipmap set isn't provided, turn off mipmapping entirely.
if (!precomputedMipMaps && img.isCompressed())
{
mipmap = false;
}
GLenum texAddress = GetGLTexAddressMode(addressMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
if (GLEW_EXT_texture_filter_anisotropic && texCaps.preferredAnisotropy > 1)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, texCaps.preferredAnisotropy);
}
if (mipMapMode == AutoMipMaps)
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
int internalFormat = getInternalFormat(img.getFormat());
if (mipmap)
{
if (precomputedMipMaps)
{
LoadMipmapSet(img, GL_TEXTURE_2D);
}
else if (mipMapMode == DefaultMipMaps)
{
gluBuild2DMipmaps(GL_TEXTURE_2D,
internalFormat,
getWidth(), getHeight(),
(GLenum) img.getFormat(),
GL_UNSIGNED_BYTE,
img.getPixels());
}
else
{
assert(mipMapMode == AutoMipMaps);
LoadMiplessTexture(img, GL_TEXTURE_2D);
}
}
else
{
LoadMiplessTexture(img, GL_TEXTURE_2D);
}
alpha = img.hasAlpha();
compressed = img.isCompressed();
}
ImageTexture::~ImageTexture()
{
if (glName != 0)
glDeleteTextures(1, (const GLuint*) &glName);
}
void ImageTexture::bind()
{
glBindTexture(GL_TEXTURE_2D, glName);
}
const TextureTile ImageTexture::getTile(int lod, int u, int v)
{
if (lod != 0 || u != 0 || v != 0)
return TextureTile(0);
return TextureTile(glName);
}
unsigned int ImageTexture::getName() const
{
return glName;
}
void ImageTexture::setBorderColor(Color borderColor)
{
bind();
SetBorderColor(borderColor, GL_TEXTURE_2D);
}
TiledTexture::TiledTexture(Image& img,
int _uSplit, int _vSplit,
MipMapMode mipMapMode) :
Texture(img.getWidth(), img.getHeight()),
uSplit(_uSplit),
vSplit(_vSplit),
glNames(nullptr)
{
glNames = new uint[uSplit * vSplit];
{
for (int i = 0; i < uSplit * vSplit; i++)
glNames[i] = 0;
}
alpha = img.hasAlpha();
compressed = img.isCompressed();
bool mipmap = mipMapMode != NoMipMaps;
bool precomputedMipMaps = false;
// Require a complete set of mipmaps
int mipLevelCount = img.getMipLevelCount();
int completeMipCount = CalcMipLevelCount(img.getWidth(), img.getHeight());
// Allow a bit of slack here--it turns out that some tools don't want to
// calculate the 1x1 mip level. Rather than turn off mipmaps, we'll just
// point the 1x1 mip to the 2x1.
if (mipmap && mipLevelCount >= completeMipCount - 1)
precomputedMipMaps = true;
// We can't automatically generate mipmaps for compressed textures.
// If a precomputed mipmap set isn't provided, turn of mipmapping entirely.
if (!precomputedMipMaps && img.isCompressed())
mipmap = false;
GLenum texAddress = GetGLTexAddressMode(EdgeClamp);
int internalFormat = getInternalFormat(img.getFormat());
int components = img.getComponents();
// Create a temporary image which we'll use for the tile texels
int tileWidth = img.getWidth() / uSplit;
int tileHeight = img.getHeight() / vSplit;
int tileMipLevelCount = CalcMipLevelCount(tileWidth, tileHeight);
Image* tile = new Image(img.getFormat(),
tileWidth, tileHeight,
tileMipLevelCount);
if (tile == nullptr)
return;
for (int v = 0; v < vSplit; v++)
{
for (int u = 0; u < uSplit; u++)
{
// Create the texture and set up sampling and addressing
glGenTextures(1, (GLuint*)&glNames[v * uSplit + u]);
glBindTexture(GL_TEXTURE_2D, glNames[v * uSplit + u]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texAddress);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
if (GLEW_EXT_texture_filter_anisotropic && texCaps.preferredAnisotropy > 1)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, texCaps.preferredAnisotropy);
}
// Copy texels from the subtexture area to the pixel buffer. This
// is straightforward for normal textures, but an immense headache
// for compressed textures with prebuilt mipmaps.
if (precomputedMipMaps)
{
if (img.isCompressed())
{
for (int mip = 0; mip < tileMipLevelCount; mip++)
{
int blockSize = getCompressedBlockSize(img.getFormat());
unsigned char* imgMip =
img.getMipLevel(min(mip, mipLevelCount));
unsigned int mipWidth = max((uint) img.getWidth() >> mip, 1u);
unsigned char* tileMip = tile->getMipLevel(mip);
unsigned int tileMipWidth = max((uint) tile->getWidth() >> mip, 1u);
unsigned int tileMipHeight = max((uint) tile->getHeight() >> mip, 1u);
int uBlocks = max(tileMipWidth / 4, 1u);
int vBlocks = max(tileMipHeight / 4, 1u);
int destBytesPerRow = uBlocks * blockSize;
int srcBytesPerRow = max(mipWidth / 4, 1u) * blockSize;
int srcU = u * tileMipWidth / 4;
int srcV = v * tileMipHeight / 4;
int tileOffset = srcV * srcBytesPerRow +
srcU * blockSize;
for (int y = 0; y < vBlocks; y++)
{
memcpy(tileMip + y * destBytesPerRow,
imgMip + tileOffset + y * srcBytesPerRow,
destBytesPerRow);
}
}
}
else
{
// TODO: Handle uncompressed textures with prebuilt mipmaps
}
LoadMipmapSet(*tile, GL_TEXTURE_2D);
}
else
{
if (img.isCompressed())
{
int blockSize = getCompressedBlockSize(img.getFormat());
int uBlocks = max(tileWidth / 4, 1);
int vBlocks = max(tileHeight / 4, 1);
int destBytesPerRow = uBlocks * blockSize;
int srcBytesPerRow = max(img.getWidth() / 4, 1) * blockSize;
int srcU = u * tileWidth / 4;
int srcV = v * tileHeight / 4;
int tileOffset = srcV * srcBytesPerRow +
srcU * blockSize;
for (int y = 0; y < vBlocks; y++)
{
memcpy(tile->getPixels() + y * destBytesPerRow,
img.getPixels() + tileOffset + y * srcBytesPerRow,
destBytesPerRow);
}
}
else
{
unsigned char* tilePixels = img.getPixels() +
(v * tileHeight * img.getWidth() + u * tileWidth) * components;
for (int y = 0; y < tileHeight; y++)
{
memcpy(tile->getPixels() + y * tileWidth * components,
tilePixels + y * img.getWidth() * components,
tileWidth * components);
}
}
if (mipmap)
{
gluBuild2DMipmaps(GL_TEXTURE_2D,
internalFormat,
tileWidth, tileHeight,
(GLenum) tile->getFormat(),
GL_UNSIGNED_BYTE,
tile->getPixels());
}
else
{
LoadMiplessTexture(*tile, GL_TEXTURE_2D);
}
}
}
}
delete tile;
}
TiledTexture::~TiledTexture()
{
if (glNames != nullptr)
{
for (int i = 0; i < uSplit * vSplit; i++)
{
if (glNames[i] != 0)
glDeleteTextures(1, (const GLuint*) &glNames[i]);
}
delete[] glNames;
}
}
void TiledTexture::bind()
{
}
void TiledTexture::setBorderColor(Color borderColor)
{
for (int i = 0; i < vSplit; i++)
{
for (int j = 0; j < uSplit; j++)
{
glBindTexture(GL_TEXTURE_2D, glNames[i * uSplit + j]);
SetBorderColor(borderColor, GL_TEXTURE_2D);
}
}
}
int TiledTexture::getUTileCount(int /*lod*/) const
{
return uSplit;
}
int TiledTexture::getVTileCount(int /*lod*/) const
{
return vSplit;
}
const TextureTile TiledTexture::getTile(int lod, int u, int v)
{
if (lod != 0 || u >= uSplit || u < 0 || v >= vSplit || v < 0)
return TextureTile(0);
return TextureTile(glNames[v * uSplit + u]);
}
CubeMap::CubeMap(Image* faces[]) :
Texture(faces[0]->getWidth(), faces[0]->getHeight()),
glName(0)
{
// Verify that all the faces are square and have the same size
int width = faces[0]->getWidth();
int format = faces[0]->getFormat();
int i = 0;
for (i = 0; i < 6; i++)
{
if (faces[i]->getWidth() != width ||
faces[i]->getHeight() != width ||
faces[i]->getFormat() != format)
return;
}
// For now, always enable mipmaps; in the future, it should be possible to
// override this.
bool mipmap = true;
bool precomputedMipMaps = false;
// Require a complete set of mipmaps
int mipLevelCount = faces[0]->getMipLevelCount();
if (mipmap && mipLevelCount == CalcMipLevelCount(width, width))
precomputedMipMaps = true;
// We can't automatically generate mipmaps for compressed textures.
// If a precomputed mipmap set isn't provided, turn of mipmapping entirely.
if (!precomputedMipMaps && faces[0]->isCompressed())
mipmap = false;
glGenTextures(1, (GLuint*) &glName);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, glName);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER,
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
int internalFormat = getInternalFormat(format);
for (i = 0; i < 6; i++)
{
auto targetFace = (GLenum) ((int) GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i);
Image* face = faces[i];
if (mipmap)
{
if (precomputedMipMaps)
{
LoadMipmapSet(*face, targetFace);
}
else
{
gluBuild2DMipmaps(targetFace,
internalFormat,
getWidth(), getHeight(),
(GLenum) face->getFormat(),
GL_UNSIGNED_BYTE,
face->getPixels());
}
}
else
{
LoadMiplessTexture(*face, targetFace);
}
}
}
CubeMap::~CubeMap()
{
if (glName != 0)
glDeleteTextures(1, (const GLuint*) &glName);
}
void CubeMap::bind()
{
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, glName);
}
const TextureTile CubeMap::getTile(int lod, int u, int v)
{
if (lod != 0 || u != 0 || v != 0)
return TextureTile(0);
return TextureTile(glName);
}
void CubeMap::setBorderColor(Color borderColor)
{
bind();
SetBorderColor(borderColor, GL_TEXTURE_CUBE_MAP_ARB);
}
Texture* CreateProceduralTexture(int width, int height,
int format,
ProceduralTexEval func,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
Image* img = new Image(format, width, height);
if (img == nullptr)
return nullptr;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float u = ((float) x + 0.5f) / (float) width * 2 - 1;
float v = ((float) y + 0.5f) / (float) height * 2 - 1;
func(u, v, 0, img->getPixelRow(y) + x * img->getComponents());
}
}
Texture* tex = new ImageTexture(*img, addressMode, mipMode);
delete img;
return tex;
}
Texture* CreateProceduralTexture(int width, int height,
int format,
TexelFunctionObject& func,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
Image* img = new Image(format, width, height);
if (img == nullptr)
return nullptr;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float u = ((float) x + 0.5f) / (float) width * 2 - 1;
float v = ((float) y + 0.5f) / (float) height * 2 - 1;
func(u, v, 0, img->getPixelRow(y) + x * img->getComponents());
}
}
Texture* tex = new ImageTexture(*img, addressMode, mipMode);
delete img;
return tex;
}
// Helper function for CreateProceduralCubeMap; return the normalized
// vector pointing to (s, t) on the specified face.
static Vector3f cubeVector(int face, float s, float t)
{
Vector3f v;
switch (face)
{
case 0:
v = Vector3f(1.0f, -t, -s);
break;
case 1:
v = Vector3f(-1.0f, -t, s);
break;
case 2:
v = Vector3f(s, 1.0f, t);
break;
case 3:
v = Vector3f(s, -1.0f, -t);
break;
case 4:
v = Vector3f(s, -t, 1.0f);
break;
case 5:
v = Vector3f(-s, -t, -1.0f);
break;
default:
// assert(false);
break;
}
v.normalize();
return v;
}
extern Texture* CreateProceduralCubeMap(int size, int format,
ProceduralTexEval func)
{
Image* faces[6];
bool failed = false;
int i = 0;
for (i = 0; i < 6; i++)
{
faces[i] = nullptr;
faces[i] = new Image(format, size, size);
if (faces[i] == nullptr)
failed = true;
}
if (!failed)
{
for (int i = 0; i < 6; i++)
{
Image* face = faces[i];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float s = ((float) x + 0.5f) / (float) size * 2 - 1;
float t = ((float) y + 0.5f) / (float) size * 2 - 1;
Vector3f v = cubeVector(i, s, t);
func(v.x(), v.y(), v.z(),
face->getPixelRow(y) + x * face->getComponents());
}
}
}
}
Texture* tex = new CubeMap(faces);
// Clean up the images
for (i = 0; i < 6; i++)
{
if (faces[i] != nullptr)
delete faces[i];
}
return tex;
}
#if 0
static bool isPow2(int x)
{
return ((x & (x - 1)) == 0);
}
#endif
static Texture* CreateTextureFromImage(Image& img,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
#if 0
// Require texture dimensions to be powers of two. Even though the
// OpenGL driver will automatically rescale textures with non-power of
// two sizes, some quality loss may result. The power of two requirement
// forces texture creators to resize their textures in an image editing
// program, hopefully resulting in textures that look as good as possible
// when rendered on current hardware.
if (!isPow2(img.getWidth()) || !isPow2(img.getHeight()))
{
clog << "Texture has non-power of two dimensions.\n";
return nullptr;
}
#endif
// If non power of two textures are supported switch mipmap generation to
// automatic. gluBuildMipMaps may rescale the texture to a power of two
// on some drivers even when the hardware supports non power of two textures,
// whereas auto mipmap generation will properly deal with the dimensions.
if (GetTextureCaps().nonPow2Supported)
{
if (mipMode == Texture::DefaultMipMaps)
mipMode = Texture::AutoMipMaps;
}
bool splittingAllowed = true;
Texture* tex = nullptr;
int maxDim = GetTextureCaps().maxTextureSize;
if ((img.getWidth() > maxDim || img.getHeight() > maxDim) &&
splittingAllowed)
{
// The texture is too large; we need to split it.
int uSplit = max(1, img.getWidth() / maxDim);
int vSplit = max(1, img.getHeight() / maxDim);
clog << _("Creating tiled texture. Width=") << img.getWidth() << _(", max=") << maxDim << "\n";
tex = new TiledTexture(img, uSplit, vSplit, mipMode);
}
else
{
clog << _("Creating ordinary texture: ") << img.getWidth() << "x" << img.getHeight() << "\n";
// The image is small enough to fit in a single texture; or, splitting
// was disallowed so we'll scale the large image down to fit in
// an ordinary texture.
tex = new ImageTexture(img, addressMode, mipMode);
}
return tex;
}
Texture* LoadTextureFromFile(const string& filename,
Texture::AddressMode addressMode,
Texture::MipMapMode mipMode)
{
// Check for a Celestia texture--these need to be handled specially.
ContentType contentType = DetermineFileType(filename);
if (contentType == Content_CelestiaTexture)
return LoadVirtualTexture(filename);
// All other texture types are handled by first loading an image, then
// creating a texture from that image.
Image* img = LoadImageFromFile(filename);
if (img == nullptr)
return nullptr;
Texture* tex = CreateTextureFromImage(*img, addressMode, mipMode);
if (contentType == Content_DXT5NormalMap)
{
// If the texture came from a .dxt5nm file then mark it as a dxt5
// compressed normal map. There's no separate OpenGL format for dxt5
// normal maps, so the file extension is the only thing that
// distinguishes it from a plain old dxt5 texture.
if (img->getFormat() == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
{
tex->setFormatOptions(Texture::DXT5NormalMap);
}
}
delete img;
return tex;
}
// Load a height map texture from a file and convert it to a normal map.
Texture* LoadHeightMapFromFile(const string& filename,
float height,
Texture::AddressMode addressMode)
{
Image* img = LoadImageFromFile(filename);
if (img == nullptr)
return nullptr;
Image* normalMap = img->computeNormalMap(height,
addressMode == Texture::Wrap);
delete img;
if (normalMap == nullptr)
return nullptr;
Texture* tex = CreateTextureFromImage(*normalMap, addressMode,
Texture::DefaultMipMaps);
delete normalMap;
return tex;
}