00001 /* 00002 ----------------------------------------------------------------------------- 00003 This source file is part of OGRE 00004 (Object-oriented Graphics Rendering Engine) 00005 For the latest info, see http://www.ogre3d.org/ 00006 00007 Copyright © 2000-2002 The OGRE Team 00008 Also see acknowledgements in Readme.html 00009 00010 This program is free software you can redistribute it and/or modify it under 00011 the terms of the GNU Lesser General Public License as published by the Free Software 00012 Foundation either version 2 of the License, or (at your option) any later 00013 version. 00014 00015 This program is distributed in the hope that it will be useful, but WITHOUT 00016 ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS 00017 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 00018 00019 You should have received a copy of the GNU Lesser General Public License along with 00020 this program if not, write to the Free Software Foundation, Inc., 59 Temple 00021 Place - Suite 330, Boston, MA 02111-1307, USA, or go to 00022 http://www.gnu.org/copyleft/lesser.txt. 00023 ----------------------------------------------------------------------------- 00024 */ 00025 #include "OgreStableHeaders.h" 00026 00027 #include "OgreTechnique.h" 00028 #include "OgreMaterial.h" 00029 #include "OgrePass.h" 00030 #include "OgreRoot.h" 00031 #include "OgreRenderSystem.h" 00032 #include "OgreRenderSystemCapabilities.h" 00033 #include "OgreGpuProgramManager.h" 00034 00035 00036 namespace Ogre { 00037 //----------------------------------------------------------------------------- 00038 Technique::Technique(Material* parent) 00039 : mParent(parent), mIsSupported(false), mLodIndex(0) 00040 { 00041 // See above, defaults to unsupported until examined 00042 } 00043 //----------------------------------------------------------------------------- 00044 Technique::Technique(Material* parent, const Technique& oth) 00045 : mParent(parent), mLodIndex(0) 00046 { 00047 // Copy using operator= 00048 *this = oth; 00049 } 00050 //----------------------------------------------------------------------------- 00051 Technique::~Technique() 00052 { 00053 removeAllPasses(); 00054 clearIlluminationPasses(); 00055 } 00056 //----------------------------------------------------------------------------- 00057 bool Technique::isSupported(void) const 00058 { 00059 return mIsSupported; 00060 } 00061 //----------------------------------------------------------------------------- 00062 void Technique::_compile(bool autoManageTextureUnits) 00063 { 00064 // assume not supported 00065 mIsSupported = false; 00066 // Go through each pass, checking requirements 00067 Passes::iterator i, iend; 00068 iend = mPasses.end(); 00069 for (i = mPasses.begin(); i != iend; ++i) 00070 { 00071 Pass* currPass = *i; 00072 // Check texture unit requirements 00073 size_t numTexUnitsRequested = currPass->getNumTextureUnitStates(); 00074 const RenderSystemCapabilities* caps = 00075 Root::getSingleton().getRenderSystem()->getCapabilities(); 00076 unsigned short numTexUnits = caps->getNumTextureUnits(); 00077 00078 if (currPass->hasFragmentProgram()) 00079 { 00080 // Check texture units 00081 if (numTexUnitsRequested > numTexUnits) 00082 { 00083 // Can't do this one, and can't split a fragment pass 00084 return; 00085 } 00086 // Check fragment program version 00087 if (!currPass->getFragmentProgram()->isSupported()) 00088 { 00089 // Can't do this one 00090 return; 00091 } 00092 } 00093 else 00094 { 00095 // Check a few fixed-function options in texture layers 00096 Pass::TextureUnitStateIterator texi = currPass->getTextureUnitStateIterator(); 00097 while (texi.hasMoreElements()) 00098 { 00099 TextureUnitState* tex = texi.getNext(); 00100 // Any Cube textures? NB we make the assumption that any 00101 // card capable of running fragment programs can support 00102 // cubic textures, which has to be true, surely? 00103 if (tex->is3D() && !caps->hasCapability(RSC_CUBEMAPPING)) 00104 { 00105 // Fail 00106 return; 00107 } 00108 // Any Dot3 blending? 00109 if (tex->getColourBlendMode().operation == LBX_DOTPRODUCT && 00110 !caps->hasCapability(RSC_DOT3)) 00111 { 00112 // Fail 00113 return; 00114 } 00115 } 00116 00117 // We're ok on operations, now we need to check # texture units 00118 // Keep splitting this pass so long as units requested > gpu units 00119 while (numTexUnitsRequested > numTexUnits) 00120 { 00121 // chop this pass into many passes 00122 currPass = currPass->_split(numTexUnits); 00123 numTexUnitsRequested = currPass->getNumTextureUnitStates(); 00124 } 00125 } 00126 00127 if (currPass->hasVertexProgram()) 00128 { 00129 // Check vertex program version 00130 if (!currPass->getVertexProgram()->isSupported() ) 00131 { 00132 // Can't do this one 00133 return; 00134 } 00135 } 00136 00137 } 00138 // If we got this far, we're ok 00139 mIsSupported = true; 00140 00141 // Now compile for categorised illumination, incase we need it 00142 _compileIlluminationPasses(); 00143 00144 } 00145 //----------------------------------------------------------------------------- 00146 Pass* Technique::createPass(void) 00147 { 00148 Pass* newPass = new Pass(this, static_cast<unsigned short>(mPasses.size())); 00149 mPasses.push_back(newPass); 00150 return newPass; 00151 } 00152 //----------------------------------------------------------------------------- 00153 Pass* Technique::getPass(unsigned short index) 00154 { 00155 assert(index < mPasses.size() && "Index out of bounds"); 00156 return mPasses[index]; 00157 } 00158 //----------------------------------------------------------------------------- 00159 unsigned short Technique::getNumPasses(void) const 00160 { 00161 return static_cast<unsigned short>(mPasses.size()); 00162 } 00163 //----------------------------------------------------------------------------- 00164 void Technique::removePass(unsigned short index) 00165 { 00166 assert(index < mPasses.size() && "Index out of bounds"); 00167 Passes::iterator i = mPasses.begin() + index; 00168 (*i)->queueForDeletion(); 00169 mPasses.erase(i); 00170 } 00171 //----------------------------------------------------------------------------- 00172 void Technique::removeAllPasses(void) 00173 { 00174 Passes::iterator i, iend; 00175 iend = mPasses.end(); 00176 for (i = mPasses.begin(); i != iend; ++i) 00177 { 00178 (*i)->queueForDeletion(); 00179 } 00180 mPasses.clear(); 00181 } 00182 //----------------------------------------------------------------------------- 00183 const Technique::PassIterator Technique::getPassIterator(void) 00184 { 00185 return PassIterator(mPasses.begin(), mPasses.end()); 00186 } 00187 //----------------------------------------------------------------------------- 00188 Technique& Technique::operator=(const Technique& rhs) 00189 { 00190 this->mIsSupported = rhs.mIsSupported; 00191 this->mLodIndex = rhs.mLodIndex; 00192 // copy passes 00193 removeAllPasses(); 00194 Passes::const_iterator i, iend; 00195 iend = rhs.mPasses.end(); 00196 for (i = rhs.mPasses.begin(); i != iend; ++i) 00197 { 00198 Pass* p = new Pass(this, (*i)->getIndex(), *(*i)); 00199 mPasses.push_back(p); 00200 } 00201 // recompile illumination passes 00202 _compileIlluminationPasses(); 00203 return *this; 00204 } 00205 //----------------------------------------------------------------------------- 00206 bool Technique::isTransparent(void) const 00207 { 00208 if (mPasses.empty()) 00209 { 00210 return false; 00211 } 00212 else 00213 { 00214 // Base decision on the transparency of the first pass 00215 return mPasses[0]->isTransparent(); 00216 } 00217 } 00218 //----------------------------------------------------------------------------- 00219 bool Technique::isDepthWriteEnabled(void) const 00220 { 00221 if (mPasses.empty()) 00222 { 00223 return false; 00224 } 00225 else 00226 { 00227 // Base decision on the depth settings of the first pass 00228 return mPasses[0]->getDepthWriteEnabled(); 00229 } 00230 } 00231 //----------------------------------------------------------------------------- 00232 bool Technique::isDepthCheckEnabled(void) const 00233 { 00234 if (mPasses.empty()) 00235 { 00236 return false; 00237 } 00238 else 00239 { 00240 // Base decision on the depth settings of the first pass 00241 return mPasses[0]->getDepthCheckEnabled(); 00242 } 00243 } 00244 //----------------------------------------------------------------------------- 00245 void Technique::_load(void) 00246 { 00247 assert (mIsSupported && "This technique is not supported"); 00248 // Load each pass 00249 Passes::iterator i, iend; 00250 iend = mPasses.end(); 00251 for (i = mPasses.begin(); i != iend; ++i) 00252 { 00253 (*i)->_load(); 00254 } 00255 } 00256 //----------------------------------------------------------------------------- 00257 void Technique::_unload(void) 00258 { 00259 // Unload each pass 00260 Passes::iterator i, iend; 00261 iend = mPasses.end(); 00262 for (i = mPasses.begin(); i != iend; ++i) 00263 { 00264 (*i)->_unload(); 00265 } 00266 } 00267 //----------------------------------------------------------------------------- 00268 bool Technique::isLoaded(void) const 00269 { 00270 return mParent->isLoaded(); 00271 } 00272 //----------------------------------------------------------------------- 00273 void Technique::setAmbient(Real red, Real green, Real blue) 00274 { 00275 Passes::iterator i, iend; 00276 iend = mPasses.end(); 00277 for (i = mPasses.begin(); i != iend; ++i) 00278 { 00279 (*i)->setAmbient(red, green, blue); 00280 } 00281 00282 } 00283 //----------------------------------------------------------------------- 00284 void Technique::setAmbient(const ColourValue& ambient) 00285 { 00286 setAmbient(ambient.r, ambient.g, ambient.b); 00287 } 00288 //----------------------------------------------------------------------- 00289 void Technique::setDiffuse(Real red, Real green, Real blue, Real alpha) 00290 { 00291 Passes::iterator i, iend; 00292 iend = mPasses.end(); 00293 for (i = mPasses.begin(); i != iend; ++i) 00294 { 00295 (*i)->setDiffuse(red, green, blue, alpha); 00296 } 00297 } 00298 //----------------------------------------------------------------------- 00299 void Technique::setDiffuse(const ColourValue& diffuse) 00300 { 00301 setDiffuse(diffuse.r, diffuse.g, diffuse.b, diffuse.a); 00302 } 00303 //----------------------------------------------------------------------- 00304 void Technique::setSpecular(Real red, Real green, Real blue, Real alpha) 00305 { 00306 Passes::iterator i, iend; 00307 iend = mPasses.end(); 00308 for (i = mPasses.begin(); i != iend; ++i) 00309 { 00310 (*i)->setSpecular(red, green, blue, alpha); 00311 } 00312 } 00313 //----------------------------------------------------------------------- 00314 void Technique::setSpecular(const ColourValue& specular) 00315 { 00316 setSpecular(specular.r, specular.g, specular.b, specular.a); 00317 } 00318 //----------------------------------------------------------------------- 00319 void Technique::setShininess(Real val) 00320 { 00321 Passes::iterator i, iend; 00322 iend = mPasses.end(); 00323 for (i = mPasses.begin(); i != iend; ++i) 00324 { 00325 (*i)->setShininess(val); 00326 } 00327 } 00328 //----------------------------------------------------------------------- 00329 void Technique::setSelfIllumination(Real red, Real green, Real blue) 00330 { 00331 Passes::iterator i, iend; 00332 iend = mPasses.end(); 00333 for (i = mPasses.begin(); i != iend; ++i) 00334 { 00335 (*i)->setSelfIllumination(red, green, blue); 00336 } 00337 } 00338 //----------------------------------------------------------------------- 00339 void Technique::setSelfIllumination(const ColourValue& selfIllum) 00340 { 00341 setSelfIllumination(selfIllum.r, selfIllum.g, selfIllum.b); 00342 } 00343 //----------------------------------------------------------------------- 00344 void Technique::setDepthCheckEnabled(bool enabled) 00345 { 00346 Passes::iterator i, iend; 00347 iend = mPasses.end(); 00348 for (i = mPasses.begin(); i != iend; ++i) 00349 { 00350 (*i)->setDepthCheckEnabled(enabled); 00351 } 00352 } 00353 //----------------------------------------------------------------------- 00354 void Technique::setDepthWriteEnabled(bool enabled) 00355 { 00356 Passes::iterator i, iend; 00357 iend = mPasses.end(); 00358 for (i = mPasses.begin(); i != iend; ++i) 00359 { 00360 (*i)->setDepthWriteEnabled(enabled); 00361 } 00362 } 00363 //----------------------------------------------------------------------- 00364 void Technique::setDepthFunction( CompareFunction func ) 00365 { 00366 Passes::iterator i, iend; 00367 iend = mPasses.end(); 00368 for (i = mPasses.begin(); i != iend; ++i) 00369 { 00370 (*i)->setDepthFunction(func); 00371 } 00372 } 00373 //----------------------------------------------------------------------- 00374 void Technique::setColourWriteEnabled(bool enabled) 00375 { 00376 Passes::iterator i, iend; 00377 iend = mPasses.end(); 00378 for (i = mPasses.begin(); i != iend; ++i) 00379 { 00380 (*i)->setColourWriteEnabled(enabled); 00381 } 00382 } 00383 //----------------------------------------------------------------------- 00384 void Technique::setCullingMode( CullingMode mode ) 00385 { 00386 Passes::iterator i, iend; 00387 iend = mPasses.end(); 00388 for (i = mPasses.begin(); i != iend; ++i) 00389 { 00390 (*i)->setCullingMode(mode); 00391 } 00392 } 00393 //----------------------------------------------------------------------- 00394 void Technique::setManualCullingMode( ManualCullingMode mode ) 00395 { 00396 Passes::iterator i, iend; 00397 iend = mPasses.end(); 00398 for (i = mPasses.begin(); i != iend; ++i) 00399 { 00400 (*i)->setManualCullingMode(mode); 00401 } 00402 } 00403 //----------------------------------------------------------------------- 00404 void Technique::setLightingEnabled(bool enabled) 00405 { 00406 Passes::iterator i, iend; 00407 iend = mPasses.end(); 00408 for (i = mPasses.begin(); i != iend; ++i) 00409 { 00410 (*i)->setLightingEnabled(enabled); 00411 } 00412 } 00413 //----------------------------------------------------------------------- 00414 void Technique::setShadingMode( ShadeOptions mode ) 00415 { 00416 Passes::iterator i, iend; 00417 iend = mPasses.end(); 00418 for (i = mPasses.begin(); i != iend; ++i) 00419 { 00420 (*i)->setShadingMode(mode); 00421 } 00422 } 00423 //----------------------------------------------------------------------- 00424 void Technique::setFog(bool overrideScene, FogMode mode, const ColourValue& colour, 00425 Real expDensity, Real linearStart, Real linearEnd) 00426 { 00427 Passes::iterator i, iend; 00428 iend = mPasses.end(); 00429 for (i = mPasses.begin(); i != iend; ++i) 00430 { 00431 (*i)->setFog(overrideScene, mode, colour, expDensity, linearStart, linearEnd); 00432 } 00433 } 00434 //----------------------------------------------------------------------- 00435 void Technique::setDepthBias(ushort bias) 00436 { 00437 Passes::iterator i, iend; 00438 iend = mPasses.end(); 00439 for (i = mPasses.begin(); i != iend; ++i) 00440 { 00441 (*i)->setDepthBias(bias); 00442 } 00443 } 00444 //----------------------------------------------------------------------- 00445 void Technique::setTextureFiltering(TextureFilterOptions filterType) 00446 { 00447 Passes::iterator i, iend; 00448 iend = mPasses.end(); 00449 for (i = mPasses.begin(); i != iend; ++i) 00450 { 00451 (*i)->setTextureFiltering(filterType); 00452 } 00453 } 00454 // -------------------------------------------------------------------- 00455 void Technique::setTextureAnisotropy(unsigned int maxAniso) 00456 { 00457 Passes::iterator i, iend; 00458 iend = mPasses.end(); 00459 for (i = mPasses.begin(); i != iend; ++i) 00460 { 00461 (*i)->setTextureAnisotropy(maxAniso); 00462 } 00463 } 00464 // -------------------------------------------------------------------- 00465 void Technique::setSceneBlending( const SceneBlendType sbt ) 00466 { 00467 Passes::iterator i, iend; 00468 iend = mPasses.end(); 00469 for (i = mPasses.begin(); i != iend; ++i) 00470 { 00471 (*i)->setSceneBlending(sbt); 00472 } 00473 } 00474 // -------------------------------------------------------------------- 00475 void Technique::setSceneBlending( const SceneBlendFactor sourceFactor, 00476 const SceneBlendFactor destFactor) 00477 { 00478 Passes::iterator i, iend; 00479 iend = mPasses.end(); 00480 for (i = mPasses.begin(); i != iend; ++i) 00481 { 00482 (*i)->setSceneBlending(sourceFactor, destFactor); 00483 } 00484 } 00485 00486 //----------------------------------------------------------------------- 00487 void Technique::_notifyNeedsRecompile(void) 00488 { 00489 mParent->_notifyNeedsRecompile(); 00490 } 00491 //----------------------------------------------------------------------- 00492 void Technique::setLodIndex(unsigned short index) 00493 { 00494 mLodIndex = index; 00495 _notifyNeedsRecompile(); 00496 } 00497 //----------------------------------------------------------------------- 00498 void Technique::_compileIlluminationPasses(void) 00499 { 00500 clearIlluminationPasses(); 00501 00502 if (isTransparent()) 00503 { 00504 // Don't need to split transparents since they are rendered separately 00505 return; 00506 } 00507 00508 Passes::iterator i, iend; 00509 iend = mPasses.end(); 00510 i = mPasses.begin(); 00511 00512 IlluminationStage iStage = IS_AMBIENT; 00513 00514 bool haveAmbient = false; 00515 while (i != iend) 00516 { 00517 IlluminationPass* iPass; 00518 Pass* p = *i; 00519 switch(iStage) 00520 { 00521 case IS_AMBIENT: 00522 // Keep looking for ambient only 00523 if (p->isAmbientOnly()) 00524 { 00525 // Add this pass wholesale 00526 iPass = new IlluminationPass(); 00527 iPass->destroyOnShutdown = false; 00528 iPass->originalPass = iPass->pass = p; 00529 iPass->stage = iStage; 00530 mIlluminationPasses.push_back(iPass); 00531 haveAmbient = true; 00532 // progress to next pass 00533 ++i; 00534 } 00535 else 00536 { 00537 // Split off any ambient part 00538 if (p->getAmbient() != ColourValue::Black || 00539 p->getSelfIllumination() != ColourValue::Black) 00540 { 00541 // Copy existing pass 00542 Pass* newPass = new Pass(this, p->getIndex()); 00543 *newPass = *p; 00544 // Remove any texture units 00545 newPass->removeAllTextureUnitStates(); 00546 // Remove any fragment program 00547 if (newPass->hasFragmentProgram()) 00548 newPass->setFragmentProgram(""); 00549 // We have to leave vertex program alone (if any) and 00550 // just trust that the author is using light bindings, which 00551 // we will ensure there are none in the ambient pass 00552 newPass->setDiffuse(ColourValue::Black); 00553 newPass->setSpecular(ColourValue::Black); 00554 00555 // If ambient & emissive are zero, then no colour write 00556 if (newPass->getAmbient() == ColourValue::Black && 00557 newPass->getSelfIllumination() == ColourValue::Black) 00558 { 00559 newPass->setColourWriteEnabled(false); 00560 } 00561 00562 iPass = new IlluminationPass(); 00563 iPass->destroyOnShutdown = true; 00564 iPass->originalPass = p; 00565 iPass->pass = newPass; 00566 iPass->stage = iStage; 00567 00568 mIlluminationPasses.push_back(iPass); 00569 haveAmbient = true; 00570 00571 } 00572 00573 if (!haveAmbient) 00574 { 00575 // Make up a new basic pass 00576 Pass* newPass = new Pass(this, p->getIndex()); 00577 newPass->setAmbient(ColourValue::Black); 00578 newPass->setDiffuse(ColourValue::Black); 00579 iPass = new IlluminationPass(); 00580 iPass->destroyOnShutdown = true; 00581 iPass->originalPass = p; 00582 iPass->pass = newPass; 00583 iPass->stage = iStage; 00584 mIlluminationPasses.push_back(iPass); 00585 haveAmbient = true; 00586 } 00587 // This means we're done with ambients, progress to per-light 00588 iStage = IS_PER_LIGHT; 00589 } 00590 break; 00591 case IS_PER_LIGHT: 00592 if (p->getRunOncePerLight()) 00593 { 00594 // If this is per-light already, use it directly 00595 iPass = new IlluminationPass(); 00596 iPass->destroyOnShutdown = false; 00597 iPass->originalPass = iPass->pass = p; 00598 iPass->stage = iStage; 00599 mIlluminationPasses.push_back(iPass); 00600 // progress to next pass 00601 ++i; 00602 } 00603 else 00604 { 00605 // Split off per-light details (can only be done for one) 00606 if (p->getLightingEnabled() && 00607 (p->getDiffuse() != ColourValue::Black || 00608 p->getSpecular() != ColourValue::Black)) 00609 { 00610 // Copy existing pass 00611 Pass* newPass = new Pass(this, p->getIndex()); 00612 *newPass = *p; 00613 // remove texture units 00614 newPass->removeAllTextureUnitStates(); 00615 // remove fragment programs 00616 if (newPass->hasFragmentProgram()) 00617 newPass->setFragmentProgram(""); 00618 // Cannot remove vertex program, have to assume that 00619 // it will process diffuse lights, ambient will be turned off 00620 newPass->setAmbient(ColourValue::Black); 00621 newPass->setSelfIllumination(ColourValue::Black); 00622 // must be additive 00623 newPass->setSceneBlending(SBF_ONE, SBF_ONE); 00624 00625 iPass = new IlluminationPass(); 00626 iPass->destroyOnShutdown = true; 00627 iPass->originalPass = p; 00628 iPass->pass = newPass; 00629 iPass->stage = iStage; 00630 00631 mIlluminationPasses.push_back(iPass); 00632 00633 } 00634 // This means the end of per-light passes 00635 iStage = IS_DECAL; 00636 } 00637 break; 00638 case IS_DECAL: 00639 // We just want a 'lighting off' pass to finish off 00640 // and only if there are texture units 00641 if (p->getNumTextureUnitStates() > 0) 00642 { 00643 if (!p->getLightingEnabled()) 00644 { 00645 // we assume this pass already combines as required with the scene 00646 iPass = new IlluminationPass(); 00647 iPass->destroyOnShutdown = false; 00648 iPass->originalPass = iPass->pass = p; 00649 iPass->stage = iStage; 00650 mIlluminationPasses.push_back(iPass); 00651 } 00652 else 00653 { 00654 // Copy the pass and tweak away the lighting parts 00655 Pass* newPass = new Pass(this, p->getIndex()); 00656 *newPass = *p; 00657 newPass->setAmbient(ColourValue::Black); 00658 newPass->setDiffuse(ColourValue::Black); 00659 newPass->setSpecular(ColourValue::Black); 00660 newPass->setSelfIllumination(ColourValue::Black); 00661 newPass->setLightingEnabled(false); 00662 // modulate 00663 newPass->setSceneBlending(SBF_DEST_COLOUR, SBF_ZERO); 00664 00665 // NB there is nothing we can do about vertex & fragment 00666 // programs here, so people will just have to make their 00667 // programs friendly-like if they want to use this technique 00668 iPass = new IlluminationPass(); 00669 iPass->destroyOnShutdown = true; 00670 iPass->originalPass = p; 00671 iPass->pass = newPass; 00672 iPass->stage = iStage; 00673 mIlluminationPasses.push_back(iPass); 00674 00675 } 00676 } 00677 ++i; // always increment on decal, since nothing more to do with this pass 00678 00679 break; 00680 } 00681 } 00682 00683 } 00684 //----------------------------------------------------------------------- 00685 void Technique::clearIlluminationPasses(void) 00686 { 00687 IlluminationPassList::iterator i, iend; 00688 iend = mIlluminationPasses.end(); 00689 for (i = mIlluminationPasses.begin(); i != iend; ++i) 00690 { 00691 if ((*i)->destroyOnShutdown) 00692 { 00693 (*i)->pass->queueForDeletion(); 00694 } 00695 delete *i; 00696 } 00697 mIlluminationPasses.clear(); 00698 } 00699 //----------------------------------------------------------------------- 00700 const Technique::IlluminationPassIterator 00701 Technique::getIlluminationPassIterator(void) 00702 { 00703 return IlluminationPassIterator(mIlluminationPasses.begin(), 00704 mIlluminationPasses.end()); 00705 } 00706 00707 00708 }
Copyright © 2002-2003 by The OGRE Team
Last modified Sun Nov 28 19:48:48 2004