irrlicht/source/Irrlicht/CQuake3ShaderSceneNode.cpp

796 lines
19 KiB
C++

// Copyright (C) 2002-2009 Nikolaus Gebhardt / Thomas Alten
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_BSP_LOADER_
#include "CQuake3ShaderSceneNode.h"
#include "ISceneManager.h"
#include "IVideoDriver.h"
#include "ICameraSceneNode.h"
#include "SViewFrustum.h"
#include "IMeshManipulator.h"
#include "SMesh.h"
#include "IMaterialRenderer.h"
namespace irr
{
namespace scene
{
CQuake3ShaderSceneNode::CQuake3ShaderSceneNode(
scene::ISceneNode* parent, scene::ISceneManager* mgr,s32 id,
io::IFileSystem *fileSystem, scene::IMeshBuffer *buffer,
const quake3::SShader * shader)
: scene::ISceneNode(parent, mgr, id), MeshBuffer(0), Original(0),
Shader(shader), TimeAbs(0.f)
{
#ifdef _DEBUG
core::stringc dName = "CQuake3ShaderSceneNode ";
dName += Shader->name;
setDebugName( dName.c_str() );
#endif
MeshBuffer = new SMeshBuffer();
Original = new SMeshBufferLightMap();
// name the Scene Node
this->Name = Shader->name;
// clone meshbuffer to modifiable buffer
cloneBuffer( static_cast< scene::SMeshBufferLightMap *>( buffer ) );
// load all Textures in all stages
loadTextures( fileSystem );
setAutomaticCulling( scene::EAC_BOX );
}
CQuake3ShaderSceneNode::~CQuake3ShaderSceneNode()
{
if (MeshBuffer)
MeshBuffer->drop();
if (Original)
Original->drop();
}
/*
create single copies
*/
void CQuake3ShaderSceneNode::cloneBuffer( scene::SMeshBufferLightMap * buffer )
{
Original->Material = buffer->Material;
MeshBuffer->Material = buffer->Material;
Original->Indices = buffer->Indices;
MeshBuffer->Indices = buffer->Indices;
const u32 vsize = buffer->Vertices.size();
Original->Vertices.reallocate( vsize );
MeshBuffer->Vertices.reallocate( vsize );
for ( u32 i = 0; i!= vsize; ++i )
{
const video::S3DVertex2TCoords& src = buffer->Vertices[i];
// Original has same Vertex Format
Original->Vertices.push_back(src);
// we have a different vertex format
MeshBuffer->Vertices.push_back(src);
MeshBuffer->Vertices.getLast().Color=0xFFFFFFFF;
}
MeshBuffer->recalculateBoundingBox();
#if 1
// move the (temp) Mesh
{
// original bounding box
const core::aabbox3df& b = MeshBuffer->getBoundingBox();
// set Scene Node Position
setPosition( b.getCenter() );
core::matrix4 m;
m.setTranslation( -b.getCenter() );
SceneManager->getMeshManipulator()->transform( Original, m );
SceneManager->getMeshManipulator()->transform( MeshBuffer, m );
MeshBuffer->recalculateBoundingBox();
}
#endif
// used for sorting
MeshBuffer->Material.setTexture(0, (video::ITexture*) Shader);
}
/*
load the textures for all stages
*/
void CQuake3ShaderSceneNode::loadTextures( io::IFileSystem * fileSystem )
{
const quake3::SVarGroup *group;
u32 i;
// clear all stages and prefill empty
Q3Texture.clear();
for ( i = 0; i != Shader->VarGroup->VariableGroup.size(); ++i )
{
Q3Texture.push_back( SQ3Texture() );
}
u32 pos;
// get texture map
for ( i = 0; i < Shader->VarGroup->VariableGroup.size(); ++i )
{
group = Shader->getGroup( i );
const core::stringc &mapname = group->get( "map" );
if ( 0 == mapname.size() )
continue;
// our lightmap is passed in material.Texture[2]
if ( mapname == "$lightmap" )
{
Q3Texture [i].Texture.push_back( Original->getMaterial().getTexture(1) );
}
else
{
pos = 0;
quake3::getTextures( Q3Texture [i].Texture, mapname, pos, fileSystem, SceneManager->getVideoDriver() );
}
}
// get anim map
for ( i = 0; i < Shader->VarGroup->VariableGroup.size(); ++i )
{
if ( Q3Texture [i].Texture.size() )
continue;
group = Shader->getGroup( i );
const core::stringc &animmap = group->get( "animmap" );
if ( 0 == animmap.size() )
continue;
// first parameter is frequency
pos = 0;
Q3Texture [i].TextureFrequency = core::max_( 0.0001f, quake3::getAsFloat( animmap, pos ) );
quake3::getTextures( Q3Texture [i].Texture, animmap, pos,fileSystem, SceneManager->getVideoDriver() );
}
// get clamp map
for ( i = 0; i < Shader->VarGroup->VariableGroup.size(); ++i )
{
if ( Q3Texture [i].Texture.size() )
continue;
group = Shader->getGroup( i );
const core::stringc &clampmap = group->get( "clampmap" );
if ( 0 == clampmap.size() )
continue;
Q3Texture [i].TextureAddressMode = video::ETC_CLAMP;
pos = 0;
quake3::getTextures( Q3Texture [i].Texture, clampmap, pos,fileSystem, SceneManager->getVideoDriver() );
}
}
/*
Register each texture stage, if first is visible
*/
void CQuake3ShaderSceneNode::OnRegisterSceneNode()
{
if ( isVisible() )
{
SceneManager->registerNodeForRendering(this, isTransparent() ? ESNRP_TRANSPARENT: ESNRP_SOLID );
}
ISceneNode::OnRegisterSceneNode();
}
/*
is this a transparent node ?
*/
bool CQuake3ShaderSceneNode::isTransparent() const
{
bool ret = false;
// generic stage
const quake3::SVarGroup *group;
group = Shader->getGroup( 1 );
/*
if ( group->isDefined( "surfaceparm", "nonsolid" ) )
{
ret = true;
}
*/
if ( group->isDefined( "surfaceparm", "trans" ) )
{
ret = true;
}
else
{
for ( u32 stage = 0; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
{
if ( 0 == Q3Texture [ stage].Texture.size() )
continue;
group = Shader->getGroup( stage );
quake3::SBlendFunc blendfunc;
quake3::getBlendFunc( group->get( "blendfunc" ), blendfunc );
quake3::getBlendFunc( group->get( "alphafunc" ), blendfunc );
ret = blendfunc.isTransparent;
break;
}
}
return ret;
}
/*
render in multipass technique
*/
void CQuake3ShaderSceneNode::render()
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
bool isTransparentPass =
SceneManager->getSceneNodeRenderPass() == scene::ESNRP_TRANSPARENT;
video::SMaterial material;
const quake3::SVarGroup *group;
material.Lighting = false;
material.setTexture(1, 0);
material.NormalizeNormals = false;
// generic stage
group = Shader->getGroup( 1 );
material.BackfaceCulling = quake3::isDisabled( group->get( "cull" ) );
// u32 zEnable = group->getIndex( "polygonoffset" ) >= 0;
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation );
u32 drawCount = 0;
for ( u32 stage = 0; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
//for ( u32 stage = 2; stage < 3; ++stage )
{
SQ3Texture &q = Q3Texture[stage];
// advance current stage
core::matrix4 texture;
animate( stage, texture );
// stage 1 finished, no drawing stage ( vertex transform only )
if ( 0 == q.Texture.size() )
continue;
// current stage
group = Shader->getGroup( stage );
material.setTexture(0, q.Texture[ q.TextureIndex ]);
material.ZBuffer = quake3::getDepthFunction( group->get( "depthfunc" ) );
if ( group->getIndex( "depthwrite" ) >= 0 )
{
material.ZWriteEnable = true;
}
else
{
material.ZWriteEnable = drawCount ? false: !isTransparentPass;
}
//resolve quake3 blendfunction to irrlicht Material Type
quake3::SBlendFunc blendfunc;
quake3::getBlendFunc( group->get( "blendfunc" ), blendfunc );
quake3::getBlendFunc( group->get( "alphafunc" ), blendfunc );
material.MaterialType = blendfunc.type;
material.MaterialTypeParam = blendfunc.param;
material.TextureLayer[0].TextureWrap = q.TextureAddressMode;
material.setTextureMatrix( 0, texture );
driver->setMaterial( material );
driver->drawMeshBuffer( MeshBuffer );
drawCount += 1;
}
if ( DebugDataVisible & scene::EDS_BBOX )
{
video::SMaterial deb_m;
deb_m.Lighting = false;
driver->setMaterial(deb_m);
driver->draw3DBox( getBoundingBox(), video::SColor(0,255,255,255));
}
}
/*!
*/
void CQuake3ShaderSceneNode::vertextransform_wave( f32 dt, quake3::SModifierFunction &function )
{
function.wave = core::reciprocal( function.wave );
const f32 phase = function.phase;
const u32 vsize = MeshBuffer->Vertices.size();
for ( u32 i = 0; i != vsize; ++i )
{
const video::S3DVertex2TCoords &src = Original->Vertices[i];
video::S3DVertex &dst = MeshBuffer->Vertices[i];
const f32 wavephase = (src.Pos.X + src.Pos.Y + src.Pos.Z) * function.wave;
function.phase = phase + wavephase;
const f32 f = function.evaluate( dt );
dst.Pos.X = src.Pos.X + f * src.Normal.X;
dst.Pos.Y = src.Pos.Y + f * src.Normal.Y;
dst.Pos.Z = src.Pos.Z + f * src.Normal.Z;
}
}
/*!
*/
void CQuake3ShaderSceneNode::vertextransform_bulge( f32 dt, quake3::SModifierFunction &function )
{
function.func = 0;
function.wave = core::reciprocal( function.bulgewidth );
dt *= function.bulgespeed * 0.1f;
const f32 phase = function.phase;
const u32 vsize = MeshBuffer->Vertices.size();
for ( u32 i = 0; i != vsize; ++i )
{
const video::S3DVertex2TCoords &src = Original->Vertices[i];
video::S3DVertex &dst = MeshBuffer->Vertices[i];
const f32 wavephase = (Original->Vertices[i].TCoords.X ) * function.wave;
function.phase = phase + wavephase;
const f32 f = function.evaluate( dt );
dst.Pos.X = src.Pos.X + f * src.Normal.X;
dst.Pos.Y = src.Pos.Y + f * src.Normal.Y;
dst.Pos.Z = src.Pos.Z + f * src.Normal.Z;
}
}
/*!
*/
void CQuake3ShaderSceneNode::vertextransform_autosprite( f32 dt, quake3::SModifierFunction &function )
{
const core::matrix4 &m = SceneManager->getActiveCamera()->getViewFrustum()->Matrices [ video::ETS_VIEW ];
const core::vector3df view( -m[2], -m[6] , -m[10] );
const u32 vsize = MeshBuffer->Vertices.size();
core::aabbox3df box;
u32 g;
for ( u32 i = 0; i < vsize; i += 4 )
{
// in pairs of 4
box.reset( Original->Vertices[i].Pos );
for ( g = 1; g != 4; ++g )
{
box.addInternalPoint( Original->Vertices[i + g].Pos );
}
core::vector3df c = box.getCenter();
f32 sh = 0.5f * ( box.MaxEdge.Z - box.MinEdge.Z );
f32 sv = 0.5f * ( box.MaxEdge.Y - box.MinEdge.Y );
const core::vector3df h( m[0] * sh, m[4] * sh, m[8] * sh );
const core::vector3df v( m[1] * sv, m[5] * sv, m[9] * sv );
MeshBuffer->Vertices[ i + 0 ].Pos = c + h + v;
MeshBuffer->Vertices[ i + 1 ].Pos = c - h - v;
MeshBuffer->Vertices[ i + 2 ].Pos = c + h - v;
MeshBuffer->Vertices[ i + 3 ].Pos = c - h + v;
MeshBuffer->Vertices[ i + 0 ].Normal = view;
MeshBuffer->Vertices[ i + 1 ].Normal = view;
MeshBuffer->Vertices[ i + 2 ].Normal = view;
MeshBuffer->Vertices[ i + 3 ].Normal = view;
}
}
/*
Generate Vertex Color
*/
void CQuake3ShaderSceneNode::vertextransform_rgbgen( f32 dt, quake3::SModifierFunction &function )
{
u32 i;
const u32 vsize = MeshBuffer->Vertices.size();
switch ( function.masterfunc1 )
{
case 6:
//identity
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color = 0xFFFFFFFF;
break;
case 7:
// vertex
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color = Original->Vertices[i].Color;
break;
case 5:
{
// wave
f32 f = function.evaluate( dt ) * 255.f;
s32 value = core::clamp( core::floor32(f), 0, 255 );
value |= value << 8;
value |= value << 16;
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color = value;
} break;
}
}
/*
Generate Texture Coordinates
*/
void CQuake3ShaderSceneNode::vertextransform_tcgen( f32 dt, quake3::SModifierFunction &function )
{
u32 i;
const u32 vsize = MeshBuffer->Vertices.size();
switch ( function.tcgen )
{
case 12:
//tcgen turb
{
function.wave = core::reciprocal( function.wave );
const f32 phase = function.phase;
for ( i = 0; i != vsize; ++i )
{
const video::S3DVertex2TCoords &src = Original->Vertices[i];
video::S3DVertex &dst = MeshBuffer->Vertices[i];
const f32 wavephase = (src.Pos.X + src.Pos.Y + src.Pos.Z) * function.wave;
function.phase = phase + wavephase;
const f32 f = function.evaluate( dt );
dst.TCoords.X = src.TCoords.X + f * src.Normal.X;
dst.TCoords.Y = src.TCoords.Y + f * src.Normal.Y;
}
}
break;
case 8:
// tcgen texture
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords;
break;
case 9:
// tcgen lightmap
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords2;
break;
case 10:
{
// tcgen environment ( D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR )
// using eye linear, sphere map may be cooler;-)
// modelmatrix is identity
const core::matrix4 &view = SceneManager->getActiveCamera()->getViewFrustum()->Matrices [ video::ETS_VIEW ];
const core::matrix4 &viewinverse = SceneManager->getActiveCamera()->getViewFrustum()->Matrices [ SViewFrustum::ETS_VIEW_MODEL_INVERSE_3 ];
// eyePlane
core::vector3df eyePlaneS;
core::vector3df eyePlaneT;
viewinverse.transformVect( eyePlaneS, core::vector3df(1.f, 0.f, 0.f) );
viewinverse.transformVect( eyePlaneT, core::vector3df(0.f, 1.f, 0.f) );
eyePlaneS.normalize();
eyePlaneT.normalize();
core::vector3df v;
for ( i = 0; i != vsize; ++i )
{
// vertex in eye space
view.transformVect( v, Original->Vertices[i].Pos );
v.normalize();
MeshBuffer->Vertices[i].TCoords.X = (1.f + eyePlaneS.dotProduct(v) ) * 0.5f;
MeshBuffer->Vertices[i].TCoords.Y = 1.f - ( (1.f + eyePlaneT.dotProduct(v) ) * 0.5f );
}
} break;
}
}
/*
Transform Texture Coordinates
*/
void CQuake3ShaderSceneNode::transformtex( const core::matrix4 &m, const u32 addressMode )
{
u32 i;
const u32 vsize = MeshBuffer->Vertices.size();
f32 tx1;
f32 ty1;
if ( addressMode )
{
for ( i = 0; i != vsize; ++i )
{
core::vector2df &tx = MeshBuffer->Vertices[i].TCoords;
tx1 = m[0] * tx.X + m[4] * tx.Y + m[8];
ty1 = m[1] * tx.X + m[5] * tx.Y + m[9];
tx.X = tx1;
tx.Y = ty1;
}
}
else
{
for ( i = 0; i != vsize; ++i )
{
core::vector2df &tx = MeshBuffer->Vertices[i].TCoords;
tx1 = m[0] * tx.X + m[4] * tx.Y + m[8];
ty1 = m[1] * tx.X + m[5] * tx.Y + m[9];
tx.X = tx1 <= 0.f ? 0.f : tx1 >= 1.f ? 1.f : tx1;
tx.Y = ty1 <= 0.f ? 0.f : ty1 >= 1.f ? 1.f : ty1;
//tx.X = core::clamp( tx1, 0.f, 1.f );
//tx.Y = core::clamp( ty1, 0.f, 1.f );
}
}
}
/*
Texture & Vertex Transform Animator
Return a Texture Transformation for this stage
Vertex transformation are called if found
*/
void CQuake3ShaderSceneNode::animate( u32 stage,core::matrix4 &texture )
{
const quake3::SVarGroup *group = Shader->getGroup( stage );
// select current texture
if ( Q3Texture [ stage ].TextureFrequency != 0.f )
{
s32 v = core::floor32( TimeAbs * Q3Texture[stage].TextureFrequency );
Q3Texture [ stage ].TextureIndex = v % Q3Texture[stage].Texture.size();
}
core::matrix4 m2;
quake3::SModifierFunction function;
f32 f0;
f32 f1;
// walk group for all modifiers
for ( u32 g = 0; g != group->Variable.size(); ++g )
{
const quake3::SVariable &v = group->Variable[g];
// get the modifier
static const c8 * modifierList[] =
{
"tcmod","deformvertexes","rgbgen","tcgen","map"
};
u32 pos = 0;
function.masterfunc0 = quake3::isEqual( v.name, pos, modifierList, 5 );
if ( -2 == function.masterfunc0 )
continue;
switch ( function.masterfunc0 )
{
//tcmod
case 0:
m2.makeIdentity();
break;
}
// get the modifier function
static const c8 * funclist[] =
{
"scroll","scale","rotate","stretch","turb",
"wave","identity","vertex",
"texture","lightmap","environment","$lightmap",
"bulge","autosprite","autosprite2"
};
pos = 0;
function.masterfunc1 = quake3::isEqual( v.content, pos, funclist, 14 );
switch ( function.masterfunc1 )
{
case 0:
// scroll
f0 = quake3::getAsFloat( v.content, pos ) * TimeAbs;
f1 = quake3::getAsFloat( v.content, pos ) * TimeAbs;
m2.setTextureTranslate( f0, f1 );
break;
case 1:
// scale
f0 = quake3::getAsFloat( v.content, pos );
f1 = quake3::getAsFloat( v.content, pos );
m2.setTextureScale( f0, f1 );
break;
case 2:
//rotate
m2.setTextureRotationCenter( quake3::getAsFloat( v.content, pos ) * core::DEGTORAD * TimeAbs );
break;
case 3:
case 4:
case 5:
case 6:
case 7:
{
// turb == sin, default == sin
function.func = 0;
if (function.masterfunc1 == 5 && function.masterfunc0 == 1)
{
// deformvertexes, wave
function.wave = quake3::getAsFloat( v.content, pos );
}
if (function.masterfunc1 == 3 ||
function.masterfunc1 == 4 ||
function.masterfunc1 == 5)
{
// stretch, wave, tub
quake3::getModifierFunc( function, v.content, pos );
}
switch ( function.masterfunc1 )
{
case 3:
// stretch
f0 = core::reciprocal( function.evaluate(TimeAbs) );
m2.setTextureScaleCenter( f0, f0 );
break;
case 4:
{
// turb
/*
quake3::SModifierFunction ampFunction;
ampFunction.func = 0;
ampFunction.freq = function.freq;
ampFunction.base = 1.f;
ampFunction.amp = 0.2f;
f2 = ampFunction.evaluate( TimeAbs );
f0 = function.evaluate( TimeAbs );
function.func = 1;
f1 = function.evaluate( TimeAbs );
m2.setTextureTranslate( f0, f1 );
m2.setTextureScaleCenter( f2, f2 );
//m2.setTextureScale( f2, f2 );
*/
}break;
case 5:
if ( function.masterfunc0 == 1 )
{
vertextransform_wave( TimeAbs, function );
}
else
{
vertextransform_rgbgen( TimeAbs, function );
}
break;
case 6:
case 7:
vertextransform_rgbgen( TimeAbs, function );
break;
}
} break;
case 8:
case 9:
case 10:
// "texture","lightmap","environment"
function.tcgen = function.masterfunc1;
break;
case 11:
// map == lightmap, tcgen == lightmap
function.tcgen = 9;
break;
case 12:
// deformvertexes bulge
function.bulgewidth = quake3::getAsFloat( v.content, pos );
function.bulgeheight = quake3::getAsFloat( v.content, pos );
function.bulgespeed = quake3::getAsFloat( v.content, pos );
vertextransform_bulge(TimeAbs, function);
break;
case 13:
case 14:
// deformvertexes autosprite
vertextransform_autosprite(TimeAbs, function);
break;
} // func
switch ( function.masterfunc0 )
{
case 0:
texture *= m2;
break;
}
} // group
// texture coordinate modifier
vertextransform_tcgen(TimeAbs, function);
}
void CQuake3ShaderSceneNode::OnAnimate(u32 timeMs)
{
TimeAbs = f32( timeMs ) * (1.f/1000.f);
ISceneNode::OnAnimate( timeMs );
}
const core::aabbox3d<f32>& CQuake3ShaderSceneNode::getBoundingBox() const
{
return MeshBuffer->getBoundingBox();
}
u32 CQuake3ShaderSceneNode::getMaterialCount() const
{
return Q3Texture.size();
}
video::SMaterial& CQuake3ShaderSceneNode::getMaterial(u32 i)
{
video::SMaterial& m = MeshBuffer->getMaterial();
m.setTexture(0, 0);
if ( Q3Texture [ i ].TextureIndex )
m.setTexture(0, Q3Texture [ i ].Texture [ Q3Texture [ i ].TextureIndex ]);
return m;
}
} // end namespace scene
} // end namespace irr
#endif