489 lines
16 KiB
C++
489 lines
16 KiB
C++
/* ResidualVM - A 3D game interpreter
|
|
*
|
|
* ResidualVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the AUTHORS
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "engines/stark/gfx/openglactor.h"
|
|
|
|
#include "engines/stark/model/model.h"
|
|
#include "engines/stark/model/animhandler.h"
|
|
#include "engines/stark/scene.h"
|
|
#include "engines/stark/services/services.h"
|
|
#include "engines/stark/services/settings.h"
|
|
#include "engines/stark/gfx/texture.h"
|
|
|
|
#include "math/vector2d.h"
|
|
|
|
#if defined(USE_OPENGL_GAME)
|
|
|
|
namespace Stark {
|
|
namespace Gfx {
|
|
|
|
OpenGLActorRenderer::OpenGLActorRenderer(OpenGLDriver *gfx) :
|
|
VisualActor(),
|
|
_gfx(gfx),
|
|
_faceVBO(nullptr) {
|
|
}
|
|
|
|
OpenGLActorRenderer::~OpenGLActorRenderer() {
|
|
clearVertices();
|
|
}
|
|
|
|
void OpenGLActorRenderer::render(const Math::Vector3d &position, float direction, const LightEntryArray &lights) {
|
|
if (_modelIsDirty) {
|
|
clearVertices();
|
|
uploadVertices();
|
|
_modelIsDirty = false;
|
|
}
|
|
|
|
// TODO: Move updates outside of the rendering code
|
|
_animHandler->animate(_time);
|
|
_model->updateBoundingBox();
|
|
|
|
bool drawShadow = false;
|
|
if (_castsShadow &&
|
|
StarkScene->shouldRenderShadows() &&
|
|
StarkSettings->getBoolSetting(Settings::kShadow)) {
|
|
drawShadow = true;
|
|
}
|
|
Math::Vector3d lightDirection;
|
|
|
|
_gfx->set3DMode();
|
|
if (!_gfx->computeLightsEnabled())
|
|
_gfx->setupLights(lights);
|
|
|
|
Math::Matrix4 model = getModelMatrix(position, direction);
|
|
Math::Matrix4 view = StarkScene->getViewMatrix();
|
|
Math::Matrix4 projection = StarkScene->getProjectionMatrix();
|
|
|
|
Math::Matrix4 modelViewMatrix = view * model;
|
|
modelViewMatrix.transpose(); // OpenGL expects matrices transposed
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadMatrixf(modelViewMatrix.getData());
|
|
|
|
Math::Matrix4 projectionMatrix = projection;
|
|
projectionMatrix.transpose(); // OpenGL expects matrices transposed
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadMatrixf(projectionMatrix.getData());
|
|
|
|
Math::Matrix4 normalMatrix;
|
|
if (_gfx->computeLightsEnabled()) {
|
|
projectionMatrix.transpose();
|
|
modelViewMatrix.transpose();
|
|
|
|
normalMatrix = modelViewMatrix;
|
|
normalMatrix.invertAffineOrthonormal();
|
|
}
|
|
|
|
Math::Matrix4 mvp;
|
|
if (drawShadow) {
|
|
mvp = view * model;
|
|
mvp.transpose();
|
|
Math::Matrix4 modelInverse = model;
|
|
modelInverse.inverse();
|
|
lightDirection = getShadowLightDirection(lights, position, modelInverse.getRotation());
|
|
}
|
|
|
|
Common::Array<Face *> faces = _model->getFaces();
|
|
Common::Array<Material *> mats = _model->getMaterials();
|
|
const Common::Array<BoneNode *> &bones = _model->getBones();
|
|
|
|
if (!_gfx->computeLightsEnabled()) {
|
|
glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
|
|
glEnable(GL_COLOR_MATERIAL);
|
|
}
|
|
|
|
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
|
|
const Material *material = mats[(*face)->materialId];
|
|
Math::Vector3d color;
|
|
const Gfx::Texture *tex = resolveTexture(material);
|
|
if (tex) {
|
|
tex->bind();
|
|
glEnable(GL_TEXTURE_2D);
|
|
} else {
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
auto vertexIndices = _faceEBO[*face];
|
|
auto numVertexIndices = (*face)->vertexIndices.size();
|
|
for (uint32 i = 0; i < numVertexIndices; i++) {
|
|
if (tex) {
|
|
if (_gfx->computeLightsEnabled())
|
|
color = Math::Vector3d(1.0f, 1.0f, 1.0f);
|
|
else
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
} else {
|
|
if (_gfx->computeLightsEnabled())
|
|
color = Math::Vector3d(material->r, material->g, material->b);
|
|
else
|
|
glColor3f(material->r, material->g, material->b);
|
|
}
|
|
uint32 index = vertexIndices[i];
|
|
auto vertex = _faceVBO[index];
|
|
uint32 bone1 = vertex.bone1;
|
|
uint32 bone2 = vertex.bone2;
|
|
Math::Vector3d position1 = Math::Vector3d(vertex.pos1x, vertex.pos1y, vertex.pos1z);
|
|
Math::Vector3d position2 = Math::Vector3d(vertex.pos2x, vertex.pos2y, vertex.pos2z);
|
|
Math::Vector3d bone1Position = Math::Vector3d(bones[bone1]->_animPos.x(),
|
|
bones[bone1]->_animPos.y(),
|
|
bones[bone1]->_animPos.z());
|
|
Math::Vector3d bone2Position = Math::Vector3d(bones[bone2]->_animPos.x(),
|
|
bones[bone2]->_animPos.y(),
|
|
bones[bone2]->_animPos.z());
|
|
Math::Quaternion bone1Rotation = Math::Quaternion(bones[bone1]->_animRot.x(),
|
|
bones[bone1]->_animRot.y(),
|
|
bones[bone1]->_animRot.z(),
|
|
bones[bone1]->_animRot.w());
|
|
Math::Quaternion bone2Rotation = Math::Quaternion(bones[bone2]->_animRot.x(),
|
|
bones[bone2]->_animRot.y(),
|
|
bones[bone2]->_animRot.z(),
|
|
bones[bone2]->_animRot.w());
|
|
float boneWeight = vertex.boneWeight;
|
|
Math::Vector3d normal = Math::Vector3d(vertex.normalx, vertex.normaly, vertex.normalz);
|
|
|
|
// Compute the vertex position in eye-space
|
|
bone1Rotation.transform(position1);
|
|
position1 += bone1Position;
|
|
bone2Rotation.transform(position2);
|
|
position2 += bone2Position;
|
|
Math::Vector3d modelPosition = Math::Vector3d::interpolate(position2, position1, boneWeight);
|
|
vertex.x = modelPosition.x();
|
|
vertex.y = modelPosition.y();
|
|
vertex.z = modelPosition.z();
|
|
Math::Vector4d modelEyePosition;
|
|
if (_gfx->computeLightsEnabled()) {
|
|
modelEyePosition = modelViewMatrix * Math::Vector4d(modelPosition.x(),
|
|
modelPosition.y(),
|
|
modelPosition.z(),
|
|
1.0);
|
|
}
|
|
// Compute the vertex normal in eye-space
|
|
Math::Vector3d n1 = normal;
|
|
bone1Rotation.transform(n1);
|
|
Math::Vector3d n2 = normal;
|
|
bone2Rotation.transform(n2);
|
|
Math::Vector3d modelNormal = Math::Vector3d(Math::Vector3d::interpolate(n2, n1, boneWeight)).getNormalized();
|
|
vertex.nx = modelNormal.x();
|
|
vertex.ny = modelNormal.y();
|
|
vertex.nz = modelNormal.z();
|
|
Math::Vector3d modelEyeNormal;
|
|
if (_gfx->computeLightsEnabled()) {
|
|
modelEyeNormal = normalMatrix.getRotation() * modelNormal;
|
|
modelEyeNormal.normalize();
|
|
}
|
|
|
|
if (drawShadow) {
|
|
Math::Vector3d shadowPosition = modelPosition + lightDirection * (-modelPosition.y() / lightDirection.y());
|
|
vertex.sx = shadowPosition.x();
|
|
vertex.sy = 0.0f;
|
|
vertex.sz = shadowPosition.z();
|
|
}
|
|
|
|
if (_gfx->computeLightsEnabled()) {
|
|
static const uint maxLights = 10;
|
|
|
|
assert(lights.size() >= 1);
|
|
assert(lights.size() <= maxLights);
|
|
|
|
const LightEntry *ambient = lights[0];
|
|
assert(ambient->type == LightEntry::kAmbient); // The first light must be the ambient light
|
|
|
|
Math::Vector3d lightColor = ambient->color;
|
|
|
|
for (uint li = 0; li < lights.size() - 1; li++) {
|
|
const LightEntry *l = lights[li + 1];
|
|
|
|
switch (l->type) {
|
|
case LightEntry::kPoint: {
|
|
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
|
|
|
|
float dist = vertexToLight.length();
|
|
vertexToLight.normalize();
|
|
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
|
|
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, vertexToLight));
|
|
lightColor += l->color * attn * incidence;
|
|
break;
|
|
}
|
|
case LightEntry::kDirectional: {
|
|
float incidence = MAX(0.0f, Math::Vector3d::dotProduct(modelEyeNormal, -l->eyeDirection));
|
|
lightColor += (l->color * incidence);
|
|
break;
|
|
}
|
|
case LightEntry::kSpot: {
|
|
Math::Vector3d vertexToLight = l->eyePosition.getXYZ() - modelEyePosition.getXYZ();
|
|
|
|
float dist = vertexToLight.length();
|
|
float attn = CLIP((l->falloffFar - dist) / MAX(0.001f, l->falloffFar - l->falloffNear), 0.0f, 1.0f);
|
|
|
|
vertexToLight.normalize();
|
|
float incidence = MAX(0.0f, modelEyeNormal.dotProduct(vertexToLight));
|
|
|
|
float cosAngle = MAX(0.0f, vertexToLight.dotProduct(-l->eyeDirection));
|
|
float cone = CLIP((cosAngle - l->innerConeAngle.getCosine()) / MAX(0.001f, l->outerConeAngle.getCosine() - l->innerConeAngle.getCosine()), 0.0f, 1.0f);
|
|
|
|
lightColor += l->color * attn * incidence * cone;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
lightColor.x() = CLIP(lightColor.x(), 0.0f, 1.0f);
|
|
lightColor.y() = CLIP(lightColor.y(), 0.0f, 1.0f);
|
|
lightColor.z() = CLIP(lightColor.z(), 0.0f, 1.0f);
|
|
color = color * lightColor;
|
|
vertex.r = color.x();
|
|
vertex.g = color.y();
|
|
vertex.b = color.z();
|
|
}
|
|
|
|
_faceVBO[index] = vertex;
|
|
}
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
if (_gfx->computeLightsEnabled())
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
if (tex)
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glEnableClientState(GL_NORMAL_ARRAY);
|
|
|
|
glVertexPointer(3, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].x);
|
|
if (tex)
|
|
glTexCoordPointer(2, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].texS);
|
|
glNormalPointer(GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].nx);
|
|
if (_gfx->computeLightsEnabled())
|
|
glColorPointer(3, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].r);
|
|
|
|
glDrawElements(GL_TRIANGLES, numVertexIndices, GL_UNSIGNED_INT, vertexIndices);
|
|
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
if (_gfx->computeLightsEnabled())
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glDisableClientState(GL_NORMAL_ARRAY);
|
|
}
|
|
if (!_gfx->computeLightsEnabled())
|
|
glDisable(GL_COLOR_MATERIAL);
|
|
|
|
|
|
if (drawShadow) {
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_STENCIL_TEST);
|
|
glDisable(GL_TEXTURE_2D);
|
|
if (!_gfx->computeLightsEnabled())
|
|
glDisable(GL_LIGHTING);
|
|
|
|
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
|
|
|
|
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
|
|
glVertexPointer(3, GL_FLOAT, sizeof(ActorVertex), &_faceVBO[0].sx);
|
|
|
|
glDrawElements(GL_TRIANGLES, (*face)->vertexIndices.size(), GL_UNSIGNED_INT, _faceEBO[*face]);
|
|
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
}
|
|
|
|
if (!_gfx->computeLightsEnabled())
|
|
glEnable(GL_LIGHTING);
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_STENCIL_TEST);
|
|
}
|
|
}
|
|
|
|
void OpenGLActorRenderer::clearVertices() {
|
|
delete[] _faceVBO;
|
|
_faceVBO = nullptr;
|
|
|
|
for (FaceBufferMap::iterator it = _faceEBO.begin(); it != _faceEBO.end(); ++it) {
|
|
delete[] it->_value;
|
|
}
|
|
|
|
_faceEBO.clear();
|
|
}
|
|
|
|
void OpenGLActorRenderer::uploadVertices() {
|
|
_faceVBO = createModelVBO(_model);
|
|
|
|
Common::Array<Face *> faces = _model->getFaces();
|
|
for (Common::Array<Face *>::const_iterator face = faces.begin(); face != faces.end(); ++face) {
|
|
_faceEBO[*face] = createFaceEBO(*face);
|
|
}
|
|
}
|
|
|
|
ActorVertex *OpenGLActorRenderer::createModelVBO(const Model *model) {
|
|
const Common::Array<VertNode *> &modelVertices = model->getVertices();
|
|
|
|
auto vertices = new ActorVertex[modelVertices.size()];
|
|
// Build a vertex array
|
|
int i = 0;
|
|
for (Common::Array<VertNode *>::const_iterator tri = modelVertices.begin(); tri != modelVertices.end(); ++tri, i++) {
|
|
vertices[i].pos1x = (*tri)->_pos1.x();
|
|
vertices[i].pos1y = (*tri)->_pos1.y();
|
|
vertices[i].pos1z = (*tri)->_pos1.z();
|
|
vertices[i].pos2x = (*tri)->_pos2.x();
|
|
vertices[i].pos2y = (*tri)->_pos2.y();
|
|
vertices[i].pos2z = (*tri)->_pos2.z();
|
|
vertices[i].bone1 = (*tri)->_bone1;
|
|
vertices[i].bone2 = (*tri)->_bone2;
|
|
vertices[i].boneWeight = (*tri)->_boneWeight;
|
|
vertices[i].normalx = (*tri)->_normal.x();
|
|
vertices[i].normaly = (*tri)->_normal.y();
|
|
vertices[i].normalz = (*tri)->_normal.z();
|
|
vertices[i].texS = -(*tri)->_texS;
|
|
vertices[i].texT = (*tri)->_texT;
|
|
}
|
|
|
|
return vertices;
|
|
}
|
|
|
|
uint32 *OpenGLActorRenderer::createFaceEBO(const Face *face) {
|
|
auto indices = new uint32[face->vertexIndices.size()];
|
|
for (uint32 index = 0; index < face->vertexIndices.size(); index++) {
|
|
indices[index] = face->vertexIndices[index];
|
|
}
|
|
|
|
return indices;
|
|
}
|
|
|
|
Math::Vector3d OpenGLActorRenderer::getShadowLightDirection(const LightEntryArray &lights, const Math::Vector3d &actorPosition,
|
|
Math::Matrix3 worldToModelRot) {
|
|
Math::Vector3d sumDirection;
|
|
bool hasLight = false;
|
|
|
|
// Compute the contribution from each lights
|
|
// The ambient light is skipped intentionally
|
|
for (uint i = 1; i < lights.size(); ++i) {
|
|
LightEntry *light = lights[i];
|
|
bool contributes = false;
|
|
|
|
Math::Vector3d lightDirection;
|
|
switch (light->type) {
|
|
case LightEntry::kPoint:
|
|
contributes = getPointLightContribution(light, actorPosition, lightDirection);
|
|
break;
|
|
case LightEntry::kDirectional:
|
|
contributes = getDirectionalLightContribution(light, lightDirection);
|
|
break;
|
|
case LightEntry::kSpot:
|
|
contributes = getSpotLightContribution(light, actorPosition, lightDirection);
|
|
break;
|
|
case LightEntry::kAmbient:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (contributes) {
|
|
sumDirection += lightDirection;
|
|
hasLight = true;
|
|
}
|
|
}
|
|
|
|
if (hasLight) {
|
|
// Clip the horizontal length
|
|
Math::Vector2d horizontalProjection(sumDirection.x(), sumDirection.y());
|
|
float shadowLength = MIN(horizontalProjection.getMagnitude(), StarkScene->getMaxShadowLength());
|
|
|
|
horizontalProjection.normalize();
|
|
horizontalProjection *= shadowLength;
|
|
|
|
sumDirection.x() = horizontalProjection.getX();
|
|
sumDirection.y() = horizontalProjection.getY();
|
|
sumDirection.z() = -1;
|
|
} else {
|
|
// Cast from above by default
|
|
sumDirection.x() = 0;
|
|
sumDirection.y() = 0;
|
|
sumDirection.z() = -1;
|
|
}
|
|
|
|
//Transform the direction to the model space and pass to the shader
|
|
return worldToModelRot * sumDirection;
|
|
}
|
|
|
|
bool OpenGLActorRenderer::getPointLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
|
|
Math::Vector3d &direction, float weight) {
|
|
float distance = light->position.getDistanceTo(actorPosition);
|
|
|
|
if (distance > light->falloffFar) {
|
|
return false;
|
|
}
|
|
|
|
float factor;
|
|
if (distance > light->falloffNear) {
|
|
if (light->falloffFar - light->falloffNear > 1) {
|
|
factor = 1 - (distance - light->falloffNear) / (light->falloffFar - light->falloffNear);
|
|
} else {
|
|
factor = 0;
|
|
}
|
|
} else {
|
|
factor = 1;
|
|
}
|
|
|
|
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
|
|
|
|
if (factor <= 0 || brightness <= 0) {
|
|
return false;
|
|
}
|
|
|
|
direction = actorPosition - light->position;
|
|
direction.normalize();
|
|
direction *= factor * brightness * weight;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OpenGLActorRenderer::getDirectionalLightContribution(LightEntry *light, Math::Vector3d &direction) {
|
|
float brightness = (light->color.x() + light->color.y() + light->color.z()) / 3.0f;
|
|
|
|
if (brightness <= 0) {
|
|
return false;
|
|
}
|
|
|
|
direction = light->direction;
|
|
direction.normalize();
|
|
direction *= brightness;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OpenGLActorRenderer::getSpotLightContribution(LightEntry *light, const Math::Vector3d &actorPosition,
|
|
Math::Vector3d &direction) {
|
|
Math::Vector3d lightToActor = actorPosition - light->position;
|
|
lightToActor.normalize();
|
|
|
|
float cosAngle = MAX(0.0f, lightToActor.dotProduct(light->direction));
|
|
float cone = (cosAngle - light->innerConeAngle.getCosine()) /
|
|
MAX(0.001f, light->outerConeAngle.getCosine() - light->innerConeAngle.getCosine());
|
|
cone = CLIP(cone, 0.0f, 1.0f);
|
|
|
|
if (cone <= 0) {
|
|
return false;
|
|
}
|
|
|
|
return getPointLightContribution(light, actorPosition, direction, cone);
|
|
}
|
|
|
|
} // End of namespace Gfx
|
|
} // End of namespace Stark
|
|
|
|
#endif // defined(USE_OPENGL_GAME)
|