// 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" #include "IMaterialRenderer.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