// * This file is part of the COLOBOT source code // * Copyright (C) 2012, Polish Portal of Colobot (PPC) // * // * 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 3 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. // * // * You should have received a copy of the GNU General Public License // * along with this program. If not, see http://www.gnu.org/licenses/. // gldevice.cpp #include "graphics/opengl/gldevice.h" #include "common/config.h" #include "common/image.h" #include "math/geometry.h" #if defined(USE_GLEW) // When using GLEW, only glew.h is needed #include #else // Should define prototypes of used extensions as OpenGL functions #define GL_GLEXT_PROTOTYPES #include #include #include #endif // if defined(GLEW) #include #include void Gfx::GLDeviceConfig::LoadDefault() { Gfx::DeviceConfig::LoadDefault(); hardwareAccel = true; redSize = 8; blueSize = 8; greenSize = 8; alphaSize = 8; depthSize = 24; } Gfx::CGLDevice::CGLDevice(const Gfx::GLDeviceConfig &config) { m_config = config; m_lighting = false; m_texturing = false; } Gfx::CGLDevice::~CGLDevice() { } void Gfx::CGLDevice::DebugHook() { /* This function is only called here, so it can be used * as a breakpoint when debugging using gDEBugger */ glColor3i(0, 0, 0); } std::string Gfx::CGLDevice::GetError() { return m_error; } bool Gfx::CGLDevice::Create() { #if defined(USE_GLEW) static bool glewInited = false; if (!glewInited) { glewInited = true; if (glewInit() != GLEW_OK) { m_error = "GLEW initialization failed"; return false; } if ( (! GLEW_ARB_multitexture) || (! GLEW_EXT_texture_env_combine) || (! GLEW_EXT_secondary_color) ) { m_error = "GLEW reports required extensions not supported"; return false; } } #endif /* NOTE: when not using GLEW, extension testing is not performed, as it is assumed that glext.h is up-to-date and the OpenGL shared library has the required functions present. */ // This is mostly done in all modern hardware by default // DirectX doesn't even allow the option to turn off perspective correction anymore // So turn it on permanently glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // To use separate specular color in drawing primitives glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); // To avoid problems with scaling & lighting glEnable(GL_RESCALE_NORMAL); // Set just to be sure glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glViewport(0, 0, m_config.size.x, m_config.size.y); m_lights = std::vector(GL_MAX_LIGHTS, Gfx::Light()); m_lightsEnabled = std::vector (GL_MAX_LIGHTS, false); int maxTextures = 0; glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxTextures); m_currentTextures = std::vector (maxTextures, Gfx::Texture()); m_texturesEnabled = std::vector (maxTextures, false); m_textureStageParams = std::vector(maxTextures, Gfx::TextureStageParams()); return true; } void Gfx::CGLDevice::Destroy() { // Delete the remaining textures // Should not be strictly necessary, but just in case DestroyAllTextures(); m_lights.clear(); m_lightsEnabled.clear(); m_currentTextures.clear(); m_texturesEnabled.clear(); m_textureStageParams.clear(); } void Gfx::CGLDevice::ConfigChanged(const Gfx::GLDeviceConfig& newConfig) { m_config = newConfig; // Reset state m_lighting = false; m_texturing = false; Destroy(); Create(); } void Gfx::CGLDevice::BeginScene() { Clear(); glMatrixMode(GL_PROJECTION); glLoadMatrixf(m_projectionMat.Array()); UpdateModelviewMatrix(); } void Gfx::CGLDevice::EndScene() { glFlush(); } void Gfx::CGLDevice::Clear() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void Gfx::CGLDevice::SetTransform(Gfx::TransformType type, const Math::Matrix &matrix) { if (type == Gfx::TRANSFORM_WORLD) { m_worldMat = matrix; UpdateModelviewMatrix(); } else if (type == Gfx::TRANSFORM_VIEW) { m_viewMat = matrix; UpdateModelviewMatrix(); } else if (type == Gfx::TRANSFORM_PROJECTION) { m_projectionMat = matrix; glMatrixMode(GL_PROJECTION); glLoadMatrixf(m_projectionMat.Array()); } else { assert(false); } } const Math::Matrix& Gfx::CGLDevice::GetTransform(Gfx::TransformType type) { if (type == Gfx::TRANSFORM_WORLD) return m_worldMat; else if (type == Gfx::TRANSFORM_VIEW) return m_viewMat; else if (type == Gfx::TRANSFORM_PROJECTION) return m_projectionMat; else assert(false); return m_worldMat; // to avoid warning } void Gfx::CGLDevice::MultiplyTransform(Gfx::TransformType type, const Math::Matrix &matrix) { if (type == Gfx::TRANSFORM_WORLD) { m_worldMat = Math::MultiplyMatrices(m_worldMat, matrix); UpdateModelviewMatrix(); } else if (type == Gfx::TRANSFORM_VIEW) { m_viewMat = Math::MultiplyMatrices(m_viewMat, matrix); UpdateModelviewMatrix(); } else if (type == Gfx::TRANSFORM_PROJECTION) { m_projectionMat = Math::MultiplyMatrices(m_projectionMat, matrix); glMatrixMode(GL_PROJECTION); glLoadMatrixf(m_projectionMat.Array()); } else { assert(false); } } void Gfx::CGLDevice::UpdateModelviewMatrix() { m_modelviewMat = Math::MultiplyMatrices(m_viewMat, m_worldMat); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glScalef(1.0f, 1.0f, -1.0f); glMultMatrixf(m_modelviewMat.Array()); if (m_lighting) { for (int index = 0; index < static_cast( m_lights.size() ); ++index) UpdateLightPosition(index); } } void Gfx::CGLDevice::SetMaterial(const Gfx::Material &material) { m_material = material; glMaterialfv(GL_FRONT, GL_AMBIENT, m_material.ambient.Array()); glMaterialfv(GL_FRONT, GL_DIFFUSE, m_material.diffuse.Array()); glMaterialfv(GL_FRONT, GL_SPECULAR, m_material.specular.Array()); } const Gfx::Material& Gfx::CGLDevice::GetMaterial() { return m_material; } int Gfx::CGLDevice::GetMaxLightCount() { return m_lights.size(); } void Gfx::CGLDevice::SetLight(int index, const Gfx::Light &light) { assert(index >= 0); assert(index < static_cast( m_lights.size() )); m_lights[index] = light; // Indexing from GL_LIGHT0 should always work glLightfv(GL_LIGHT0 + index, GL_AMBIENT, const_cast(light.ambient.Array())); glLightfv(GL_LIGHT0 + index, GL_DIFFUSE, const_cast(light.diffuse.Array())); glLightfv(GL_LIGHT0 + index, GL_SPECULAR, const_cast(light.specular.Array())); glLightf(GL_LIGHT0 + index, GL_CONSTANT_ATTENUATION, light.attenuation0); glLightf(GL_LIGHT0 + index, GL_LINEAR_ATTENUATION, light.attenuation1); glLightf(GL_LIGHT0 + index, GL_QUADRATIC_ATTENUATION, light.attenuation2); if (light.type == Gfx::LIGHT_SPOT) { glLightf(GL_LIGHT0 + index, GL_SPOT_CUTOFF, light.spotAngle); glLightf(GL_LIGHT0 + index, GL_SPOT_EXPONENT, light.spotIntensity); } else { glLightf(GL_LIGHT0 + index, GL_SPOT_CUTOFF, 180.0f); } UpdateLightPosition(index); } void Gfx::CGLDevice::UpdateLightPosition(int index) { assert(index >= 0); assert(index < static_cast( m_lights.size() )); if ((! m_lighting) || (! m_lightsEnabled[index])) return; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glScalef(1.0f, 1.0f, -1.0f); glMultMatrixf(m_viewMat.Array()); if (m_lights[index].type == LIGHT_DIRECTIONAL) { GLfloat position[4] = { m_lights[index].direction.x, m_lights[index].direction.y, m_lights[index].direction.z, 0.0f }; glLightfv(GL_LIGHT0 + index, GL_POSITION, position); } else { GLfloat position[4] = { m_lights[index].position.x, m_lights[index].position.y, m_lights[index].position.z, 1.0f }; glLightfv(GL_LIGHT0 + index, GL_POSITION, position); } if (m_lights[index].type == Gfx::LIGHT_SPOT) { GLfloat direction[4] = { m_lights[index].direction.x, m_lights[index].direction.y, m_lights[index].direction.z, 0.0f }; glLightfv(GL_LIGHT0 + index, GL_SPOT_DIRECTION, direction); } glPopMatrix(); } const Gfx::Light& Gfx::CGLDevice::GetLight(int index) { assert(index >= 0); assert(index < static_cast( m_lights.size() )); return m_lights[index]; } void Gfx::CGLDevice::SetLightEnabled(int index, bool enabled) { assert(index >= 0); assert(index < static_cast( m_lights.size() )); m_lightsEnabled[index] = enabled; glEnable(GL_LIGHT0 + index); } bool Gfx::CGLDevice::GetLightEnabled(int index) { assert(index >= 0); assert(index < static_cast( m_lights.size() )); return m_lightsEnabled[index]; } /** If image is invalid, returns invalid texture. Otherwise, returns pointer to new Gfx::Texture struct. This struct must not be deleted in other way than through DeleteTexture() */ Gfx::Texture Gfx::CGLDevice::CreateTexture(CImage *image, const Gfx::TextureCreateParams ¶ms) { ImageData *data = image->GetData(); if (data == NULL) { m_error = "Invalid texture data"; return Gfx::Texture(); // invalid texture } return CreateTexture(data, params); } Gfx::Texture Gfx::CGLDevice::CreateTexture(ImageData *data, const Gfx::TextureCreateParams ¶ms) { Gfx::Texture result; result.valid = true; result.size.x = data->surface->w; result.size.y = data->surface->h; // Use & enable 1st texture stage glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glGenTextures(1, &result.id); glBindTexture(GL_TEXTURE_2D, result.id); // Set params GLint minF = 0; if (params.minFilter == Gfx::TEX_MIN_FILTER_NEAREST) minF = GL_NEAREST; else if (params.minFilter == Gfx::TEX_MIN_FILTER_LINEAR) minF = GL_LINEAR; else if (params.minFilter == Gfx::TEX_MIN_FILTER_NEAREST_MIPMAP_NEAREST) minF = GL_NEAREST_MIPMAP_NEAREST; else if (params.minFilter == Gfx::TEX_MIN_FILTER_LINEAR_MIPMAP_NEAREST) minF = GL_LINEAR_MIPMAP_NEAREST; else if (params.minFilter == Gfx::TEX_MIN_FILTER_NEAREST_MIPMAP_LINEAR) minF = GL_NEAREST_MIPMAP_LINEAR; else if (params.minFilter == Gfx::TEX_MIN_FILTER_LINEAR_MIPMAP_LINEAR) minF = GL_LINEAR_MIPMAP_LINEAR; else assert(false); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minF); GLint magF = 0; if (params.magFilter == Gfx::TEX_MAG_FILTER_NEAREST) magF = GL_NEAREST; else if (params.magFilter == Gfx::TEX_MAG_FILTER_LINEAR) magF = GL_LINEAR; else assert(false); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magF); if (params.mipmap) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); else glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE); GLenum sourceFormat = 0; if (params.format == Gfx::TEX_IMG_RGB) { sourceFormat = GL_RGB; result.alpha = false; } else if (params.format == Gfx::TEX_IMG_BGR) { sourceFormat = GL_BGR; result.alpha = false; } else if (params.format == Gfx::TEX_IMG_RGBA) { sourceFormat = GL_RGBA; result.alpha = true; } else if (params.format == Gfx::TEX_IMG_BGRA) { sourceFormat = GL_BGRA; result.alpha = true; } else assert(false); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, data->surface->w, data->surface->h, 0, sourceFormat, GL_UNSIGNED_BYTE, data->surface->pixels); // Restore the previous state of 1st stage if (m_currentTextures[0].valid) glBindTexture(GL_TEXTURE_2D, m_currentTextures[0].id); else glBindTexture(GL_TEXTURE_2D, 0); if ( (! m_texturing) || (! m_texturesEnabled[0]) ) glDisable(GL_TEXTURE_2D); return result; } void Gfx::CGLDevice::DestroyTexture(const Gfx::Texture &texture) { std::set::iterator it = m_allTextures.find(texture); if (it != m_allTextures.end()) m_allTextures.erase(it); // Unbind the texture if in use anywhere for (int index = 0; index < static_cast( m_currentTextures.size() ); ++index) { if (m_currentTextures[index] == texture) SetTexture(index, Gfx::Texture()); // set to invalid texture } glDeleteTextures(1, &texture.id); } void Gfx::CGLDevice::DestroyAllTextures() { std::set allCopy = m_allTextures; std::set::iterator it; for (it = allCopy.begin(); it != allCopy.end(); ++it) DestroyTexture(*it); } int Gfx::CGLDevice::GetMaxTextureCount() { return m_currentTextures.size(); } /** If \a texture is invalid, unbinds the given texture. If valid, binds the texture and enables the given texture stage. The setting is remembered, even if texturing is disabled at the moment. */ void Gfx::CGLDevice::SetTexture(int index, const Gfx::Texture &texture) { assert(index >= 0); assert(index < static_cast( m_currentTextures.size() )); // Enable the given texture stage glActiveTexture(GL_TEXTURE0 + index); glEnable(GL_TEXTURE_2D); m_currentTextures[index] = texture; // remember the change if (! texture.valid) { glBindTexture(GL_TEXTURE_2D, 0); // unbind texture } else { glBindTexture(GL_TEXTURE_2D, texture.id); // bind the texture SetTextureStageParams(index, m_textureStageParams[index]); // texture stage params need to be re-set for the new texture } // Disable the stage if it is set so if ( (! m_texturing) || (! m_texturesEnabled[index]) ) glDisable(GL_TEXTURE_2D); } void Gfx::CGLDevice::SetTexture(int index, unsigned int textureId) { assert(index >= 0); assert(index < static_cast( m_currentTextures.size() )); // Enable the given texture stage glActiveTexture(GL_TEXTURE0 + index); glEnable(GL_TEXTURE_2D); m_currentTextures[index].id = textureId; glBindTexture(GL_TEXTURE_2D, textureId); // Disable the stage if it is set so if ( (! m_texturing) || (! m_texturesEnabled[index]) ) glDisable(GL_TEXTURE_2D); } /** Returns the previously assigned texture or invalid texture if the given stage is not enabled. */ Gfx::Texture Gfx::CGLDevice::GetTexture(int index) { assert(index >= 0); assert(index < static_cast( m_currentTextures.size() )); return m_currentTextures[index]; } void Gfx::CGLDevice::SetTextureEnabled(int index, bool enabled) { assert(index >= 0); assert(index < static_cast( m_currentTextures.size() )); m_texturesEnabled[index] = enabled; glActiveTexture(GL_TEXTURE0 + index); if (enabled) glEnable(GL_TEXTURE_2D); else glDisable(GL_TEXTURE_2D); } bool Gfx::CGLDevice::GetTextureEnabled(int index) { assert(index >= 0); assert(index < static_cast( m_currentTextures.size() )); return m_texturesEnabled[index]; } /** Sets the texture parameters for the given texture stage. If the given texture was not set (bound) yet, nothing happens. The settings are remembered, even if texturing is disabled at the moment. */ void Gfx::CGLDevice::SetTextureStageParams(int index, const Gfx::TextureStageParams ¶ms) { assert(index >= 0); assert(index < static_cast( m_currentTextures.size() )); // Remember the settings m_textureStageParams[index] = params; // Don't actually do anything if texture not set if (! m_currentTextures[index].valid) return; // Enable the given stage glActiveTexture(GL_TEXTURE0 + index); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, m_currentTextures[index].id); // To save some trouble if ( (params.colorOperation == Gfx::TEX_MIX_OPER_DEFAULT) && (params.alphaOperation == Gfx::TEX_MIX_OPER_DEFAULT) ) { glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); goto after_tex_operations; } glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // Only these modes of getting color & alpha are used glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); // Color operation if (params.colorOperation == Gfx::TEX_MIX_OPER_DEFAULT) { glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE); goto after_tex_color; } else if (params.colorOperation == Gfx::TEX_MIX_OPER_REPLACE) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); else if (params.colorOperation == Gfx::TEX_MIX_OPER_MODULATE) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); else if (params.colorOperation == Gfx::TEX_MIX_OPER_ADD) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD); else if (params.colorOperation == Gfx::TEX_MIX_OPER_SUBTRACT) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT); else assert(false); // Color arg1 if (params.colorArg1 == Gfx::TEX_MIX_ARG_TEXTURE) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); else if (params.colorArg1 == Gfx::TEX_MIX_ARG_COMPUTED_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); else if (params.colorArg1 == Gfx::TEX_MIX_ARG_SRC_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR); else if (params.colorArg1 == Gfx::TEX_MIX_ARG_FACTOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_CONSTANT); else assert(false); // Color arg2 if (params.colorArg2 == Gfx::TEX_MIX_ARG_TEXTURE) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE); else if (params.colorArg2 == Gfx::TEX_MIX_ARG_COMPUTED_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS); else if (params.colorArg2 == Gfx::TEX_MIX_ARG_SRC_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); else if (params.colorArg2 == Gfx::TEX_MIX_ARG_FACTOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); else assert(false); after_tex_color: // Alpha operation if (params.alphaOperation == Gfx::TEX_MIX_OPER_DEFAULT) { glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE); goto after_tex_operations; } else if (params.colorOperation == Gfx::TEX_MIX_OPER_REPLACE) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); else if (params.alphaOperation == Gfx::TEX_MIX_OPER_MODULATE) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); else if (params.alphaOperation == Gfx::TEX_MIX_OPER_ADD) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_ADD); else if (params.alphaOperation == Gfx::TEX_MIX_OPER_SUBTRACT) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_SUBTRACT); else assert(false); // Alpha arg1 if (params.alphaArg1 == Gfx::TEX_MIX_ARG_TEXTURE) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); else if (params.alphaArg1 == Gfx::TEX_MIX_ARG_COMPUTED_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); else if (params.alphaArg1 == Gfx::TEX_MIX_ARG_SRC_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PRIMARY_COLOR); else if (params.alphaArg1 == Gfx::TEX_MIX_ARG_FACTOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_CONSTANT); else assert(false); // Alpha arg2 if (params.alphaArg2 == Gfx::TEX_MIX_ARG_TEXTURE) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE); else if (params.alphaArg2 == Gfx::TEX_MIX_ARG_COMPUTED_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PREVIOUS); else if (params.alphaArg2 == Gfx::TEX_MIX_ARG_SRC_COLOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); else if (params.alphaArg2 == Gfx::TEX_MIX_ARG_FACTOR) glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_CONSTANT); else assert(false); after_tex_operations: if (params.wrapS == Gfx::TEX_WRAP_CLAMP) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); else if (params.wrapS == Gfx::TEX_WRAP_REPEAT) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); else assert(false); if (params.wrapT == Gfx::TEX_WRAP_CLAMP) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); else if (params.wrapT == Gfx::TEX_WRAP_REPEAT) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); else assert(false); // Disable the stage if it is set so if ( (! m_texturing) || (! m_texturesEnabled[index]) ) glDisable(GL_TEXTURE_2D); } Gfx::TextureStageParams Gfx::CGLDevice::GetTextureStageParams(int index) { assert(index >= 0); assert(index < static_cast( m_currentTextures.size() )); return m_textureStageParams[index]; } void Gfx::CGLDevice::SetTextureFactor(const Gfx::Color &color) { // Needs to be set for all texture stages for (int index = 0; index < static_cast( m_currentTextures.size() ); ++index) { // Activate stage glActiveTexture(GL_TEXTURE0 + index); glEnable(GL_TEXTURE_2D); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color.Array()); // Disable the stage if it is set so if ( (! m_texturing) || (! m_texturesEnabled[index]) ) glDisable(GL_TEXTURE_2D); } } Gfx::Color Gfx::CGLDevice::GetTextureFactor() { // Get from 1st stage (should be the same for all stages) glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); GLfloat color[4] = { 0.0f }; glGetTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color); // Disable the 1st stage if it is set so if ( (! m_texturing) || (! m_texturesEnabled[0]) ) glDisable(GL_TEXTURE_2D); return Gfx::Color(color[0], color[1], color[2], color[3]); } GLenum TranslateGfxPrimitive(Gfx::PrimitiveType type) { GLenum flag = 0; switch (type) { case Gfx::PRIMITIVE_POINTS: flag = GL_POINTS; break; case Gfx::PRIMITIVE_LINES: flag = GL_LINES; break; case Gfx::PRIMITIVE_LINE_STRIP: flag = GL_LINE_STRIP; break; case Gfx::PRIMITIVE_TRIANGLES: flag = GL_TRIANGLES; break; case Gfx::PRIMITIVE_TRIANGLE_STRIP: flag = GL_TRIANGLE_STRIP; break; default: assert(false); break; } return flag; } void Gfx::CGLDevice::DrawPrimitive(Gfx::PrimitiveType type, const Vertex *vertices, int vertexCount) { glBegin(TranslateGfxPrimitive(type)); glColor3f(1.0f, 1.0f, 1.0f); for (int i = 0; i < vertexCount; ++i) { glNormal3fv(const_cast(vertices[i].normal.Array())); glMultiTexCoord2fv(GL_TEXTURE0, const_cast(vertices[i].texCoord.Array())); glVertex3fv(const_cast(vertices[i].coord.Array())); } glEnd(); } void Gfx::CGLDevice::DrawPrimitive(Gfx::PrimitiveType type, const Gfx::VertexCol *vertices, int vertexCount) { glBegin(TranslateGfxPrimitive(type)); for (int i = 0; i < vertexCount; ++i) { glColor4fv(const_cast(vertices[i].color.Array())); glSecondaryColor3fv(const_cast(vertices[i].specular.Array())); glMultiTexCoord2fv(GL_TEXTURE0, const_cast(vertices[i].texCoord.Array())); glVertex3fv(const_cast(vertices[i].coord.Array())); } glEnd(); } void Gfx::CGLDevice::DrawPrimitive(Gfx::PrimitiveType type, const VertexTex2 *vertices, int vertexCount) { glBegin(TranslateGfxPrimitive(type)); glColor3f(1.0f, 1.0f, 1.0f); for (int i = 0; i < vertexCount; ++i) { glNormal3fv(const_cast(vertices[i].normal.Array())); glMultiTexCoord2fv(GL_TEXTURE0, const_cast(vertices[i].texCoord.Array())); glMultiTexCoord2fv(GL_TEXTURE1, const_cast(vertices[i].texCoord.Array())); glVertex3fv(const_cast(vertices[i].coord.Array())); } glEnd(); } bool InPlane(Math::Vector normal, float originPlane, Math::Vector center, float radius) { float distance = (originPlane + Math::DotProduct(normal, center)) / normal.Length(); if (distance < -radius) return true; return false; } /* The implementation of ComputeSphereVisibility is taken from libwine's device.c Copyright of the WINE team, licensed under GNU LGPL v 2.1 */ // TODO: testing int Gfx::CGLDevice::ComputeSphereVisibility(const Math::Vector ¢er, float radius) { Math::Matrix m; m.LoadIdentity(); m = Math::MultiplyMatrices(m, m_worldMat); m = Math::MultiplyMatrices(m, m_viewMat); m = Math::MultiplyMatrices(m, m_projectionMat); Math::Vector vec[6]; float originPlane[6]; // Left plane vec[0].x = m.Get(4, 1) + m.Get(1, 1); vec[0].y = m.Get(4, 2) + m.Get(1, 2); vec[0].z = m.Get(4, 3) + m.Get(1, 3); originPlane[0] = m.Get(4, 4) + m.Get(1, 4); // Right plane vec[1].x = m.Get(4, 1) - m.Get(1, 1); vec[1].y = m.Get(4, 2) - m.Get(1, 2); vec[1].z = m.Get(4, 3) - m.Get(1, 3); originPlane[1] = m.Get(4, 4) - m.Get(1, 4); // Top plane vec[2].x = m.Get(4, 1) - m.Get(2, 1); vec[2].y = m.Get(4, 2) - m.Get(2, 2); vec[2].z = m.Get(4, 3) - m.Get(2, 3); originPlane[2] = m.Get(4, 4) - m.Get(2, 4); // Bottom plane vec[3].x = m.Get(4, 1) + m.Get(2, 1); vec[3].y = m.Get(4, 2) + m.Get(2, 2); vec[3].z = m.Get(4, 3) + m.Get(2, 3); originPlane[3] = m.Get(4, 4) + m.Get(2, 4); // Front plane vec[4].x = m.Get(3, 1); vec[4].y = m.Get(3, 2); vec[4].z = m.Get(3, 3); originPlane[4] = m.Get(3, 4); // Back plane vec[5].x = m.Get(4, 1) - m.Get(3, 1); vec[5].y = m.Get(4, 2) - m.Get(3, 2); vec[5].z = m.Get(4, 3) - m.Get(3, 3); originPlane[5] = m.Get(4, 4) - m.Get(3, 4); int result = 0; if (InPlane(vec[0], originPlane[0], center, radius)) result |= Gfx::INTERSECT_PLANE_LEFT; if (InPlane(vec[1], originPlane[1], center, radius)) result |= Gfx::INTERSECT_PLANE_RIGHT; if (InPlane(vec[2], originPlane[2], center, radius)) result |= Gfx::INTERSECT_PLANE_TOP; if (InPlane(vec[3], originPlane[3], center, radius)) result |= Gfx::INTERSECT_PLANE_BOTTOM; if (InPlane(vec[4], originPlane[4], center, radius)) result |= Gfx::INTERSECT_PLANE_FRONT; if (InPlane(vec[5], originPlane[5], center, radius)) result |= Gfx::INTERSECT_PLANE_BACK; return result; } void Gfx::CGLDevice::SetRenderState(Gfx::RenderState state, bool enabled) { if (state == Gfx::RENDER_STATE_DEPTH_WRITE) { glDepthMask(enabled ? GL_TRUE : GL_FALSE); return; } else if (state == Gfx::RENDER_STATE_LIGHTING) { m_lighting = enabled; if (enabled) glEnable(GL_LIGHTING); else glDisable(GL_LIGHTING); if (enabled) { for (int index = 0; index < static_cast( m_lights.size() ); ++index) UpdateLightPosition(index); } return; } else if (state == Gfx::RENDER_STATE_TEXTURING) { m_texturing = enabled; // Enable/disable stages with new setting for (int index = 0; index < static_cast( m_currentTextures.size() ); ++index) { glActiveTexture(GL_TEXTURE0 + index); if (m_texturing && m_texturesEnabled[index]) glEnable(GL_TEXTURE_2D); else glDisable(GL_TEXTURE_2D); } return; } GLenum flag = 0; switch (state) { case Gfx::RENDER_STATE_BLENDING: flag = GL_BLEND; break; case Gfx::RENDER_STATE_FOG: flag = GL_FOG; break; case Gfx::RENDER_STATE_DEPTH_TEST: flag = GL_DEPTH_TEST; break; case Gfx::RENDER_STATE_ALPHA_TEST: flag = GL_ALPHA_TEST; break; case Gfx::RENDER_STATE_CULLING: flag = GL_CULL_FACE; break; case Gfx::RENDER_STATE_DITHERING: flag = GL_DITHER; break; default: assert(false); break; } if (enabled) glEnable(flag); else glDisable(flag); } bool Gfx::CGLDevice::GetRenderState(Gfx::RenderState state) { if (state == Gfx::RENDER_STATE_LIGHTING) return m_lighting; if (state == Gfx::RENDER_STATE_TEXTURING) return m_texturing; GLenum flag = 0; switch (state) { case Gfx::RENDER_STATE_DEPTH_WRITE: flag = GL_DEPTH_WRITEMASK; break; case Gfx::RENDER_STATE_BLENDING: flag = GL_BLEND; break; case Gfx::RENDER_STATE_FOG: flag = GL_FOG; break; case Gfx::RENDER_STATE_DEPTH_TEST: flag = GL_DEPTH_TEST; break; case Gfx::RENDER_STATE_ALPHA_TEST: flag = GL_ALPHA_TEST; break; case Gfx::RENDER_STATE_CULLING: flag = GL_CULL_FACE; break; case Gfx::RENDER_STATE_DITHERING: flag = GL_DITHER; break; default: assert(false); break; } GLboolean result = GL_FALSE; glGetBooleanv(flag, &result); return result == GL_TRUE; } Gfx::CompFunc TranslateGLCompFunc(GLenum flag) { switch (flag) { case GL_NEVER: return Gfx::COMP_FUNC_NEVER; case GL_LESS: return Gfx::COMP_FUNC_LESS; case GL_EQUAL: return Gfx::COMP_FUNC_EQUAL; case GL_NOTEQUAL: return Gfx::COMP_FUNC_NOTEQUAL; case GL_LEQUAL: return Gfx::COMP_FUNC_LEQUAL; case GL_GREATER: return Gfx::COMP_FUNC_GREATER; case GL_GEQUAL: return Gfx::COMP_FUNC_GEQUAL; case GL_ALWAYS: return Gfx::COMP_FUNC_ALWAYS; default: assert(false); break; } return Gfx::COMP_FUNC_NEVER; } GLenum TranslateGfxCompFunc(Gfx::CompFunc func) { switch (func) { case Gfx::COMP_FUNC_NEVER: return GL_NEVER; case Gfx::COMP_FUNC_LESS: return GL_LESS; case Gfx::COMP_FUNC_EQUAL: return GL_EQUAL; case Gfx::COMP_FUNC_NOTEQUAL: return GL_NOTEQUAL; case Gfx::COMP_FUNC_LEQUAL: return GL_LEQUAL; case Gfx::COMP_FUNC_GREATER: return GL_GREATER; case Gfx::COMP_FUNC_GEQUAL: return GL_GEQUAL; case Gfx::COMP_FUNC_ALWAYS: return GL_ALWAYS; default: assert(false); break; } return 0; } void Gfx::CGLDevice::SetDepthTestFunc(Gfx::CompFunc func) { glDepthFunc(TranslateGfxCompFunc(func)); } Gfx::CompFunc Gfx::CGLDevice::GetDepthTestFunc() { GLint flag = 0; glGetIntegerv(GL_DEPTH_FUNC, &flag); return TranslateGLCompFunc(static_cast(flag)); } void Gfx::CGLDevice::SetDepthBias(float factor) { glPolygonOffset(factor, 0.0f); } float Gfx::CGLDevice::GetDepthBias() { GLfloat result = 0.0f; glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &result); return result; } void Gfx::CGLDevice::SetAlphaTestFunc(Gfx::CompFunc func, float refValue) { glAlphaFunc(TranslateGfxCompFunc(func), refValue); } void Gfx::CGLDevice::GetAlphaTestFunc(Gfx::CompFunc &func, float &refValue) { GLint flag = 0; glGetIntegerv(GL_ALPHA_TEST_FUNC, &flag); func = TranslateGLCompFunc(static_cast(flag)); glGetFloatv(GL_ALPHA_TEST_REF, static_cast(&refValue)); } Gfx::BlendFunc TranslateGLBlendFunc(GLenum flag) { switch (flag) { case GL_ZERO: return Gfx::BLEND_ZERO; case GL_ONE: return Gfx::BLEND_ONE; case GL_SRC_COLOR: return Gfx::BLEND_SRC_COLOR; case GL_ONE_MINUS_SRC_COLOR: return Gfx::BLEND_INV_SRC_COLOR; case GL_DST_COLOR: return Gfx::BLEND_DST_COLOR; case GL_ONE_MINUS_DST_COLOR: return Gfx::BLEND_INV_DST_COLOR; case GL_SRC_ALPHA: return Gfx::BLEND_SRC_ALPHA; case GL_ONE_MINUS_SRC_ALPHA: return Gfx::BLEND_INV_SRC_ALPHA; case GL_DST_ALPHA: return Gfx::BLEND_DST_ALPHA; case GL_ONE_MINUS_DST_ALPHA: return Gfx::BLEND_INV_DST_ALPHA; case GL_SRC_ALPHA_SATURATE: return Gfx::BLEND_SRC_ALPHA_SATURATE; default: assert(false); break; } return Gfx::BLEND_ZERO; } GLenum TranslateGfxBlendFunc(Gfx::BlendFunc func) { switch (func) { case Gfx::BLEND_ZERO: return GL_ZERO; case Gfx::BLEND_ONE: return GL_ONE; case Gfx::BLEND_SRC_COLOR: return GL_SRC_COLOR; case Gfx::BLEND_INV_SRC_COLOR: return GL_ONE_MINUS_SRC_COLOR; case Gfx::BLEND_DST_COLOR: return GL_DST_COLOR; case Gfx::BLEND_INV_DST_COLOR: return GL_ONE_MINUS_DST_COLOR; case Gfx::BLEND_SRC_ALPHA: return GL_SRC_ALPHA; case Gfx::BLEND_INV_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA; case Gfx::BLEND_DST_ALPHA: return GL_DST_ALPHA; case Gfx::BLEND_INV_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA; case Gfx::BLEND_SRC_ALPHA_SATURATE: return GL_SRC_ALPHA_SATURATE; default: assert(false); break; } return 0; } void Gfx::CGLDevice::SetBlendFunc(Gfx::BlendFunc srcBlend, Gfx::BlendFunc dstBlend) { glBlendFunc(TranslateGfxBlendFunc(srcBlend), TranslateGfxBlendFunc(dstBlend)); } void Gfx::CGLDevice::GetBlendFunc(Gfx::BlendFunc &srcBlend, Gfx::BlendFunc &dstBlend) { GLint srcFlag = 0; glGetIntegerv(GL_ALPHA_TEST_FUNC, &srcFlag); srcBlend = TranslateGLBlendFunc(static_cast(srcFlag)); GLint dstFlag = 0; glGetIntegerv(GL_ALPHA_TEST_FUNC, &dstFlag); dstBlend = TranslateGLBlendFunc(static_cast(dstFlag)); } void Gfx::CGLDevice::SetClearColor(const Gfx::Color &color) { glClearColor(color.r, color.g, color.b, color.a); } Gfx::Color Gfx::CGLDevice::GetClearColor() { GLfloat color[4] = { 0.0f }; glGetFloatv(GL_COLOR_CLEAR_VALUE, color); return Gfx::Color(color[0], color[1], color[2], color[3]); } void Gfx::CGLDevice::SetGlobalAmbient(const Gfx::Color &color) { glLightModelfv(GL_LIGHT_MODEL_AMBIENT, color.Array()); } Gfx::Color Gfx::CGLDevice::GetGlobalAmbient() { GLfloat color[4] = { 0.0f }; glGetFloatv(GL_LIGHT_MODEL_AMBIENT, color); return Gfx::Color(color[0], color[1], color[2], color[3]); } void Gfx::CGLDevice::SetFogParams(Gfx::FogMode mode, const Gfx::Color &color, float start, float end, float density) { if (mode == Gfx::FOG_LINEAR) glFogi(GL_FOG_MODE, GL_LINEAR); else if (mode == Gfx::FOG_EXP) glFogi(GL_FOG_MODE, GL_EXP); else if (mode == Gfx::FOG_EXP2) glFogi(GL_FOG_MODE, GL_EXP2); else assert(false); glFogf(GL_FOG_START, start); glFogf(GL_FOG_END, end); glFogf(GL_FOG_DENSITY, density); } void Gfx::CGLDevice::GetFogParams(Gfx::FogMode &mode, Gfx::Color &color, float &start, float &end, float &density) { GLint flag = 0; glGetIntegerv(GL_FOG_MODE, &flag); if (flag == GL_LINEAR) mode = Gfx::FOG_LINEAR; else if (flag == GL_EXP) mode = Gfx::FOG_EXP; else if (flag == GL_EXP2) mode = Gfx::FOG_EXP2; else assert(false); glGetFloatv(GL_FOG_START, static_cast(&start)); glGetFloatv(GL_FOG_END, static_cast(&end)); glGetFloatv(GL_FOG_DENSITY, static_cast(&density)); } void Gfx::CGLDevice::SetCullMode(Gfx::CullMode mode) { // Cull clockwise back faces, so front face is the opposite // (assuming GL_CULL_FACE is GL_BACK) if (mode == Gfx::CULL_CW ) glFrontFace(GL_CCW); else if (mode == Gfx::CULL_CCW) glFrontFace(GL_CW); else assert(false); } Gfx::CullMode Gfx::CGLDevice::GetCullMode() { GLint flag = 0; glGetIntegerv(GL_FRONT_FACE, &flag); if (flag == GL_CW) return Gfx::CULL_CCW; else if (flag == GL_CCW) return Gfx::CULL_CW; else assert(false); return Gfx::CULL_CW; } void Gfx::CGLDevice::SetShadeModel(Gfx::ShadeModel model) { if (model == Gfx::SHADE_FLAT) glShadeModel(GL_FLAT); else if (model == Gfx::SHADE_SMOOTH) glShadeModel(GL_SMOOTH); else assert(false); } Gfx::ShadeModel Gfx::CGLDevice::GetShadeModel() { GLint flag = 0; glGetIntegerv(GL_SHADE_MODEL, &flag); if (flag == GL_FLAT) return Gfx::SHADE_FLAT; else if (flag == GL_SMOOTH) return Gfx::SHADE_SMOOTH; else assert(false); return Gfx::SHADE_FLAT; } void Gfx::CGLDevice::SetFillMode(Gfx::FillMode mode) { if (mode == Gfx::FILL_POINT) glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); else if (mode == Gfx::FILL_LINES) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); else if (mode == Gfx::FILL_FILL) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); else assert(false); } Gfx::FillMode Gfx::CGLDevice::GetFillMode() { GLint flag = 0; glGetIntegerv(GL_POLYGON_MODE, &flag); if (flag == GL_POINT) return Gfx::FILL_POINT; else if (flag == GL_LINE) return Gfx::FILL_LINES; else if (flag == GL_FILL) return Gfx::FILL_FILL; else assert(false); return Gfx::FILL_POINT; }