// Copyright (C) 2002-2012 Thomas Alten / Nikolaus Gebhardt
// 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"
#ifdef _IRR_COMPILE_WITH_SHADOW_VOLUME_SCENENODE_
#include "CShadowVolumeSceneNode.h"
#else
#include "IShadowVolumeSceneNode.h"
#endif
namespace irr
{
namespace scene
{
// who, if not you..
using namespace quake3;
/*!
*/
CQuake3ShaderSceneNode::CQuake3ShaderSceneNode(
scene::ISceneNode* parent, scene::ISceneManager* mgr,s32 id,
io::IFileSystem *fileSystem, const scene::IMeshBuffer *original,
const IShader * shader)
: scene::IMeshSceneNode(parent, mgr, id,
core::vector3df(0.f, 0.f, 0.f),
core::vector3df(0.f, 0.f, 0.f),
core::vector3df(1.f, 1.f, 1.f)),
Shader(shader), Mesh(0), Shadow(0), Original(0), MeshBuffer(0), TimeAbs(0.f)
{
#ifdef _DEBUG
core::stringc dName = "CQuake3ShaderSceneNode ";
dName += Shader->name;
setDebugName( dName.c_str() );
#endif
// name the Scene Node
this->Name = Shader->name;
// take lightmap vertex type
MeshBuffer = new SMeshBuffer();
Mesh = new SMesh ();
Mesh->addMeshBuffer ( MeshBuffer );
MeshBuffer->drop ();
//Original = new SMeshBufferLightMap();
Original = (const scene::SMeshBufferLightMap*) original;
Original->grab();
// clone meshbuffer to modifiable buffer
cloneBuffer(MeshBuffer, Original,
Original->getMaterial().ColorMask != 0);
// load all Textures in all stages
loadTextures( fileSystem );
setAutomaticCulling( scene::EAC_OFF );
}
/*!
*/
CQuake3ShaderSceneNode::~CQuake3ShaderSceneNode()
{
if (Shadow)
Shadow->drop();
if (Mesh)
Mesh->drop();
if (Original)
Original->drop();
}
/*
create single copies
*/
void CQuake3ShaderSceneNode::cloneBuffer( scene::SMeshBuffer *dest, const scene::SMeshBufferLightMap * buffer, bool translateCenter )
{
dest->Material = buffer->Material;
dest->Indices = buffer->Indices;
const u32 vsize = buffer->Vertices.size();
dest->Vertices.set_used( vsize );
for ( u32 i = 0; i!= vsize; ++i )
{
const video::S3DVertex2TCoords& src = buffer->Vertices[i];
video::S3DVertex &dst = dest->Vertices[i];
dst.Pos = src.Pos;
dst.Normal = src.Normal;
dst.Color = 0xFFFFFFFF;
dst.TCoords = src.TCoords;
if ( i == 0 )
dest->BoundingBox.reset ( src.Pos );
else
dest->BoundingBox.addInternalPoint ( src.Pos );
}
// move the (temp) Mesh to a ScenePosititon
// set Scene Node Position
if ( translateCenter )
{
MeshOffset = dest->BoundingBox.getCenter();
setPosition( MeshOffset );
core::matrix4 m;
m.setTranslation( -MeshOffset );
SceneManager->getMeshManipulator()->transform( dest, m );
}
// No Texture!. Use Shader-Pointer for sorting
dest->Material.setTexture(0, (video::ITexture*) Shader);
}
/*
load the textures for all stages
*/
void CQuake3ShaderSceneNode::loadTextures( io::IFileSystem * fileSystem )
{
const SVarGroup *group;
u32 i;
video::IVideoDriver *driver = SceneManager->getVideoDriver();
// generic stage
u32 mipmap = 0;
group = Shader->getGroup( 1 );
if ( group->isDefined ( "nomipmaps" ) )
{
mipmap = 2 | (driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS)? 1: 0 );
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
}
// clear all stages and prefill empty
Q3Texture.setAllocStrategy ( core::ALLOC_STRATEGY_SAFE );
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;
getTextures( Q3Texture [i].Texture, mapname, pos, fileSystem, driver );
}
}
// 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, getAsFloat( animmap, pos ) );
getTextures( Q3Texture [i].Texture, animmap, pos,fileSystem, driver );
}
// 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_TO_EDGE;
pos = 0;
getTextures( Q3Texture [i].Texture, clampmap, pos,fileSystem, driver );
}
if ( mipmap & 2 )
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, mipmap & 1);
}
/*
Register each texture stage, if first is visible
*/
void CQuake3ShaderSceneNode::OnRegisterSceneNode()
{
if ( isVisible() )
{
SceneManager->registerNodeForRendering(this, getRenderStage() );
}
ISceneNode::OnRegisterSceneNode();
}
/*
is this a transparent node ?
*/
E_SCENE_NODE_RENDER_PASS CQuake3ShaderSceneNode::getRenderStage() const
{
E_SCENE_NODE_RENDER_PASS ret = ESNRP_SOLID;
// generic stage
const SVarGroup *group;
group = Shader->getGroup( 1 );
/*
else
if ( group->getIndex( "portal" ) >= 0 )
{
ret = ESNRP_TRANSPARENT_EFFECT;
}
else
*/
if ( group->isDefined( "sort", "opaque" ) )
{
ret = ESNRP_SOLID;
}
else
if ( group->isDefined( "sort", "additive" ) )
{
ret = ESNRP_TRANSPARENT;
}
else
if ( strstr ( Shader->name.c_str(), "flame" ) ||
group->isDefined( "surfaceparm", "water" ) ||
group->isDefined( "sort", "underwater" )
)
{
ret = ESNRP_TRANSPARENT_EFFECT;
}
else
{
// Look if first drawing stage needs graphical underlay
for ( u32 stage = 2; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
{
if ( 0 == Q3Texture [ stage ].Texture.size() )
continue;
group = Shader->getGroup( stage );
SBlendFunc blendfunc ( video::EMFN_MODULATE_1X );
getBlendFunc( group->get( "blendfunc" ), blendfunc );
getBlendFunc( group->get( "alphafunc" ), blendfunc );
//ret = blendfunc.isTransparent ? ESNRP_TRANSPARENT : ESNRP_SOLID;
if ( blendfunc.isTransparent )
{
ret = ESNRP_TRANSPARENT;
}
break;
}
}
return ret;
}
/*
render in multipass technique
*/
void CQuake3ShaderSceneNode::render()
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
E_SCENE_NODE_RENDER_PASS pass = SceneManager->getSceneNodeRenderPass();
video::SMaterial material;
const SVarGroup *group;
material.Lighting = false;
material.setTexture(1, 0);
material.NormalizeNormals = false;
// generic stage
group = Shader->getGroup( 1 );
material.BackfaceCulling = getCullingFunction( group->get( "cull" ) );
u32 pushProjection = 0;
core::matrix4 projection ( core::matrix4::EM4CONST_NOTHING );
// decal ( solve z-fighting )
if ( group->isDefined( "polygonoffset" ) )
{
projection = driver->getTransform( video::ETS_PROJECTION );
core::matrix4 decalProjection ( projection );
/*
f32 n = SceneManager->getActiveCamera()->getNearValue();
f32 f = SceneManager->getActiveCamera()->getFarValue ();
f32 delta = 0.01f;
f32 pz = 0.2f;
f32 epsilon = -2.f * f * n * delta / ( ( f + n ) * pz * ( pz + delta ) );
decalProjection[10] *= 1.f + epsilon;
*/
// TODO: involve camera
decalProjection[10] -= 0.0002f;
driver->setTransform( video::ETS_PROJECTION, decalProjection );
pushProjection |= 1;
}
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation );
if (Shadow)
Shadow->updateShadowVolumes();
//! render all stages
u32 drawCount = (pass == ESNRP_TRANSPARENT_EFFECT) ? 1 : 0;
core::matrix4 textureMatrix ( core::matrix4::EM4CONST_NOTHING );
for ( u32 stage = 1; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
{
SQ3Texture &q = Q3Texture[stage];
// advance current stage
textureMatrix.makeIdentity();
animate( stage, textureMatrix );
// stage finished, no drawing stage ( vertex transform only )
video::ITexture * tex = q.Texture.size() ? q.Texture [ q.TextureIndex ] : 0;
if ( 0 == tex )
continue;
// current stage
group = Shader->getGroup( stage );
material.setTexture(0, tex );
material.ZBuffer = getDepthFunction( group->get( "depthfunc" ) );
// TODO: maybe should be video::EZW_ON instead of EZW_AUTO now (we didn't have that before and I just kept old values here when introducing it to not break anything)
if ( group->isDefined( "depthwrite" ) )
{
material.ZWriteEnable = video::EZW_AUTO;
}
else
{
material.ZWriteEnable = drawCount == 0 ? video::EZW_AUTO : video::EZW_OFF;
}
//resolve quake3 blendfunction to irrlicht Material Type
SBlendFunc blendfunc ( video::EMFN_MODULATE_1X );
getBlendFunc( group->get( "blendfunc" ), blendfunc );
getBlendFunc( group->get( "alphafunc" ), blendfunc );
material.MaterialType = blendfunc.type;
material.MaterialTypeParam = blendfunc.param0;
material.TextureLayer[0].TextureWrapU = q.TextureAddressMode;
material.TextureLayer[0].TextureWrapV = q.TextureAddressMode;
material.TextureLayer[0].TextureWrapW = q.TextureAddressMode;
//material.TextureLayer[0].TrilinearFilter = 1;
//material.TextureLayer[0].AnisotropicFilter = 0xFF;
material.setTextureMatrix( 0, textureMatrix );
driver->setMaterial( material );
driver->drawMeshBuffer( MeshBuffer );
drawCount += 1;
}
if ( DebugDataVisible & scene::EDS_MESH_WIRE_OVERLAY )
{
video::SMaterial deb_m;
deb_m.Wireframe = true;
deb_m.Lighting = false;
deb_m.BackfaceCulling = material.BackfaceCulling;
driver->setMaterial( deb_m );
driver->drawMeshBuffer( MeshBuffer );
}
// show normals
if ( DebugDataVisible & scene::EDS_NORMALS )
{
video::SMaterial deb_m;
IAnimatedMesh * arrow = SceneManager->addArrowMesh (
"__debugnormalq3",
0xFFECEC00,0xFF999900,
4, 8,
8.f, 6.f,
0.5f,1.f
);
if ( 0 == arrow )
{
arrow = SceneManager->getMesh ( "__debugnormalq3" );
}
const IMesh *mesh = arrow->getMesh ( 0 );
// find a good scaling factor
core::matrix4 m2;
// draw normals
const scene::IMeshBuffer* mb = MeshBuffer;
const u32 vSize = video::getVertexPitchFromType(mb->getVertexType());
const video::S3DVertex* v = ( const video::S3DVertex*)mb->getVertices();
//f32 colCycle = 270.f / (f32) core::s32_max ( mb->getVertexCount() - 1, 1 );
for ( u32 i=0; i != mb->getVertexCount(); ++i )
{
// Align to v->normal
m2.buildRotateFromTo ( core::vector3df ( 0.f, 1.f, 0 ), v->Normal );
m2.setTranslation ( v->Pos + AbsoluteTransformation.getTranslation () );
/*
core::quaternion quatRot( v->Normal.Z, 0.f, -v->Normal.X, 1 + v->Normal.Y );
quatRot.normalize();
quatRot.getMatrix ( m2, v->Pos );
m2 [ 12 ] += AbsoluteTransformation [ 12 ];
m2 [ 13 ] += AbsoluteTransformation [ 13 ];
m2 [ 14 ] += AbsoluteTransformation [ 14 ];
*/
driver->setTransform(video::ETS_WORLD, m2 );
deb_m.Lighting = true;
/*
irr::video::SColorHSL color;
irr::video::SColor rgb(0);
color.Hue = i * colCycle * core::DEGTORAD;
color.Saturation = 1.f;
color.Luminance = 0.5f;
color.toRGB( deb_m.EmissiveColor );
*/
switch ( i )
{
case 0: deb_m.EmissiveColor.set(0xFFFFFFFF); break;
case 1: deb_m.EmissiveColor.set(0xFFFF0000); break;
case 2: deb_m.EmissiveColor.set(0xFF00FF00); break;
case 3: deb_m.EmissiveColor.set(0xFF0000FF); break;
default:
deb_m.EmissiveColor = v->Color; break;
}
driver->setMaterial( deb_m );
for ( u32 a = 0; a != mesh->getMeshBufferCount(); ++a )
driver->drawMeshBuffer ( mesh->getMeshBuffer ( a ) );
v = (const video::S3DVertex*) ( (u8*) v + vSize );
}
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
}
if ( pushProjection & 1 )
{
driver->setTransform( video::ETS_PROJECTION, projection );
}
if ( DebugDataVisible & scene::EDS_BBOX )
{
video::SMaterial deb_m;
deb_m.Lighting = false;
driver->setMaterial(deb_m);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->draw3DBox( getBoundingBox(), video::SColor(255,255,0,0));
}
}
//! Removes a child from this scene node.
//! Implemented here, to be able to remove the shadow properly, if there is one,
//! or to remove attached childs.
bool CQuake3ShaderSceneNode::removeChild(ISceneNode* child)
{
if (child && Shadow == child)
{
Shadow->drop();
Shadow = 0;
}
return ISceneNode::removeChild(child);
}
//! Creates shadow volume scene node as child of this node
//! and returns a pointer to it.
IShadowVolumeSceneNode* CQuake3ShaderSceneNode::addShadowVolumeSceneNode(
const IMesh* shadowMesh, s32 id, bool zfailmethod, f32 infinity)
{
#ifdef _IRR_COMPILE_WITH_SHADOW_VOLUME_SCENENODE_
if (!SceneManager->getVideoDriver()->queryFeature(video::EVDF_STENCIL_BUFFER))
return 0;
if (!shadowMesh)
shadowMesh = Mesh; // if null is given, use the mesh of node
if (Shadow)
Shadow->drop();
Shadow = new CShadowVolumeSceneNode(shadowMesh, this, SceneManager, id, zfailmethod, infinity);
return Shadow;
#else
return 0;
#endif
}
/*!
3.3.1 deformVertexes wave
Designed for water surfaces, modifying the values differently at each point.
It accepts the standard wave functions of the type sin, triangle, square, sawtooth
or inversesawtooth. The "div" parameter is used to control the wave "spread"
- a value equal to the tessSize of the surface is a good default value
(tessSize is subdivision size, in game units, used for the shader when seen in the game world) .
*/
void CQuake3ShaderSceneNode::deformvertexes_wave( f32 dt, SModifierFunction &function )
{
function.wave = core::reciprocal( function.wave );
const f32 phase = function.phase;
const u32 vsize = Original->Vertices.size();
for ( u32 i = 0; i != vsize; ++i )
{
const video::S3DVertex2TCoords &src = Original->Vertices[i];
video::S3DVertex &dst = MeshBuffer->Vertices[i];
if ( 0 == function.count )
dst.Pos = src.Pos - MeshOffset;
const f32 wavephase = (dst.Pos.X + dst.Pos.Y + dst.Pos.Z) * function.wave;
function.phase = phase + wavephase;
const f32 f = function.evaluate( dt );
dst.Pos.X += f * src.Normal.X;
dst.Pos.Y += f * src.Normal.Y;
dst.Pos.Z += f * src.Normal.Z;
if ( i == 0 )
MeshBuffer->BoundingBox.reset ( dst.Pos );
else
MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
}
function.count = 1;
}
/*!
deformVertexes move x y z func base amplitude phase freq
The move parameter is used to make a brush, curve patch or model
appear to move together as a unit. The x y z values are the distance
and direction in game units the object appears to move relative to
it's point of origin in the map. The func base amplitude phase freq values are
the same as found in other waveform manipulations.
The product of the function modifies the values x, y, and z.
Therefore, if you have an amplitude of 5 and an x value of 2,
the object will travel 10 units from its point of origin along the x axis.
This results in a total of 20 units of motion along the x axis, since the
amplitude is the variation both above and below the base.
It must be noted that an object made with this shader does not actually
change position, it only appears to.
Design Notes:
If an object is made up of surfaces with different shaders, all must have
matching deformVertexes move values or the object will appear to tear itself apart.
*/
void CQuake3ShaderSceneNode::deformvertexes_move( f32 dt, SModifierFunction &function )
{
function.wave = core::reciprocal( function.wave );
const f32 f = function.evaluate( dt );
const u32 vsize = Original->Vertices.size();
for ( u32 i = 0; i != vsize; ++i )
{
const video::S3DVertex2TCoords &src = Original->Vertices[i];
video::S3DVertex &dst = MeshBuffer->Vertices[i];
if ( 0 == function.count )
dst.Pos = src.Pos - MeshOffset;
dst.Pos.X += f * function.x;
dst.Pos.Y += f * function.y;
dst.Pos.Z += f * function.z;
if ( i == 0 )
MeshBuffer->BoundingBox.reset ( dst.Pos );
else
MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
}
function.count = 1;
}
/*!
3.3.2 deformVertexes normal
This deformation affects the normals of a vertex without actually moving it,
which will effect later shader options like lighting and especially environment mapping.
If the shader stages don't use normals in any of their calculations, there will
be no visible effect.
Design Notes: Putting values of 0.1 t o 0.5 in Amplitude and 1.0 to 4.0 in the
Frequency can produce some satisfying results. Some things that have been
done with it: A small fluttering bat, falling leaves, rain, flags.
*/
void CQuake3ShaderSceneNode::deformvertexes_normal( f32 dt, SModifierFunction &function )
{
function.func = SINUS;
const u32 vsize = Original->Vertices.size();
for ( u32 i = 0; i != vsize; ++i )
{
const video::S3DVertex2TCoords &src = Original->Vertices[i];
video::S3DVertex &dst = MeshBuffer->Vertices[i];
function.base = atan2f ( src.Pos.X, src.Pos.Y );
function.phase = src.Pos.X + src.Pos.Z;
const f32 lat = function.evaluate( dt );
function.base = src.Normal.Y;
function.phase = src.Normal.Z + src.Normal.X;
const f32 lng = function.evaluate( dt );
dst.Normal.X = cosf ( lat ) * sinf ( lng );
dst.Normal.Y = sinf ( lat ) * sinf ( lng );
dst.Normal.Z = cosf ( lng );
}
}
/*!
3.3.3 deformVertexes bulge
This forces a bulge to move along the given s and t directions. Designed for use
on curved pipes.
Specific parameter definitions for deform keywords:
This is roughly defined as the size of the waves that occur.
It is measured in game units. Smaller values create a greater
density of smaller wave forms occurring in a given area.
Larger values create a lesser density of waves, or otherwise put,
the appearance of larger waves. To look correct this value should
closely correspond to the value (in pixels) set for tessSize (tessellation size)
of the texture. A value of 100.0 is a good default value
(which means your tessSize should be close to that for things to look "wavelike").
This is the type of wave form being created. Sin stands for sine wave,
a regular smoothly flowing wave. Triangle is a wave with a sharp ascent
and a sharp decay. It will make a choppy looking wave forms.
A square wave is simply on or off for the period of the
frequency with no in between. The sawtooth wave has the ascent of a
triangle wave, but has the decay cut off sharply like a square wave.
An inversesawtooth wave reverses this.
This is the distance, in game units that the apparent surface of the
texture is displaced from the actual surface of the brush as placed
in the editor. A positive value appears above the brush surface.
A negative value appears below the brush surface.
An example of this is the Quad effect, which essentially is a
shell with a positive base value to stand it away from the model
surface and a 0 (zero) value for amplitude.
The distance that the deformation moves away from the base value.
See Wave Forms in the introduction for a description of amplitude.
See Wave Forms in the introduction for a description of phase)
See Wave Forms in the introduction for a description of frequency)
Design Note: The div and amplitude parameters, when used in conjunction with
liquid volumes like water should take into consideration how much the water
will be moving. A large ocean area would have have massive swells (big div values)
that rose and fell dramatically (big amplitude values). While a small, quiet pool
may move very little.
*/
void CQuake3ShaderSceneNode::deformvertexes_bulge( f32 dt, SModifierFunction &function )
{
function.func = SINUS;
function.wave = core::reciprocal( function.bulgewidth );
dt *= function.bulgespeed * 0.1f;
const f32 phase = function.phase;
const u32 vsize = Original->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 );
if ( 0 == function.count )
dst.Pos = src.Pos - MeshOffset;
dst.Pos.X += f * src.Normal.X;
dst.Pos.Y += f * src.Normal.Y;
dst.Pos.Z += f * src.Normal.Z;
if ( i == 0 )
MeshBuffer->BoundingBox.reset ( dst.Pos );
else
MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
}
function.count = 1;
}
/*!
deformVertexes autosprite
This function can be used to make any given triangle quad
(pair of triangles that form a square rectangle) automatically behave
like a sprite without having to make it a separate entity. This means
that the "sprite" on which the texture is placed will rotate to always
appear at right angles to the player's view as a sprite would. Any four-sided
brush side, flat patch, or pair of triangles in a model can have the autosprite
effect on it. The brush face containing a texture with this shader keyword must
be square.
*/
void CQuake3ShaderSceneNode::deformvertexes_autosprite( f32 dt, SModifierFunction &function )
{
u32 vsize = Original->Vertices.size();
u32 g;
u32 i;
const core::vector3df& camPos = SceneManager->getActiveCamera()->getPosition();
video::S3DVertex * dv = MeshBuffer->Vertices.pointer();
const video::S3DVertex2TCoords * vin = Original->Vertices.const_pointer();
core::matrix4 lookat ( core::matrix4::EM4CONST_NOTHING );
core::quaternion q;
for ( i = 0; i < vsize; i += 4 )
{
// quad-plane
core::vector3df center = 0.25f * ( vin[i+0].Pos + vin[i+1].Pos + vin[i+2].Pos + vin[i+3].Pos );
core::vector3df forward = camPos - center;
q.rotationFromTo ( vin[i].Normal, forward );
q.getMatrixCenter ( lookat, center, MeshOffset );
for ( g = 0; g < 4; ++g )
{
lookat.transformVect ( dv[i+g].Pos, vin[i+g].Pos );
lookat.rotateVect ( dv[i+g].Normal, vin[i+g].Normal );
}
}
function.count = 1;
}
/*!
deformVertexes autosprite2
Is a slightly modified "sprite" that only rotates around the middle of its longest axis.
This allows you to make a pillar of fire that you can walk around, or an energy beam
stretched across the room.
*/
struct sortaxis
{
core::vector3df v;
bool operator < ( const sortaxis &other ) const
{
return v.getLengthSQ () < other.v.getLengthSQ ();
}
};
/*!
*/
void CQuake3ShaderSceneNode::deformvertexes_autosprite2( f32 dt, SModifierFunction &function )
{
u32 vsize = Original->Vertices.size();
u32 g;
u32 i;
const core::vector3df camPos = SceneManager->getActiveCamera()->getAbsolutePosition();
video::S3DVertex * dv = MeshBuffer->Vertices.pointer();
const video::S3DVertex2TCoords * vin = Original->Vertices.const_pointer();
core::matrix4 lookat ( core::matrix4::EM4CONST_NOTHING );
core::array < sortaxis > axis;
axis.set_used ( 3 );
for ( i = 0; i < vsize; i += 4 )
{
// quad-plane
core::vector3df center = 0.25f * ( vin[i+0].Pos + vin[i+1].Pos + vin[i+2].Pos + vin[i+3].Pos );
// longes axe
axis[0].v = vin[i+1].Pos - vin[i+0].Pos;
axis[1].v = vin[i+2].Pos - vin[i+0].Pos;
axis[2].v = vin[i+3].Pos - vin[i+0].Pos;
axis.set_sorted ( false );
axis.sort ();
lookat.buildAxisAlignedBillboard ( camPos, center, MeshOffset, axis[1].v, vin[i+0].Normal );
for ( g = 0; g < 4; ++g )
{
lookat.transformVect ( dv[i+g].Pos, vin[i+g].Pos );
lookat.rotateVect ( dv[i+g].Normal, vin[i+g].Normal );
}
}
function.count = 1;
}
/*
Generate Vertex Color
*/
void CQuake3ShaderSceneNode::vertextransform_rgbgen( f32 dt, SModifierFunction &function )
{
u32 i;
const u32 vsize = Original->Vertices.size();
switch ( function.rgbgen )
{
case IDENTITY:
//rgbgen identity
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color.set(0xFFFFFFFF);
break;
case IDENTITYLIGHTING:
// rgbgen identitylighting TODO: overbright
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color.set(0xFF7F7F7F);
break;
case EXACTVERTEX:
// alphagen exactvertex TODO lighting
case VERTEX:
// rgbgen vertex
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color=Original->Vertices[i].Color;
break;
case WAVE:
{
// rgbGen wave
f32 f = function.evaluate( dt ) * 255.f;
s32 value = core::clamp( core::floor32(f), 0, 255 );
value = 0xFF000000 | value << 16 | value << 8 | value;
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color.set(value);
} break;
case CONSTANT:
{
//rgbgen const ( x y z )
video::SColorf cf( function.x, function.y, function.z );
video::SColor col = cf.toSColor();
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color=col;
} break;
default:
break;
}
}
/*
Generate Vertex Color, Alpha
*/
void CQuake3ShaderSceneNode::vertextransform_alphagen( f32 dt, SModifierFunction &function )
{
u32 i;
const u32 vsize = Original->Vertices.size();
switch ( function.alphagen )
{
case IDENTITY:
//alphagen identity
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color.setAlpha ( 0xFF );
break;
case EXACTVERTEX:
// alphagen exactvertex TODO lighting
case VERTEX:
// alphagen vertex
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color.setAlpha ( Original->Vertices[i].Color.getAlpha() );
break;
case CONSTANT:
{
// alphagen const
u32 a = (u32) ( function.x * 255.f );
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color.setAlpha ( a );
} break;
case LIGHTINGSPECULAR:
{
// alphagen lightingspecular TODO!!!
const SViewFrustum *frustum = SceneManager->getActiveCamera()->getViewFrustum();
const core::matrix4 &view = frustum->getTransform ( video::ETS_VIEW );
const f32 *m = view.pointer();
for ( i = 0; i != vsize; ++i )
{
const core::vector3df &n = Original->Vertices[i].Normal;
MeshBuffer->Vertices[i].Color.setAlpha ((u32)( 128.f *(1.f+(n.X*m[0]+n.Y*m[1]+n.Z*m[2]))));
}
} break;
case WAVE:
{
// alphagen wave
f32 f = function.evaluate( dt ) * 255.f;
s32 value = core::clamp( core::floor32(f), 0, 255 );
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].Color.setAlpha ( value );
} break;
default:
break;
}
}
/*
Generate Texture Coordinates
*/
void CQuake3ShaderSceneNode::vertextransform_tcgen( f32 dt, SModifierFunction &function )
{
u32 i;
const u32 vsize = Original->Vertices.size();
switch ( function.tcgen )
{
case TURBULENCE:
//tcgen turb
{
function.wave = core::reciprocal( function.phase );
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 TEXTURE:
// tcgen texture
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords;
break;
case LIGHTMAP:
// tcgen lightmap
for ( i = 0; i != vsize; ++i )
MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords2;
break;
case ENVIRONMENT:
{
// tcgen environment
const SViewFrustum *frustum = SceneManager->getActiveCamera()->getViewFrustum();
const core::matrix4 &view = frustum->getTransform ( video::ETS_VIEW );
const f32 *m = view.pointer();
core::vector3df n;
for ( i = 0; i != vsize; ++i )
{
//const core::vector3df &n = Original->Vertices[i].Normal;
n = frustum->cameraPosition - Original->Vertices[i].Pos;
n.normalize();
n += Original->Vertices[i].Normal;
n.normalize();
MeshBuffer->Vertices[i].TCoords.X = 0.5f*(1.f+(n.X*m[0]+n.Y*m[1]+n.Z*m[2]));
MeshBuffer->Vertices[i].TCoords.Y = 0.5f*(1.f+(n.X*m[4]+n.Y*m[5]+n.Z*m[6]));
}
} break;
default:
break;
}
}
#if 0
/*
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 );
}
}
}
#endif
/*
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 SVarGroup *group = Shader->getGroup( stage );
// select current texture
SQ3Texture &q3Tex = Q3Texture [ stage ];
if ( q3Tex.TextureFrequency != 0.f )
{
s32 v = core::floor32( TimeAbs * q3Tex.TextureFrequency );
q3Tex.TextureIndex = v % q3Tex.Texture.size();
}
core::matrix4 m2;
SModifierFunction function;
f32 f[16];
// walk group for all modifiers
for ( u32 g = 0; g != group->Variable.size(); ++g )
{
const SVariable &v = group->Variable[g];
// get the modifier
static const c8 * const modifierList[] =
{
"tcmod","deformvertexes","rgbgen","tcgen","map","alphagen"
};
u32 pos = 0;
function.masterfunc0 = (eQ3ModifierFunction) isEqual( v.name, pos, modifierList, 6 );
if ( UNKNOWN == function.masterfunc0 )
continue;
switch ( function.masterfunc0 )
{
//tcmod
case TCMOD:
m2.makeIdentity();
break;
default:
break;
}
// get the modifier function
static const c8 * const funclist[] =
{
"scroll","scale","rotate","stretch","turb",
"wave","identity","vertex",
"texture","lightmap","environment","$lightmap",
"bulge","autosprite","autosprite2","transform",
"exactvertex","const","lightingspecular","move","normal",
"identitylighting"
};
static const c8 * const groupToken[] = { "(", ")" };
pos = 0;
function.masterfunc1 = (eQ3ModifierFunction) isEqual( v.content, pos, funclist, 22 );
if ( function.masterfunc1 != UNKNOWN )
function.masterfunc1 = (eQ3ModifierFunction) ((u32) function.masterfunc1 + FUNCTION2 + 1);
switch ( function.masterfunc1 )
{
case SCROLL:
// tcMod scroll
f[0] = getAsFloat( v.content, pos ) * TimeAbs;
f[1] = getAsFloat( v.content, pos ) * TimeAbs;
m2.setTextureTranslate( f[0], f[1] );
break;
case SCALE:
// tcmod scale
f[0] = getAsFloat( v.content, pos );
f[1] = getAsFloat( v.content, pos );
m2.setTextureScale( f[0], f[1] );
break;
case ROTATE:
// tcmod rotate
m2.setTextureRotationCenter( getAsFloat( v.content, pos ) *
core::DEGTORAD *
TimeAbs
);
break;
case TRANSFORM:
// tcMod
memset(f, 0, sizeof ( f ));
f[10] = f[15] = 1.f;
f[0] = getAsFloat( v.content, pos );
f[1] = getAsFloat( v.content, pos );
f[4] = getAsFloat( v.content, pos );
f[5] = getAsFloat( v.content, pos );
f[8] = getAsFloat( v.content, pos );
f[9] = getAsFloat( v.content, pos );
m2.setM ( f );
break;
case STRETCH: // stretch
case TURBULENCE: // turb
case WAVE: // wave
case IDENTITY: // identity
case IDENTITYLIGHTING:
case VERTEX: // vertex
case MOVE:
case CONSTANT:
{
// turb == sin, default == sin
function.func = SINUS;
if ( function.masterfunc0 == DEFORMVERTEXES )
{
switch ( function.masterfunc1 )
{
case WAVE:
// deformvertexes wave
function.wave = getAsFloat( v.content, pos );
break;
case MOVE:
//deformvertexes move
function.x = getAsFloat( v.content, pos );
function.z = getAsFloat( v.content, pos );
function.y = getAsFloat( v.content, pos );
break;
default:
break;
}
}
switch ( function.masterfunc1 )
{
case STRETCH:
case TURBULENCE:
case WAVE:
case MOVE:
getModifierFunc( function, v.content, pos );
break;
default:
break;
}
switch ( function.masterfunc1 )
{
case STRETCH:
//tcMod stretch
f[0] = core::reciprocal( function.evaluate(TimeAbs) );
m2.setTextureScaleCenter( f[0], f[0] );
break;
case TURBULENCE:
//tcMod turb
//function.tcgen = TURBULENCE;
m2.setTextureRotationCenter( function.frequency *
core::DEGTORAD *
TimeAbs
);
break;
case WAVE:
case IDENTITY:
case IDENTITYLIGHTING:
case VERTEX:
case EXACTVERTEX:
case CONSTANT:
case LIGHTINGSPECULAR:
case MOVE:
switch ( function.masterfunc0 )
{
case DEFORMVERTEXES:
switch ( function.masterfunc1 )
{
case WAVE:
deformvertexes_wave( TimeAbs, function );
break;
case MOVE:
deformvertexes_move( TimeAbs, function );
break;
default:
break;
}
break;
case RGBGEN:
function.rgbgen = function.masterfunc1;
if ( function.rgbgen == CONSTANT )
{
isEqual ( v.content, pos, groupToken, 2 );
function.x = getAsFloat( v.content, pos );
function.y = getAsFloat( v.content, pos );
function.z = getAsFloat( v.content, pos );
}
//vertextransform_rgbgen( TimeAbs, function );
break;
case ALPHAGEN:
function.alphagen = function.masterfunc1;
if ( function.alphagen == CONSTANT )
{
function.x = getAsFloat( v.content, pos );
}
//vertextransform_alphagen( TimeAbs, function );
break;
default:
break;
}
break;
default:
break;
}
} break;
case TEXTURE:
case LIGHTMAP:
case ENVIRONMENT:
// "texture","lightmap","environment"
function.tcgen = function.masterfunc1;
break;
case DOLLAR_LIGHTMAP:
// map == lightmap, tcgen == lightmap
function.tcgen = LIGHTMAP;
break;
case BULGE:
// deformvertexes bulge
function.bulgewidth = getAsFloat( v.content, pos );
function.bulgeheight = getAsFloat( v.content, pos );
function.bulgespeed = getAsFloat( v.content, pos );
deformvertexes_bulge(TimeAbs, function);
break;
case NORMAL:
// deformvertexes normal
function.amp = getAsFloat( v.content, pos );
function.frequency = getAsFloat( v.content, pos );
deformvertexes_normal(TimeAbs, function);
break;
case AUTOSPRITE:
// deformvertexes autosprite
deformvertexes_autosprite(TimeAbs, function);
break;
case AUTOSPRITE2:
// deformvertexes autosprite2
deformvertexes_autosprite2(TimeAbs, function);
break;
default:
break;
} // func
switch ( function.masterfunc0 )
{
case TCMOD:
texture *= m2;
break;
default:
break;
}
} // group
vertextransform_rgbgen( TimeAbs, function );
vertextransform_alphagen( TimeAbs, function );
vertextransform_tcgen( TimeAbs, function );
}
void CQuake3ShaderSceneNode::OnAnimate(u32 timeMs)
{
TimeAbs = f32( timeMs ) * (1.f/1000.f);
ISceneNode::OnAnimate( timeMs );
}
const core::aabbox3d& CQuake3ShaderSceneNode::getBoundingBox() const
{
return MeshBuffer->getBoundingBox();
}
u32 CQuake3ShaderSceneNode::getMaterialCount() const
{
return Q3Texture.size();
}
video::SMaterial& CQuake3ShaderSceneNode::getMaterial(u32 i)
{
video::SMaterial& m = MeshBuffer->Material;
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