mirror of
https://github.com/minetest/irrlicht.git
synced 2024-11-14 06:20:26 +01:00
c481179825
Add some for newer examples which didn't have those so far. Only updating this once now as some were broken. Next time on release. git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@6204 dfc29bdd-3216-0410-991c-e03cc46cb475
210 lines
23 KiB
HTML
210 lines
23 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
<meta name="generator" content="Doxygen 1.8.13"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
<title>Tutorial 12: Terrain Rendering</title>
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<!-- Wanted to avoid copying .css to each folder, so copied default .css from doxyen in here, kicked out most stuff we don't need for examples and modified some a little bit.
|
|
Target was having a single html in each example folder which is created from the main.cpp files and needs no files besides some images below media folder.
|
|
Feel free to improve :)
|
|
-->
|
|
<style>
|
|
body, table, div, p, dl {
|
|
font: 400 14px/22px;
|
|
}
|
|
body {
|
|
background-color: #F0F0F0;
|
|
color: black;
|
|
margin-left: 5%;
|
|
margin-right: 5%;
|
|
}
|
|
p.reference, p.definition {
|
|
font: 400 14px/22px;
|
|
}
|
|
.title {
|
|
font: 400 14px/28px;
|
|
font-size: 150%;
|
|
font-weight: bold;
|
|
margin: 10px 2px;
|
|
}
|
|
h1, h2, h3, h4, h5, h6 {
|
|
-webkit-transition: text-shadow 0.5s linear;
|
|
-moz-transition: text-shadow 0.5s linear;
|
|
-ms-transition: text-shadow 0.5s linear;
|
|
-o-transition: text-shadow 0.5s linear;
|
|
transition: text-shadow 0.5s linear;
|
|
margin-right: 15px;
|
|
}
|
|
caption {
|
|
font-weight: bold;
|
|
}
|
|
h3.version {
|
|
font-size: 90%;
|
|
text-align: center;
|
|
}
|
|
a {
|
|
color: #3D578C;
|
|
font-weight: normal;
|
|
text-decoration: none;
|
|
}
|
|
.contents a:visited {
|
|
color: #4665A2;
|
|
}
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
a.el {
|
|
font-weight: bold;
|
|
}
|
|
a.code, a.code:visited, a.line, a.line:visited {
|
|
color: #4665A2;
|
|
}
|
|
a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited {
|
|
color: #4665A2;
|
|
}
|
|
pre.fragment {
|
|
border: 1px solid #C4CFE5;
|
|
background-color: #FBFCFD;
|
|
padding: 4px 6px;
|
|
margin: 4px 8px 4px 2px;
|
|
overflow: auto;
|
|
word-wrap: break-word;
|
|
font-size: 9pt;
|
|
line-height: 125%;
|
|
font-family: monospace, fixed;
|
|
font-size: 105%;
|
|
}
|
|
div.fragment {
|
|
padding: 0px;
|
|
margin: 4px 8px 4px 2px;
|
|
background-color: #FBFCFD;
|
|
border: 1px solid #C4CFE5;
|
|
}
|
|
div.line {
|
|
font-family: monospace, fixed;
|
|
font-size: 13px;
|
|
min-height: 13px;
|
|
line-height: 1.0;
|
|
text-wrap: unrestricted;
|
|
white-space: -moz-pre-wrap; /* Moz */
|
|
white-space: -pre-wrap; /* Opera 4-6 */
|
|
white-space: -o-pre-wrap; /* Opera 7 */
|
|
white-space: pre-wrap; /* CSS3 */
|
|
word-wrap: break-word; /* IE 5.5+ */
|
|
text-indent: -53px;
|
|
padding-left: 53px;
|
|
padding-bottom: 0px;
|
|
margin: 0px;
|
|
-webkit-transition-property: background-color, box-shadow;
|
|
-webkit-transition-duration: 0.5s;
|
|
-moz-transition-property: background-color, box-shadow;
|
|
-moz-transition-duration: 0.5s;
|
|
-ms-transition-property: background-color, box-shadow;
|
|
-ms-transition-duration: 0.5s;
|
|
-o-transition-property: background-color, box-shadow;
|
|
-o-transition-duration: 0.5s;
|
|
transition-property: background-color, box-shadow;
|
|
transition-duration: 0.5s;
|
|
}
|
|
div.contents {
|
|
margin-top: 10px;
|
|
margin-left: 12px;
|
|
margin-right: 8px;
|
|
}
|
|
div.center {
|
|
text-align: center;
|
|
margin-top: 0px;
|
|
margin-bottom: 0px;
|
|
padding: 0px;
|
|
}
|
|
div.center img {
|
|
border: 0px;
|
|
}
|
|
span.keyword {
|
|
color: #008000
|
|
}
|
|
span.keywordtype {
|
|
color: #604020
|
|
}
|
|
span.keywordflow {
|
|
color: #e08000
|
|
}
|
|
span.comment {
|
|
color: #800000
|
|
}
|
|
span.preprocessor {
|
|
color: #806020
|
|
}
|
|
span.stringliteral {
|
|
color: #002080
|
|
}
|
|
span.charliteral {
|
|
color: #008080
|
|
}
|
|
blockquote {
|
|
background-color: #F7F8FB;
|
|
border-left: 2px solid #9CAFD4;
|
|
margin: 0 24px 0 4px;
|
|
padding: 0 12px 0 16px;
|
|
}
|
|
hr {
|
|
height: 0px;
|
|
border: none;
|
|
border-top: 1px solid #4A6AAA;
|
|
}
|
|
address {
|
|
font-style: normal;
|
|
color: #2A3D61;
|
|
}
|
|
div.header {
|
|
background-image:url('nav_h.png');
|
|
background-repeat:repeat-x;
|
|
background-color: #F9FAFC;
|
|
margin: 0px;
|
|
border-bottom: 1px solid #C4CFE5;
|
|
}
|
|
div.headertitle {
|
|
padding: 5px 5px 5px 10px;
|
|
}
|
|
.image {
|
|
text-align: center;
|
|
}
|
|
.caption {
|
|
font-weight: bold;
|
|
}
|
|
div.zoom {
|
|
border: 1px solid #90A5CE;
|
|
}
|
|
tr.heading h2 {
|
|
margin-top: 12px;
|
|
margin-bottom: 4px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
<!--END TITLEAREA-->
|
|
<!-- end header part -->
|
|
<!-- Generated by Doxygen 1.8.13 -->
|
|
</div><!-- top -->
|
|
<div class="header">
|
|
<div class="headertitle">
|
|
<div class="title">Tutorial 12: Terrain Rendering </div> </div>
|
|
</div><!--header-->
|
|
<div class="contents">
|
|
<div class="textblock"><div class="image">
|
|
<img src="../../media/example_screenshots/012shot.jpg" alt="012shot.jpg"/>
|
|
</div>
|
|
<p>This tutorial will briefly show how to use the terrain renderer of Irrlicht. It will also show the terrain renderer triangle selector to be able to do collision detection with terrain.</p>
|
|
<p>Note that the Terrain Renderer in Irrlicht is based on Spintz' GeoMipMapSceneNode, lots of thanks go to him. DeusXL provided a new elegant simple solution for building larger area on small heightmaps -> terrain smoothing.</p>
|
|
<p>In the beginning there is nothing special. We include the needed header files and create an event listener to listen if the user presses certain keys. </p><div class="fragment"><div class="line"><span class="preprocessor">#include <irrlicht.h></span></div><div class="line"><span class="preprocessor">#include "driverChoice.h"</span></div><div class="line"><span class="preprocessor">#include "exampleHelper.h"</span></div><div class="line"></div><div class="line"><span class="keyword">using namespace </span>irr;</div><div class="line"></div><div class="line"><span class="preprocessor">#ifdef _MSC_VER</span></div><div class="line"><span class="preprocessor">#pragma comment(lib, "Irrlicht.lib")</span></div><div class="line"><span class="preprocessor">#endif</span></div><div class="line"></div><div class="line"></div><div class="line"><span class="keyword">class </span>MyEventReceiver : <span class="keyword">public</span> IEventReceiver</div><div class="line">{</div><div class="line"><span class="keyword">public</span>:</div><div class="line"></div><div class="line"> MyEventReceiver(scene::ISceneNode* terrain, scene::ISceneNode* skybox, scene::ISceneNode* skydome) :</div><div class="line"> Terrain(terrain), Skybox(skybox), Skydome(skydome), showBox(true), showDebug(false)</div><div class="line"> {</div><div class="line"> Skybox->setVisible(showBox);</div><div class="line"> Skydome->setVisible(!showBox);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keywordtype">bool</span> OnEvent(<span class="keyword">const</span> SEvent& event)</div><div class="line"> {</div><div class="line"> <span class="comment">// check if user presses the key 'W' or 'D'</span></div><div class="line"> <span class="keywordflow">if</span> (event.EventType == irr::EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown)</div><div class="line"> {</div><div class="line"> <span class="keywordflow">switch</span> (event.KeyInput.Key)</div><div class="line"> {</div><div class="line"> <span class="keywordflow">case</span> irr::KEY_KEY_W: <span class="comment">// switch wire frame mode</span></div><div class="line"> Terrain->setMaterialFlag(video::EMF_WIREFRAME,</div><div class="line"> !Terrain->getMaterial(0).Wireframe);</div><div class="line"> Terrain->setMaterialFlag(video::EMF_POINTCLOUD, <span class="keyword">false</span>);</div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">true</span>;</div><div class="line"> <span class="keywordflow">case</span> irr::KEY_KEY_P: <span class="comment">// switch point cloud mode</span></div><div class="line"> Terrain->setMaterialFlag(video::EMF_POINTCLOUD,</div><div class="line"> !Terrain->getMaterial(0).PointCloud);</div><div class="line"> Terrain->setMaterialFlag(video::EMF_WIREFRAME, <span class="keyword">false</span>);</div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">true</span>;</div><div class="line"> <span class="keywordflow">case</span> irr::KEY_KEY_D: <span class="comment">// toggle detail map</span></div><div class="line"> Terrain->setMaterialType(</div><div class="line"> Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?</div><div class="line"> video::EMT_DETAIL_MAP : video::EMT_SOLID);</div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">true</span>;</div><div class="line"> <span class="keywordflow">case</span> irr::KEY_KEY_S: <span class="comment">// toggle skies</span></div><div class="line"> showBox=!showBox;</div><div class="line"> Skybox->setVisible(showBox);</div><div class="line"> Skydome->setVisible(!showBox);</div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">true</span>;</div><div class="line"> <span class="keywordflow">case</span> irr::KEY_KEY_X: <span class="comment">// toggle debug information</span></div><div class="line"> showDebug=!showDebug;</div><div class="line"> Terrain->setDebugDataVisible(showDebug?scene::EDS_BBOX_ALL:scene::EDS_OFF);</div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">true</span>;</div><div class="line"> <span class="keywordflow">default</span>:</div><div class="line"> <span class="keywordflow">break</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"><span class="keyword">private</span>:</div><div class="line"> scene::ISceneNode* Terrain;</div><div class="line"> scene::ISceneNode* Skybox;</div><div class="line"> scene::ISceneNode* Skydome;</div><div class="line"> <span class="keywordtype">bool</span> showBox;</div><div class="line"> <span class="keywordtype">bool</span> showDebug;</div><div class="line">};</div></div><!-- fragment --><p> The start of the main function starts like in most other example. We ask the user for the desired renderer and start it up. This time with the advanced parameter handling. </p><div class="fragment"><div class="line"><span class="keywordtype">int</span> main()</div><div class="line">{</div><div class="line"> <span class="comment">// ask user for driver</span></div><div class="line"> video::E_DRIVER_TYPE driverType=driverChoiceConsole();</div><div class="line"> <span class="keywordflow">if</span> (driverType==video::EDT_COUNT)</div><div class="line"> <span class="keywordflow">return</span> 1;</div><div class="line"></div><div class="line"> <span class="comment">// create device with full flexibility over creation parameters</span></div><div class="line"> <span class="comment">// you can add more parameters if desired, check irr::SIrrlichtCreationParameters</span></div><div class="line"> irr::SIrrlichtCreationParameters params;</div><div class="line"> params.DriverType=driverType;</div><div class="line"> params.WindowSize=core::dimension2d<u32>(640, 480);</div><div class="line"> IrrlichtDevice* device = createDeviceEx(params);</div><div class="line"></div><div class="line"> <span class="keywordflow">if</span> (device == 0)</div><div class="line"> <span class="keywordflow">return</span> 1; <span class="comment">// could not create selected driver.</span></div></div><!-- fragment --><p> First, we add standard stuff to the scene: A nice irrlicht engine logo, a small help text, a user controlled camera, and we disable the mouse cursor. </p><div class="fragment"><div class="line">video::IVideoDriver* driver = device->getVideoDriver();</div><div class="line">scene::ISceneManager* smgr = device->getSceneManager();</div><div class="line">gui::IGUIEnvironment* env = device->getGUIEnvironment();</div><div class="line"></div><div class="line">driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, <span class="keyword">true</span>);</div><div class="line"></div><div class="line"><span class="keyword">const</span> io::path mediaPath = getExampleMediaPath();</div><div class="line"></div><div class="line"><span class="comment">// add irrlicht logo</span></div><div class="line">env->addImage(driver->getTexture(mediaPath + <span class="stringliteral">"irrlichtlogo3.png"</span>),</div><div class="line"> core::position2d<s32>(10,10));</div><div class="line"></div><div class="line"><span class="comment">//set other font</span></div><div class="line">env->getSkin()->setFont(env->getFont(mediaPath + <span class="stringliteral">"fontlucida.png"</span>));</div><div class="line"></div><div class="line"><span class="comment">// add some help text (let's ignore 'P' and 'X' which are more about debugging)</span></div><div class="line">env->addStaticText(</div><div class="line"> L<span class="stringliteral">"Press 'W' to change wireframe mode\nPress 'D' to toggle detail map\nPress 'S' to toggle skybox/skydome"</span>,</div><div class="line"> core::rect<s32>(10,421,250,475), <span class="keyword">true</span>, <span class="keyword">true</span>, 0, -1, <span class="keyword">true</span>);</div><div class="line"></div><div class="line"><span class="comment">// add camera</span></div><div class="line">scene::ICameraSceneNode* camera =</div><div class="line"> smgr->addCameraSceneNodeFPS(0,100.0f,1.2f);</div><div class="line"></div><div class="line">camera->setPosition(core::vector3df(2700*2,255*2,2600*2));</div><div class="line">camera->setTarget(core::vector3df(2397*2,343*2,2700*2));</div><div class="line">camera->setFarValue(42000.0f);</div><div class="line"></div><div class="line"><span class="comment">// disable mouse cursor</span></div><div class="line">device->getCursorControl()->setVisible(<span class="keyword">false</span>);</div></div><!-- fragment --><p> Here comes the terrain renderer scene node: We add it just like any other scene node to the scene using ISceneManager::addTerrainSceneNode(). The first parameter is a file name to the heightmap we use. A heightmap is simply a gray scale texture. The terrain renderer loads it and creates the 3D terrain from it.</p>
|
|
<p>To make the terrain look bigger, we change it's scale factor to (40, 4.4, 40). Because we don't have any dynamic lights in the scene, we switch off the lighting, and we set the file terrain-texture.jpg as texture for the terrain and detailmap3.jpg as second texture, called detail map. At last, we set the scale values for the texture: The first texture will be repeated only one time over the whole terrain, and the second one (detail map) 20 times. </p><div class="fragment"><div class="line"><span class="comment">// add terrain scene node</span></div><div class="line">scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(</div><div class="line"> mediaPath + <span class="stringliteral">"terrain-heightmap.bmp"</span>,</div><div class="line"> 0, <span class="comment">// parent node</span></div><div class="line"> -1, <span class="comment">// node id</span></div><div class="line"> core::vector3df(0.f, 0.f, 0.f), <span class="comment">// position</span></div><div class="line"> core::vector3df(0.f, 0.f, 0.f), <span class="comment">// rotation</span></div><div class="line"> core::vector3df(40.f, 4.4f, 40.f), <span class="comment">// scale</span></div><div class="line"> video::SColor ( 255, 255, 255, 255 ), <span class="comment">// vertexColor</span></div><div class="line"> 5, <span class="comment">// maxLOD</span></div><div class="line"> scene::ETPS_17, <span class="comment">// patchSize</span></div><div class="line"> 4 <span class="comment">// smoothFactor</span></div><div class="line"> );</div><div class="line"></div><div class="line">terrain->setMaterialFlag(video::EMF_LIGHTING, <span class="keyword">false</span>);</div><div class="line"></div><div class="line">terrain->setMaterialTexture(0,</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"terrain-texture.jpg"</span>));</div><div class="line">terrain->setMaterialTexture(1,</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"detailmap3.jpg"</span>));</div><div class="line"></div><div class="line">terrain->setMaterialType(video::EMT_DETAIL_MAP);</div><div class="line"></div><div class="line">terrain->scaleTexture(1.0f, 20.0f);</div></div><!-- fragment --><p> To be able to do collision with the terrain, we create a triangle selector. If you want to know what triangle selectors do, just take a look into the collision tutorial. The terrain triangle selector works together with the terrain. To demonstrate this, we create a collision response animator and attach it to the camera, so that the camera will not be able to fly through the terrain. </p><div class="fragment"><div class="line"><span class="comment">// create triangle selector for the terrain </span></div><div class="line">scene::ITriangleSelector* selector</div><div class="line"> = smgr->createTerrainTriangleSelector(terrain, 0);</div><div class="line">terrain->setTriangleSelector(selector);</div><div class="line"></div><div class="line"><span class="comment">// create collision response animator and attach it to the camera</span></div><div class="line">scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(</div><div class="line"> selector, camera, core::vector3df(60,100,60),</div><div class="line"> core::vector3df(0,0,0),</div><div class="line"> core::vector3df(0,50,0));</div><div class="line">selector->drop();</div><div class="line">camera->addAnimator(anim);</div><div class="line">anim->drop();</div></div><!-- fragment --><p> If you need access to the terrain data you can also do this directly via the following code fragment. </p><div class="fragment"><div class="line">scene::CDynamicMeshBuffer* buffer = <span class="keyword">new</span> scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);</div><div class="line">terrain->getMeshBufferForLOD(*buffer, 0);</div><div class="line">video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();</div><div class="line"><span class="comment">// Work on data or get the IndexBuffer with a similar call.</span></div><div class="line">(void)data; <span class="comment">// disable unused variable warnings</span></div><div class="line">buffer->drop(); <span class="comment">// When done drop the buffer again.</span></div></div><!-- fragment --><p> To make the user be able to switch between normal and wireframe mode, we create an instance of the event receiver from above and let Irrlicht know about it. In addition, we add the skybox which we already used in lots of Irrlicht examples and a skydome, which is shown mutually exclusive with the skybox by pressing 'S'. </p><div class="fragment"><div class="line"><span class="comment">// create skybox and skydome</span></div><div class="line">driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, <span class="keyword">false</span>);</div><div class="line"></div><div class="line">scene::ISceneNode* skybox=smgr->addSkyBoxSceneNode(</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"irrlicht2_up.jpg"</span>),</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"irrlicht2_dn.jpg"</span>),</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"irrlicht2_lf.jpg"</span>),</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"irrlicht2_rt.jpg"</span>),</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"irrlicht2_ft.jpg"</span>),</div><div class="line"> driver->getTexture(mediaPath + <span class="stringliteral">"irrlicht2_bk.jpg"</span>));</div><div class="line">scene::ISceneNode* skydome=smgr->addSkyDomeSceneNode(driver->getTexture(mediaPath + <span class="stringliteral">"skydome.jpg"</span>),16,8,0.95f,2.0f);</div><div class="line"></div><div class="line">driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, <span class="keyword">true</span>);</div><div class="line"></div><div class="line"><span class="comment">// create event receiver</span></div><div class="line">MyEventReceiver receiver(terrain, skybox, skydome);</div><div class="line">device->setEventReceiver(&receiver);</div></div><!-- fragment --><p> That's it, draw everything. </p><div class="fragment"><div class="line"> <span class="keywordtype">int</span> lastFPS = -1;</div><div class="line"></div><div class="line"> <span class="keywordflow">while</span>(device->run())</div><div class="line"> <span class="keywordflow">if</span> (device->isWindowActive())</div><div class="line"> {</div><div class="line"> driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));</div><div class="line"></div><div class="line"> smgr->drawAll();</div><div class="line"> env->drawAll();</div><div class="line"></div><div class="line"> driver->endScene();</div><div class="line"></div><div class="line"> <span class="comment">// display frames per second in window title</span></div><div class="line"> <span class="keywordtype">int</span> fps = driver->getFPS();</div><div class="line"> <span class="keywordflow">if</span> (lastFPS != fps)</div><div class="line"> {</div><div class="line"> core::stringw str = L<span class="stringliteral">"Terrain Renderer - Irrlicht Engine ["</span>;</div><div class="line"> str += driver->getName();</div><div class="line"> str += <span class="stringliteral">"] FPS:"</span>;</div><div class="line"> str += fps;</div><div class="line"> <span class="comment">// Also print terrain height of current camera position</span></div><div class="line"> <span class="comment">// We can use camera position because terrain is located at coordinate origin</span></div><div class="line"> str += <span class="stringliteral">" Height: "</span>;</div><div class="line"> str += terrain->getHeight(camera->getAbsolutePosition().X,</div><div class="line"> camera->getAbsolutePosition().Z);</div><div class="line"></div><div class="line"> device->setWindowCaption(str.c_str());</div><div class="line"> lastFPS = fps;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> device->drop();</div><div class="line"> </div><div class="line"> <span class="keywordflow">return</span> 0;</div><div class="line">}</div></div><!-- fragment --><p> Now you know how to use terrain in Irrlicht. </p>
|
|
</div></div><!-- contents -->
|
|
<!-- HTML footer for doxygen 1.8.13-->
|
|
<!-- start footer part -->
|
|
<p> </p>
|
|
</body>
|
|
</html>
|