aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/others/irrlicht-1.8.1/source/Irrlicht/CQuake3ShaderSceneNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/others/irrlicht-1.8.1/source/Irrlicht/CQuake3ShaderSceneNode.cpp')
-rw-r--r--src/others/irrlicht-1.8.1/source/Irrlicht/CQuake3ShaderSceneNode.cpp1376
1 files changed, 1376 insertions, 0 deletions
diff --git a/src/others/irrlicht-1.8.1/source/Irrlicht/CQuake3ShaderSceneNode.cpp b/src/others/irrlicht-1.8.1/source/Irrlicht/CQuake3ShaderSceneNode.cpp
new file mode 100644
index 0000000..9219782
--- /dev/null
+++ b/src/others/irrlicht-1.8.1/source/Irrlicht/CQuake3ShaderSceneNode.cpp
@@ -0,0 +1,1376 @@
1// Copyright (C) 2002-2012 Thomas Alten / Nikolaus Gebhardt
2// This file is part of the "Irrlicht Engine".
3// For conditions of distribution and use, see copyright notice in irrlicht.h
4
5#include "IrrCompileConfig.h"
6
7#ifdef _IRR_COMPILE_WITH_BSP_LOADER_
8
9#include "CQuake3ShaderSceneNode.h"
10#include "ISceneManager.h"
11#include "IVideoDriver.h"
12#include "ICameraSceneNode.h"
13#include "SViewFrustum.h"
14#include "IMeshManipulator.h"
15#include "SMesh.h"
16#include "IMaterialRenderer.h"
17#include "CShadowVolumeSceneNode.h"
18
19namespace irr
20{
21namespace scene
22{
23
24// who, if not you..
25using namespace quake3;
26
27/*!
28*/
29CQuake3ShaderSceneNode::CQuake3ShaderSceneNode(
30 scene::ISceneNode* parent, scene::ISceneManager* mgr,s32 id,
31 io::IFileSystem *fileSystem, const scene::IMeshBuffer *original,
32 const IShader * shader)
33: scene::IMeshSceneNode(parent, mgr, id,
34 core::vector3df(0.f, 0.f, 0.f),
35 core::vector3df(0.f, 0.f, 0.f),
36 core::vector3df(1.f, 1.f, 1.f)),
37 Shader(shader), Mesh(0), Shadow(0), Original(0), MeshBuffer(0), TimeAbs(0.f)
38{
39 #ifdef _DEBUG
40 core::stringc dName = "CQuake3ShaderSceneNode ";
41 dName += Shader->name;
42
43 setDebugName( dName.c_str() );
44 #endif
45
46 // name the Scene Node
47 this->Name = Shader->name;
48
49 // take lightmap vertex type
50 MeshBuffer = new SMeshBuffer();
51
52 Mesh = new SMesh ();
53 Mesh->addMeshBuffer ( MeshBuffer );
54 MeshBuffer->drop ();
55
56 //Original = new SMeshBufferLightMap();
57 Original = (const scene::SMeshBufferLightMap*) original;
58 Original->grab();
59
60 // clone meshbuffer to modifiable buffer
61 cloneBuffer(MeshBuffer, Original,
62 Original->getMaterial().ColorMask != 0);
63
64 // load all Textures in all stages
65 loadTextures( fileSystem );
66
67 setAutomaticCulling( scene::EAC_OFF );
68}
69
70
71/*!
72*/
73CQuake3ShaderSceneNode::~CQuake3ShaderSceneNode()
74{
75 if (Shadow)
76 Shadow->drop();
77
78 if (Mesh)
79 Mesh->drop();
80
81 if (Original)
82 Original->drop();
83}
84
85
86
87/*
88 create single copies
89*/
90void CQuake3ShaderSceneNode::cloneBuffer( scene::SMeshBuffer *dest, const scene::SMeshBufferLightMap * buffer, bool translateCenter )
91{
92 dest->Material = buffer->Material;
93 dest->Indices = buffer->Indices;
94
95 const u32 vsize = buffer->Vertices.size();
96
97 dest->Vertices.set_used( vsize );
98 for ( u32 i = 0; i!= vsize; ++i )
99 {
100 const video::S3DVertex2TCoords& src = buffer->Vertices[i];
101 video::S3DVertex &dst = dest->Vertices[i];
102
103 dst.Pos = src.Pos;
104 dst.Normal = src.Normal;
105 dst.Color = 0xFFFFFFFF;
106 dst.TCoords = src.TCoords;
107
108 if ( i == 0 )
109 dest->BoundingBox.reset ( src.Pos );
110 else
111 dest->BoundingBox.addInternalPoint ( src.Pos );
112 }
113
114 // move the (temp) Mesh to a ScenePosititon
115 // set Scene Node Position
116
117 if ( translateCenter )
118 {
119 MeshOffset = dest->BoundingBox.getCenter();
120 setPosition( MeshOffset );
121
122 core::matrix4 m;
123 m.setTranslation( -MeshOffset );
124 SceneManager->getMeshManipulator()->transform( dest, m );
125 }
126
127 // No Texture!. Use Shader-Pointer for sorting
128 dest->Material.setTexture(0, (video::ITexture*) Shader);
129}
130
131
132/*
133 load the textures for all stages
134*/
135void CQuake3ShaderSceneNode::loadTextures( io::IFileSystem * fileSystem )
136{
137 const SVarGroup *group;
138 u32 i;
139
140 video::IVideoDriver *driver = SceneManager->getVideoDriver();
141
142 // generic stage
143 u32 mipmap = 0;
144 group = Shader->getGroup( 1 );
145 if ( group->isDefined ( "nomipmaps" ) )
146 {
147 mipmap = 2 | (driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS)? 1: 0 );
148 driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
149 }
150
151 // clear all stages and prefill empty
152 Q3Texture.setAllocStrategy ( core::ALLOC_STRATEGY_SAFE );
153 Q3Texture.clear();
154 for ( i = 0; i != Shader->VarGroup->VariableGroup.size(); ++i )
155 {
156 Q3Texture.push_back( SQ3Texture() );
157 }
158
159 u32 pos;
160
161 // get texture map
162 for ( i = 0; i < Shader->VarGroup->VariableGroup.size(); ++i )
163 {
164 group = Shader->getGroup( i );
165
166 const core::stringc &mapname = group->get( "map" );
167 if ( 0 == mapname.size() )
168 continue;
169
170 // our lightmap is passed in material.Texture[2]
171 if ( mapname == "$lightmap" )
172 {
173 Q3Texture [i].Texture.push_back( Original->getMaterial().getTexture(1) );
174 }
175 else
176 {
177 pos = 0;
178 getTextures( Q3Texture [i].Texture, mapname, pos, fileSystem, driver );
179 }
180 }
181
182 // get anim map
183 for ( i = 0; i < Shader->VarGroup->VariableGroup.size(); ++i )
184 {
185 if ( Q3Texture [i].Texture.size() )
186 continue;
187
188 group = Shader->getGroup( i );
189
190 const core::stringc &animmap = group->get( "animmap" );
191 if ( 0 == animmap.size() )
192 continue;
193
194 // first parameter is frequency
195 pos = 0;
196 Q3Texture [i].TextureFrequency = core::max_( 0.0001f, getAsFloat( animmap, pos ) );
197
198 getTextures( Q3Texture [i].Texture, animmap, pos,fileSystem, driver );
199 }
200
201 // get clamp map
202 for ( i = 0; i < Shader->VarGroup->VariableGroup.size(); ++i )
203 {
204 if ( Q3Texture [i].Texture.size() )
205 continue;
206
207 group = Shader->getGroup( i );
208
209 const core::stringc &clampmap = group->get( "clampmap" );
210 if ( 0 == clampmap.size() )
211 continue;
212
213 Q3Texture [i].TextureAddressMode = video::ETC_CLAMP_TO_EDGE;
214 pos = 0;
215 getTextures( Q3Texture [i].Texture, clampmap, pos,fileSystem, driver );
216 }
217
218 if ( mipmap & 2 )
219 driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, mipmap & 1);
220}
221
222/*
223 Register each texture stage, if first is visible
224*/
225void CQuake3ShaderSceneNode::OnRegisterSceneNode()
226{
227 if ( isVisible() )
228 {
229 SceneManager->registerNodeForRendering(this, getRenderStage() );
230 }
231 ISceneNode::OnRegisterSceneNode();
232}
233
234/*
235 is this a transparent node ?
236*/
237E_SCENE_NODE_RENDER_PASS CQuake3ShaderSceneNode::getRenderStage() const
238{
239 E_SCENE_NODE_RENDER_PASS ret = ESNRP_SOLID;
240
241 // generic stage
242 const SVarGroup *group;
243
244 group = Shader->getGroup( 1 );
245/*
246 else
247 if ( group->getIndex( "portal" ) >= 0 )
248 {
249 ret = ESNRP_TRANSPARENT_EFFECT;
250 }
251 else
252*/
253 if ( group->isDefined( "sort", "opaque" ) )
254 {
255 ret = ESNRP_SOLID;
256 }
257 else
258 if ( group->isDefined( "sort", "additive" ) )
259 {
260 ret = ESNRP_TRANSPARENT;
261 }
262 else
263 if ( strstr ( Shader->name.c_str(), "flame" ) ||
264 group->isDefined( "surfaceparm", "water" ) ||
265 group->isDefined( "sort", "underwater" ) ||
266 group->isDefined( "sort", "underwater" )
267 )
268 {
269 ret = ESNRP_TRANSPARENT_EFFECT;
270 }
271 else
272 {
273 // Look if first drawing stage needs graphical underlay
274 for ( u32 stage = 2; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
275 {
276 if ( 0 == Q3Texture [ stage ].Texture.size() )
277 continue;
278
279 group = Shader->getGroup( stage );
280
281 SBlendFunc blendfunc ( video::EMFN_MODULATE_1X );
282 getBlendFunc( group->get( "blendfunc" ), blendfunc );
283 getBlendFunc( group->get( "alphafunc" ), blendfunc );
284
285 //ret = blendfunc.isTransparent ? ESNRP_TRANSPARENT : ESNRP_SOLID;
286 if ( blendfunc.isTransparent )
287 {
288 ret = ESNRP_TRANSPARENT;
289 }
290 break;
291 }
292 }
293
294 return ret;
295}
296
297
298/*
299 render in multipass technique
300*/
301void CQuake3ShaderSceneNode::render()
302{
303 video::IVideoDriver* driver = SceneManager->getVideoDriver();
304 E_SCENE_NODE_RENDER_PASS pass = SceneManager->getSceneNodeRenderPass();
305
306 video::SMaterial material;
307 const SVarGroup *group;
308
309 material.Lighting = false;
310 material.setTexture(1, 0);
311 material.NormalizeNormals = false;
312
313 // generic stage
314 group = Shader->getGroup( 1 );
315 material.BackfaceCulling = getCullingFunction( group->get( "cull" ) );
316
317 u32 pushProjection = 0;
318 core::matrix4 projection ( core::matrix4::EM4CONST_NOTHING );
319
320 // decal ( solve z-fighting )
321 if ( group->isDefined( "polygonoffset" ) )
322 {
323 projection = driver->getTransform( video::ETS_PROJECTION );
324
325 core::matrix4 decalProjection ( projection );
326
327/*
328 f32 n = SceneManager->getActiveCamera()->getNearValue();
329 f32 f = SceneManager->getActiveCamera()->getFarValue ();
330
331 f32 delta = 0.01f;
332 f32 pz = 0.2f;
333 f32 epsilon = -2.f * f * n * delta / ( ( f + n ) * pz * ( pz + delta ) );
334 decalProjection[10] *= 1.f + epsilon;
335*/
336 // TODO: involve camera
337 decalProjection[10] -= 0.0002f;
338 driver->setTransform( video::ETS_PROJECTION, decalProjection );
339 pushProjection |= 1;
340 }
341
342 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation );
343 if (Shadow)
344 Shadow->updateShadowVolumes();
345
346 //! render all stages
347 u32 drawCount = (pass == ESNRP_TRANSPARENT_EFFECT) ? 1 : 0;
348 core::matrix4 textureMatrix ( core::matrix4::EM4CONST_NOTHING );
349 for ( u32 stage = 1; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
350 {
351 SQ3Texture &q = Q3Texture[stage];
352
353 // advance current stage
354 textureMatrix.makeIdentity();
355 animate( stage, textureMatrix );
356
357 // stage finished, no drawing stage ( vertex transform only )
358 video::ITexture * tex = q.Texture.size() ? q.Texture [ q.TextureIndex ] : 0;
359 if ( 0 == tex )
360 continue;
361
362 // current stage
363 group = Shader->getGroup( stage );
364
365 material.setTexture(0, tex );
366 material.ZBuffer = getDepthFunction( group->get( "depthfunc" ) );
367
368 if ( group->isDefined( "depthwrite" ) )
369 {
370 material.ZWriteEnable = true;
371 }
372 else
373 {
374 material.ZWriteEnable = drawCount == 0;
375 }
376
377 //resolve quake3 blendfunction to irrlicht Material Type
378 SBlendFunc blendfunc ( video::EMFN_MODULATE_1X );
379 getBlendFunc( group->get( "blendfunc" ), blendfunc );
380 getBlendFunc( group->get( "alphafunc" ), blendfunc );
381
382 material.MaterialType = blendfunc.type;
383 material.MaterialTypeParam = blendfunc.param0;
384
385 material.TextureLayer[0].TextureWrapU = q.TextureAddressMode;
386 material.TextureLayer[0].TextureWrapV = q.TextureAddressMode;
387 //material.TextureLayer[0].TrilinearFilter = 1;
388 //material.TextureLayer[0].AnisotropicFilter = 0xFF;
389 material.setTextureMatrix( 0, textureMatrix );
390
391 driver->setMaterial( material );
392 driver->drawMeshBuffer( MeshBuffer );
393 drawCount += 1;
394
395 }
396
397 if ( DebugDataVisible & scene::EDS_MESH_WIRE_OVERLAY )
398 {
399 video::SMaterial deb_m;
400 deb_m.Wireframe = true;
401 deb_m.Lighting = false;
402 deb_m.BackfaceCulling = material.BackfaceCulling;
403 driver->setMaterial( deb_m );
404
405 driver->drawMeshBuffer( MeshBuffer );
406 }
407
408 // show normals
409 if ( DebugDataVisible & scene::EDS_NORMALS )
410 {
411 video::SMaterial deb_m;
412
413 IAnimatedMesh * arrow = SceneManager->addArrowMesh (
414 "__debugnormalq3",
415 0xFFECEC00,0xFF999900,
416 4, 8,
417 8.f, 6.f,
418 0.5f,1.f
419 );
420 if ( 0 == arrow )
421 {
422 arrow = SceneManager->getMesh ( "__debugnormalq3" );
423 }
424 const IMesh *mesh = arrow->getMesh ( 0 );
425
426 // find a good scaling factor
427
428 core::matrix4 m2;
429
430 // draw normals
431 const scene::IMeshBuffer* mb = MeshBuffer;
432 const u32 vSize = video::getVertexPitchFromType(mb->getVertexType());
433 const video::S3DVertex* v = ( const video::S3DVertex*)mb->getVertices();
434
435 //f32 colCycle = 270.f / (f32) core::s32_max ( mb->getVertexCount() - 1, 1 );
436
437 for ( u32 i=0; i != mb->getVertexCount(); ++i )
438 {
439 // Align to v->normal
440 m2.buildRotateFromTo ( core::vector3df ( 0.f, 1.f, 0 ), v->Normal );
441 m2.setTranslation ( v->Pos + AbsoluteTransformation.getTranslation () );
442/*
443 core::quaternion quatRot( v->Normal.Z, 0.f, -v->Normal.X, 1 + v->Normal.Y );
444 quatRot.normalize();
445 quatRot.getMatrix ( m2, v->Pos );
446
447 m2 [ 12 ] += AbsoluteTransformation [ 12 ];
448 m2 [ 13 ] += AbsoluteTransformation [ 13 ];
449 m2 [ 14 ] += AbsoluteTransformation [ 14 ];
450*/
451 driver->setTransform(video::ETS_WORLD, m2 );
452
453 deb_m.Lighting = true;
454/*
455 irr::video::SColorHSL color;
456 irr::video::SColor rgb(0);
457 color.Hue = i * colCycle * core::DEGTORAD;
458 color.Saturation = 1.f;
459 color.Luminance = 0.5f;
460 color.toRGB( deb_m.EmissiveColor );
461*/
462 switch ( i )
463 {
464 case 0: deb_m.EmissiveColor.set(0xFFFFFFFF); break;
465 case 1: deb_m.EmissiveColor.set(0xFFFF0000); break;
466 case 2: deb_m.EmissiveColor.set(0xFF00FF00); break;
467 case 3: deb_m.EmissiveColor.set(0xFF0000FF); break;
468 default:
469 deb_m.EmissiveColor = v->Color; break;
470 }
471 driver->setMaterial( deb_m );
472
473 for ( u32 a = 0; a != mesh->getMeshBufferCount(); ++a )
474 driver->drawMeshBuffer ( mesh->getMeshBuffer ( a ) );
475
476 v = (const video::S3DVertex*) ( (u8*) v + vSize );
477 }
478 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
479 }
480
481
482 if ( pushProjection & 1 )
483 {
484 driver->setTransform( video::ETS_PROJECTION, projection );
485 }
486
487 if ( DebugDataVisible & scene::EDS_BBOX )
488 {
489 video::SMaterial deb_m;
490 deb_m.Lighting = false;
491 driver->setMaterial(deb_m);
492 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
493 driver->draw3DBox( getBoundingBox(), video::SColor(255,255,0,0));
494 }
495
496}
497
498
499//! Removes a child from this scene node.
500//! Implemented here, to be able to remove the shadow properly, if there is one,
501//! or to remove attached childs.
502bool CQuake3ShaderSceneNode::removeChild(ISceneNode* child)
503{
504 if (child && Shadow == child)
505 {
506 Shadow->drop();
507 Shadow = 0;
508 }
509
510 return ISceneNode::removeChild(child);
511}
512
513
514//! Creates shadow volume scene node as child of this node
515//! and returns a pointer to it.
516IShadowVolumeSceneNode* CQuake3ShaderSceneNode::addShadowVolumeSceneNode(
517 const IMesh* shadowMesh, s32 id, bool zfailmethod, f32 infinity)
518{
519 if (!SceneManager->getVideoDriver()->queryFeature(video::EVDF_STENCIL_BUFFER))
520 return 0;
521
522 if (!shadowMesh)
523 shadowMesh = Mesh; // if null is given, use the mesh of node
524
525 if (Shadow)
526 Shadow->drop();
527
528 Shadow = new CShadowVolumeSceneNode(shadowMesh, this, SceneManager, id, zfailmethod, infinity);
529 return Shadow;
530}
531
532
533/*!
5343.3.1 deformVertexes wave <div> <func> <base> <amplitude> <phase> <freq>
535 Designed for water surfaces, modifying the values differently at each point.
536 It accepts the standard wave functions of the type sin, triangle, square, sawtooth
537 or inversesawtooth. The "div" parameter is used to control the wave "spread"
538 - a value equal to the tessSize of the surface is a good default value
539 (tessSize is subdivision size, in game units, used for the shader when seen in the game world) .
540*/
541void CQuake3ShaderSceneNode::deformvertexes_wave( f32 dt, SModifierFunction &function )
542{
543 function.wave = core::reciprocal( function.wave );
544
545 const f32 phase = function.phase;
546
547 const u32 vsize = Original->Vertices.size();
548 for ( u32 i = 0; i != vsize; ++i )
549 {
550 const video::S3DVertex2TCoords &src = Original->Vertices[i];
551 video::S3DVertex &dst = MeshBuffer->Vertices[i];
552
553 if ( 0 == function.count )
554 dst.Pos = src.Pos - MeshOffset;
555
556 const f32 wavephase = (dst.Pos.X + dst.Pos.Y + dst.Pos.Z) * function.wave;
557 function.phase = phase + wavephase;
558
559 const f32 f = function.evaluate( dt );
560
561 dst.Pos.X += f * src.Normal.X;
562 dst.Pos.Y += f * src.Normal.Y;
563 dst.Pos.Z += f * src.Normal.Z;
564
565 if ( i == 0 )
566 MeshBuffer->BoundingBox.reset ( dst.Pos );
567 else
568 MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
569 }
570 function.count = 1;
571}
572
573/*!
574 deformVertexes move x y z func base amplitude phase freq
575 The move parameter is used to make a brush, curve patch or model
576 appear to move together as a unit. The x y z values are the distance
577 and direction in game units the object appears to move relative to
578 it's point of origin in the map. The func base amplitude phase freq values are
579 the same as found in other waveform manipulations.
580
581 The product of the function modifies the values x, y, and z.
582 Therefore, if you have an amplitude of 5 and an x value of 2,
583 the object will travel 10 units from its point of origin along the x axis.
584 This results in a total of 20 units of motion along the x axis, since the
585 amplitude is the variation both above and below the base.
586
587 It must be noted that an object made with this shader does not actually
588 change position, it only appears to.
589
590 Design Notes:
591 If an object is made up of surfaces with different shaders, all must have
592 matching deformVertexes move values or the object will appear to tear itself apart.
593*/
594void CQuake3ShaderSceneNode::deformvertexes_move( f32 dt, SModifierFunction &function )
595{
596 function.wave = core::reciprocal( function.wave );
597 const f32 f = function.evaluate( dt );
598
599 const u32 vsize = Original->Vertices.size();
600 for ( u32 i = 0; i != vsize; ++i )
601 {
602 const video::S3DVertex2TCoords &src = Original->Vertices[i];
603 video::S3DVertex &dst = MeshBuffer->Vertices[i];
604
605 if ( 0 == function.count )
606 dst.Pos = src.Pos - MeshOffset;
607
608 dst.Pos.X += f * function.x;
609 dst.Pos.Y += f * function.y;
610 dst.Pos.Z += f * function.z;
611
612 if ( i == 0 )
613 MeshBuffer->BoundingBox.reset ( dst.Pos );
614 else
615 MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
616 }
617 function.count = 1;
618
619}
620
621/*!
622 3.3.2 deformVertexes normal <div> <func> <base> <amplitude ~0.1-~0.5> <frequency ~1.0-~4.0>
623 This deformation affects the normals of a vertex without actually moving it,
624 which will effect later shader options like lighting and especially environment mapping.
625 If the shader stages don't use normals in any of their calculations, there will
626 be no visible effect.
627
628 Design Notes: Putting values of 0.1 t o 0.5 in Amplitude and 1.0 to 4.0 in the
629 Frequency can produce some satisfying results. Some things that have been
630 done with it: A small fluttering bat, falling leaves, rain, flags.
631*/
632void CQuake3ShaderSceneNode::deformvertexes_normal( f32 dt, SModifierFunction &function )
633{
634 function.func = SINUS;
635 const u32 vsize = Original->Vertices.size();
636 for ( u32 i = 0; i != vsize; ++i )
637 {
638 const video::S3DVertex2TCoords &src = Original->Vertices[i];
639 video::S3DVertex &dst = MeshBuffer->Vertices[i];
640
641 function.base = atan2f ( src.Pos.X, src.Pos.Y );
642 function.phase = src.Pos.X + src.Pos.Z;
643
644 const f32 lat = function.evaluate( dt );
645
646 function.base = src.Normal.Y;
647 function.phase = src.Normal.Z + src.Normal.X;
648
649 const f32 lng = function.evaluate( dt );
650
651 dst.Normal.X = cosf ( lat ) * sinf ( lng );
652 dst.Normal.Y = sinf ( lat ) * sinf ( lng );
653 dst.Normal.Z = cosf ( lng );
654
655 }
656}
657
658
659/*!
660 3.3.3 deformVertexes bulge <bulgeWidth> <bulgeHeight> <bulgeSpeed>
661 This forces a bulge to move along the given s and t directions. Designed for use
662 on curved pipes.
663
664 Specific parameter definitions for deform keywords:
665 <div> This is roughly defined as the size of the waves that occur.
666 It is measured in game units. Smaller values create a greater
667 density of smaller wave forms occurring in a given area.
668 Larger values create a lesser density of waves, or otherwise put,
669 the appearance of larger waves. To look correct this value should
670 closely correspond to the value (in pixels) set for tessSize (tessellation size)
671 of the texture. A value of 100.0 is a good default value
672 (which means your tessSize should be close to that for things to look "wavelike").
673
674 <func> This is the type of wave form being created. Sin stands for sine wave,
675 a regular smoothly flowing wave. Triangle is a wave with a sharp ascent
676 and a sharp decay. It will make a choppy looking wave forms.
677 A square wave is simply on or off for the period of the
678 frequency with no in between. The sawtooth wave has the ascent of a
679 triangle wave, but has the decay cut off sharply like a square wave.
680 An inversesawtooth wave reverses this.
681
682 <base> This is the distance, in game units that the apparent surface of the
683 texture is displaced from the actual surface of the brush as placed
684 in the editor. A positive value appears above the brush surface.
685 A negative value appears below the brush surface.
686 An example of this is the Quad effect, which essentially is a
687 shell with a positive base value to stand it away from the model
688 surface and a 0 (zero) value for amplitude.
689
690 <amplitude> The distance that the deformation moves away from the base value.
691 See Wave Forms in the introduction for a description of amplitude.
692
693 <phase> See Wave Forms in the introduction for a description of phase)
694
695 <frequency> See Wave Forms in the introduction for a description of frequency)
696
697 Design Note: The div and amplitude parameters, when used in conjunction with
698 liquid volumes like water should take into consideration how much the water
699 will be moving. A large ocean area would have have massive swells (big div values)
700 that rose and fell dramatically (big amplitude values). While a small, quiet pool
701 may move very little.
702*/
703void CQuake3ShaderSceneNode::deformvertexes_bulge( f32 dt, SModifierFunction &function )
704{
705 function.func = SINUS;
706 function.wave = core::reciprocal( function.bulgewidth );
707
708 dt *= function.bulgespeed * 0.1f;
709 const f32 phase = function.phase;
710
711 const u32 vsize = Original->Vertices.size();
712 for ( u32 i = 0; i != vsize; ++i )
713 {
714 const video::S3DVertex2TCoords &src = Original->Vertices[i];
715 video::S3DVertex &dst = MeshBuffer->Vertices[i];
716
717 const f32 wavephase = (Original->Vertices[i].TCoords.X ) * function.wave;
718 function.phase = phase + wavephase;
719
720 const f32 f = function.evaluate( dt );
721
722 if ( 0 == function.count )
723 dst.Pos = src.Pos - MeshOffset;
724
725 dst.Pos.X += f * src.Normal.X;
726 dst.Pos.Y += f * src.Normal.Y;
727 dst.Pos.Z += f * src.Normal.Z;
728
729 if ( i == 0 )
730 MeshBuffer->BoundingBox.reset ( dst.Pos );
731 else
732 MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
733 }
734
735 function.count = 1;
736}
737
738
739/*!
740 deformVertexes autosprite
741
742 This function can be used to make any given triangle quad
743 (pair of triangles that form a square rectangle) automatically behave
744 like a sprite without having to make it a separate entity. This means
745 that the "sprite" on which the texture is placed will rotate to always
746 appear at right angles to the player's view as a sprite would. Any four-sided
747 brush side, flat patch, or pair of triangles in a model can have the autosprite
748 effect on it. The brush face containing a texture with this shader keyword must
749 be square.
750*/
751void CQuake3ShaderSceneNode::deformvertexes_autosprite( f32 dt, SModifierFunction &function )
752{
753 u32 vsize = Original->Vertices.size();
754 u32 g;
755 u32 i;
756
757 const core::vector3df& camPos = SceneManager->getActiveCamera()->getPosition();
758
759 video::S3DVertex * dv = MeshBuffer->Vertices.pointer();
760 const video::S3DVertex2TCoords * vin = Original->Vertices.const_pointer();
761
762 core::matrix4 lookat ( core::matrix4::EM4CONST_NOTHING );
763 core::quaternion q;
764 for ( i = 0; i < vsize; i += 4 )
765 {
766 // quad-plane
767 core::vector3df center = 0.25f * ( vin[i+0].Pos + vin[i+1].Pos + vin[i+2].Pos + vin[i+3].Pos );
768 core::vector3df forward = camPos - center;
769
770 q.rotationFromTo ( vin[i].Normal, forward );
771 q.getMatrixCenter ( lookat, center, MeshOffset );
772
773 for ( g = 0; g < 4; ++g )
774 {
775 lookat.transformVect ( dv[i+g].Pos, vin[i+g].Pos );
776 lookat.rotateVect ( dv[i+g].Normal, vin[i+g].Normal );
777 }
778
779 }
780 function.count = 1;
781}
782
783
784/*!
785 deformVertexes autosprite2
786 Is a slightly modified "sprite" that only rotates around the middle of its longest axis.
787 This allows you to make a pillar of fire that you can walk around, or an energy beam
788 stretched across the room.
789*/
790
791struct sortaxis
792{
793 core::vector3df v;
794 bool operator < ( const sortaxis &other ) const
795 {
796 return v.getLengthSQ () < other.v.getLengthSQ ();
797 }
798};
799/*!
800*/
801void CQuake3ShaderSceneNode::deformvertexes_autosprite2( f32 dt, SModifierFunction &function )
802{
803 u32 vsize = Original->Vertices.size();
804 u32 g;
805 u32 i;
806
807 const core::vector3df camPos = SceneManager->getActiveCamera()->getAbsolutePosition();
808
809 video::S3DVertex * dv = MeshBuffer->Vertices.pointer();
810 const video::S3DVertex2TCoords * vin = Original->Vertices.const_pointer();
811
812 core::matrix4 lookat ( core::matrix4::EM4CONST_NOTHING );
813
814 core::array < sortaxis > axis;
815 axis.set_used ( 3 );
816
817 for ( i = 0; i < vsize; i += 4 )
818 {
819 // quad-plane
820 core::vector3df center = 0.25f * ( vin[i+0].Pos + vin[i+1].Pos + vin[i+2].Pos + vin[i+3].Pos );
821
822 // longes axe
823 axis[0].v = vin[i+1].Pos - vin[i+0].Pos;
824 axis[1].v = vin[i+2].Pos - vin[i+0].Pos;
825 axis[2].v = vin[i+3].Pos - vin[i+0].Pos;
826 axis.set_sorted ( false );
827 axis.sort ();
828
829 lookat.buildAxisAlignedBillboard ( camPos, center, MeshOffset, axis[1].v, vin[i+0].Normal );
830
831 for ( g = 0; g < 4; ++g )
832 {
833 lookat.transformVect ( dv[i+g].Pos, vin[i+g].Pos );
834 lookat.rotateVect ( dv[i+g].Normal, vin[i+g].Normal );
835 }
836 }
837 function.count = 1;
838}
839
840/*
841 Generate Vertex Color
842*/
843void CQuake3ShaderSceneNode::vertextransform_rgbgen( f32 dt, SModifierFunction &function )
844{
845 u32 i;
846 const u32 vsize = Original->Vertices.size();
847
848 switch ( function.rgbgen )
849 {
850 case IDENTITY:
851 //rgbgen identity
852 for ( i = 0; i != vsize; ++i )
853 MeshBuffer->Vertices[i].Color.set(0xFFFFFFFF);
854 break;
855
856 case IDENTITYLIGHTING:
857 // rgbgen identitylighting TODO: overbright
858 for ( i = 0; i != vsize; ++i )
859 MeshBuffer->Vertices[i].Color.set(0xFF7F7F7F);
860 break;
861
862 case EXACTVERTEX:
863 // alphagen exactvertex TODO lighting
864 case VERTEX:
865 // rgbgen vertex
866 for ( i = 0; i != vsize; ++i )
867 MeshBuffer->Vertices[i].Color=Original->Vertices[i].Color;
868 break;
869 case WAVE:
870 {
871 // rgbGen wave <func> <base> <amp> <phase> <freq>
872 f32 f = function.evaluate( dt ) * 255.f;
873 s32 value = core::clamp( core::floor32(f), 0, 255 );
874 value = 0xFF000000 | value << 16 | value << 8 | value;
875
876 for ( i = 0; i != vsize; ++i )
877 MeshBuffer->Vertices[i].Color.set(value);
878 } break;
879 case CONSTANT:
880 {
881 //rgbgen const ( x y z )
882 video::SColorf cf( function.x, function.y, function.z );
883 video::SColor col = cf.toSColor();
884 for ( i = 0; i != vsize; ++i )
885 MeshBuffer->Vertices[i].Color=col;
886 } break;
887 default:
888 break;
889 }
890}
891
892/*
893 Generate Vertex Color, Alpha
894*/
895void CQuake3ShaderSceneNode::vertextransform_alphagen( f32 dt, SModifierFunction &function )
896{
897 u32 i;
898 const u32 vsize = Original->Vertices.size();
899
900 switch ( function.alphagen )
901 {
902 case IDENTITY:
903 //alphagen identity
904 for ( i = 0; i != vsize; ++i )
905 MeshBuffer->Vertices[i].Color.setAlpha ( 0xFF );
906 break;
907
908 case EXACTVERTEX:
909 // alphagen exactvertex TODO lighting
910 case VERTEX:
911 // alphagen vertex
912 for ( i = 0; i != vsize; ++i )
913 MeshBuffer->Vertices[i].Color.setAlpha ( Original->Vertices[i].Color.getAlpha() );
914 break;
915 case CONSTANT:
916 {
917 // alphagen const
918 u32 a = (u32) ( function.x * 255.f );
919 for ( i = 0; i != vsize; ++i )
920 MeshBuffer->Vertices[i].Color.setAlpha ( a );
921 } break;
922
923 case LIGHTINGSPECULAR:
924 {
925 // alphagen lightingspecular TODO!!!
926 const SViewFrustum *frustum = SceneManager->getActiveCamera()->getViewFrustum();
927 const core::matrix4 &view = frustum->getTransform ( video::ETS_VIEW );
928
929 const f32 *m = view.pointer();
930
931 for ( i = 0; i != vsize; ++i )
932 {
933 const core::vector3df &n = Original->Vertices[i].Normal;
934 MeshBuffer->Vertices[i].Color.setAlpha ((u32)( 128.f *(1.f+(n.X*m[0]+n.Y*m[1]+n.Z*m[2]))));
935 }
936
937 } break;
938
939
940 case WAVE:
941 {
942 // alphagen wave
943 f32 f = function.evaluate( dt ) * 255.f;
944 s32 value = core::clamp( core::floor32(f), 0, 255 );
945
946 for ( i = 0; i != vsize; ++i )
947 MeshBuffer->Vertices[i].Color.setAlpha ( value );
948 } break;
949 default:
950 break;
951 }
952}
953
954
955
956/*
957 Generate Texture Coordinates
958*/
959void CQuake3ShaderSceneNode::vertextransform_tcgen( f32 dt, SModifierFunction &function )
960{
961 u32 i;
962 const u32 vsize = Original->Vertices.size();
963
964 switch ( function.tcgen )
965 {
966 case TURBULENCE:
967 //tcgen turb
968 {
969 function.wave = core::reciprocal( function.phase );
970
971 const f32 phase = function.phase;
972
973 for ( i = 0; i != vsize; ++i )
974 {
975 const video::S3DVertex2TCoords &src = Original->Vertices[i];
976 video::S3DVertex &dst = MeshBuffer->Vertices[i];
977
978 const f32 wavephase = (src.Pos.X + src.Pos.Y + src.Pos.Z) * function.wave;
979 function.phase = phase + wavephase;
980
981 const f32 f = function.evaluate( dt );
982
983 dst.TCoords.X = src.TCoords.X + f * src.Normal.X;
984 dst.TCoords.Y = src.TCoords.Y + f * src.Normal.Y;
985 }
986 }
987 break;
988
989 case TEXTURE:
990 // tcgen texture
991 for ( i = 0; i != vsize; ++i )
992 MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords;
993 break;
994 case LIGHTMAP:
995 // tcgen lightmap
996 for ( i = 0; i != vsize; ++i )
997 MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords2;
998 break;
999 case ENVIRONMENT:
1000 {
1001 // tcgen environment
1002 const SViewFrustum *frustum = SceneManager->getActiveCamera()->getViewFrustum();
1003 const core::matrix4 &view = frustum->getTransform ( video::ETS_VIEW );
1004
1005 const f32 *m = view.pointer();
1006
1007 core::vector3df n;
1008 for ( i = 0; i != vsize; ++i )
1009 {
1010 //const core::vector3df &n = Original->Vertices[i].Normal;
1011
1012 n = frustum->cameraPosition - Original->Vertices[i].Pos;
1013 n.normalize();
1014 n += Original->Vertices[i].Normal;
1015 n.normalize();
1016
1017 MeshBuffer->Vertices[i].TCoords.X = 0.5f*(1.f+(n.X*m[0]+n.Y*m[1]+n.Z*m[2]));
1018 MeshBuffer->Vertices[i].TCoords.Y = 0.5f*(1.f+(n.X*m[4]+n.Y*m[5]+n.Z*m[6]));
1019 }
1020
1021 } break;
1022 default:
1023 break;
1024 }
1025}
1026
1027
1028#if 0
1029/*
1030 Transform Texture Coordinates
1031*/
1032void CQuake3ShaderSceneNode::transformtex( const core::matrix4 &m, const u32 addressMode )
1033{
1034 u32 i;
1035 const u32 vsize = MeshBuffer->Vertices.size();
1036
1037 f32 tx1;
1038 f32 ty1;
1039
1040 if ( addressMode )
1041 {
1042 for ( i = 0; i != vsize; ++i )
1043 {
1044 core::vector2df &tx = MeshBuffer->Vertices[i].TCoords;
1045
1046 tx1 = m[0] * tx.X + m[4] * tx.Y + m[8];
1047 ty1 = m[1] * tx.X + m[5] * tx.Y + m[9];
1048
1049 tx.X = tx1;
1050 tx.Y = ty1;
1051 }
1052 }
1053 else
1054 {
1055
1056 for ( i = 0; i != vsize; ++i )
1057 {
1058 core::vector2df &tx = MeshBuffer->Vertices[i].TCoords;
1059
1060 tx1 = m[0] * tx.X + m[4] * tx.Y + m[8];
1061 ty1 = m[1] * tx.X + m[5] * tx.Y + m[9];
1062
1063 tx.X = tx1 <= 0.f ? 0.f : tx1 >= 1.f ? 1.f : tx1;
1064 tx.Y = ty1 <= 0.f ? 0.f : ty1 >= 1.f ? 1.f : ty1;
1065
1066 //tx.X = core::clamp( tx1, 0.f, 1.f );
1067 //tx.Y = core::clamp( ty1, 0.f, 1.f );
1068 }
1069 }
1070}
1071
1072#endif
1073
1074
1075/*
1076 Texture & Vertex Transform Animator
1077
1078 Return a Texture Transformation for this stage
1079 Vertex transformation are called if found
1080
1081*/
1082void CQuake3ShaderSceneNode::animate( u32 stage,core::matrix4 &texture )
1083{
1084 const SVarGroup *group = Shader->getGroup( stage );
1085
1086 // select current texture
1087 SQ3Texture &q3Tex = Q3Texture [ stage ];
1088 if ( q3Tex.TextureFrequency != 0.f )
1089 {
1090 s32 v = core::floor32( TimeAbs * q3Tex.TextureFrequency );
1091 q3Tex.TextureIndex = v % q3Tex.Texture.size();
1092 }
1093
1094 core::matrix4 m2;
1095 SModifierFunction function;
1096
1097 f32 f[16];
1098
1099 // walk group for all modifiers
1100 for ( u32 g = 0; g != group->Variable.size(); ++g )
1101 {
1102 const SVariable &v = group->Variable[g];
1103
1104 // get the modifier
1105 static const c8 * modifierList[] =
1106 {
1107 "tcmod","deformvertexes","rgbgen","tcgen","map","alphagen"
1108 };
1109
1110 u32 pos = 0;
1111 function.masterfunc0 = (eQ3ModifierFunction) isEqual( v.name, pos, modifierList, 6 );
1112
1113 if ( UNKNOWN == function.masterfunc0 )
1114 continue;
1115
1116 switch ( function.masterfunc0 )
1117 {
1118 //tcmod
1119 case TCMOD:
1120 m2.makeIdentity();
1121 break;
1122 default:
1123 break;
1124 }
1125
1126 // get the modifier function
1127 static const c8 * funclist[] =
1128 {
1129 "scroll","scale","rotate","stretch","turb",
1130 "wave","identity","vertex",
1131 "texture","lightmap","environment","$lightmap",
1132 "bulge","autosprite","autosprite2","transform",
1133 "exactvertex","const","lightingspecular","move","normal",
1134 "identitylighting"
1135 };
1136 static const c8 * groupToken[] = { "(", ")" };
1137
1138 pos = 0;
1139 function.masterfunc1 = (eQ3ModifierFunction) isEqual( v.content, pos, funclist, 22 );
1140 if ( function.masterfunc1 != UNKNOWN )
1141 function.masterfunc1 = (eQ3ModifierFunction) ((u32) function.masterfunc1 + FUNCTION2 + 1);
1142
1143 switch ( function.masterfunc1 )
1144 {
1145 case SCROLL:
1146 // tcMod scroll <sSpeed> <tSpeed>
1147 f[0] = getAsFloat( v.content, pos ) * TimeAbs;
1148 f[1] = getAsFloat( v.content, pos ) * TimeAbs;
1149 m2.setTextureTranslate( f[0], f[1] );
1150 break;
1151 case SCALE:
1152 // tcmod scale <sScale> <tScale>
1153 f[0] = getAsFloat( v.content, pos );
1154 f[1] = getAsFloat( v.content, pos );
1155 m2.setTextureScale( f[0], f[1] );
1156 break;
1157 case ROTATE:
1158 // tcmod rotate <degress per second>
1159 m2.setTextureRotationCenter( getAsFloat( v.content, pos ) *
1160 core::DEGTORAD *
1161 TimeAbs
1162 );
1163 break;
1164 case TRANSFORM:
1165 // tcMod <transform> <m00> <m01> <m10> <m11> <t0> <t1>
1166 memset(f, 0, sizeof ( f ));
1167 f[10] = f[15] = 1.f;
1168
1169 f[0] = getAsFloat( v.content, pos );
1170 f[1] = getAsFloat( v.content, pos );
1171 f[4] = getAsFloat( v.content, pos );
1172 f[5] = getAsFloat( v.content, pos );
1173 f[8] = getAsFloat( v.content, pos );
1174 f[9] = getAsFloat( v.content, pos );
1175 m2.setM ( f );
1176 break;
1177
1178 case STRETCH: // stretch
1179 case TURBULENCE: // turb
1180 case WAVE: // wave
1181 case IDENTITY: // identity
1182 case IDENTITYLIGHTING:
1183 case VERTEX: // vertex
1184 case MOVE:
1185 case CONSTANT:
1186 {
1187 // turb == sin, default == sin
1188 function.func = SINUS;
1189
1190 if ( function.masterfunc0 == DEFORMVERTEXES )
1191 {
1192 switch ( function.masterfunc1 )
1193 {
1194 case WAVE:
1195 // deformvertexes wave
1196 function.wave = getAsFloat( v.content, pos );
1197 break;
1198 case MOVE:
1199 //deformvertexes move
1200 function.x = getAsFloat( v.content, pos );
1201 function.z = getAsFloat( v.content, pos );
1202 function.y = getAsFloat( v.content, pos );
1203 break;
1204 default:
1205 break;
1206 }
1207 }
1208
1209 switch ( function.masterfunc1 )
1210 {
1211 case STRETCH:
1212 case TURBULENCE:
1213 case WAVE:
1214 case MOVE:
1215 getModifierFunc( function, v.content, pos );
1216 break;
1217 default:
1218 break;
1219 }
1220
1221 switch ( function.masterfunc1 )
1222 {
1223 case STRETCH:
1224 //tcMod stretch <func> <base> <amplitude> <phase> <frequency>
1225 f[0] = core::reciprocal( function.evaluate(TimeAbs) );
1226 m2.setTextureScaleCenter( f[0], f[0] );
1227 break;
1228 case TURBULENCE:
1229 //tcMod turb <base> <amplitude> <phase> <freq>
1230 //function.tcgen = TURBULENCE;
1231 m2.setTextureRotationCenter( function.frequency *
1232 core::DEGTORAD *
1233 TimeAbs
1234 );
1235 break;
1236 case WAVE:
1237 case IDENTITY:
1238 case IDENTITYLIGHTING:
1239 case VERTEX:
1240 case EXACTVERTEX:
1241 case CONSTANT:
1242 case LIGHTINGSPECULAR:
1243 case MOVE:
1244 switch ( function.masterfunc0 )
1245 {
1246 case DEFORMVERTEXES:
1247 switch ( function.masterfunc1 )
1248 {
1249 case WAVE:
1250 deformvertexes_wave( TimeAbs, function );
1251 break;
1252 case MOVE:
1253 deformvertexes_move( TimeAbs, function );
1254 break;
1255 default:
1256 break;
1257 }
1258 break;
1259 case RGBGEN:
1260 function.rgbgen = function.masterfunc1;
1261 if ( function.rgbgen == CONSTANT )
1262 {
1263 isEqual ( v.content, pos, groupToken, 2 );
1264 function.x = getAsFloat( v.content, pos );
1265 function.y = getAsFloat( v.content, pos );
1266 function.z = getAsFloat( v.content, pos );
1267 }
1268 //vertextransform_rgbgen( TimeAbs, function );
1269 break;
1270 case ALPHAGEN:
1271 function.alphagen = function.masterfunc1;
1272 if ( function.alphagen == CONSTANT )
1273 {
1274 function.x = getAsFloat( v.content, pos );
1275 }
1276
1277 //vertextransform_alphagen( TimeAbs, function );
1278 break;
1279 default:
1280 break;
1281 }
1282 break;
1283 default:
1284 break;
1285 }
1286
1287 } break;
1288 case TEXTURE:
1289 case LIGHTMAP:
1290 case ENVIRONMENT:
1291 // "texture","lightmap","environment"
1292 function.tcgen = function.masterfunc1;
1293 break;
1294 case DOLLAR_LIGHTMAP:
1295 // map == lightmap, tcgen == lightmap
1296 function.tcgen = LIGHTMAP;
1297 break;
1298 case BULGE:
1299 // deformvertexes bulge
1300 function.bulgewidth = getAsFloat( v.content, pos );
1301 function.bulgeheight = getAsFloat( v.content, pos );
1302 function.bulgespeed = getAsFloat( v.content, pos );
1303
1304 deformvertexes_bulge(TimeAbs, function);
1305 break;
1306
1307 case NORMAL:
1308 // deformvertexes normal
1309 function.amp = getAsFloat( v.content, pos );
1310 function.frequency = getAsFloat( v.content, pos );
1311
1312 deformvertexes_normal(TimeAbs, function);
1313 break;
1314
1315 case AUTOSPRITE:
1316 // deformvertexes autosprite
1317 deformvertexes_autosprite(TimeAbs, function);
1318 break;
1319
1320 case AUTOSPRITE2:
1321 // deformvertexes autosprite2
1322 deformvertexes_autosprite2(TimeAbs, function);
1323 break;
1324 default:
1325 break;
1326 } // func
1327
1328 switch ( function.masterfunc0 )
1329 {
1330 case TCMOD:
1331 texture *= m2;
1332 break;
1333 default:
1334 break;
1335 }
1336
1337 } // group
1338
1339 vertextransform_rgbgen( TimeAbs, function );
1340 vertextransform_alphagen( TimeAbs, function );
1341 vertextransform_tcgen( TimeAbs, function );
1342}
1343
1344
1345void CQuake3ShaderSceneNode::OnAnimate(u32 timeMs)
1346{
1347 TimeAbs = f32( timeMs ) * (1.f/1000.f);
1348 ISceneNode::OnAnimate( timeMs );
1349}
1350
1351const core::aabbox3d<f32>& CQuake3ShaderSceneNode::getBoundingBox() const
1352{
1353 return MeshBuffer->getBoundingBox();
1354}
1355
1356
1357u32 CQuake3ShaderSceneNode::getMaterialCount() const
1358{
1359 return Q3Texture.size();
1360}
1361
1362video::SMaterial& CQuake3ShaderSceneNode::getMaterial(u32 i)
1363{
1364 video::SMaterial& m = MeshBuffer->Material;
1365 m.setTexture(0, 0);
1366 if ( Q3Texture [ i ].TextureIndex )
1367 m.setTexture(0, Q3Texture [ i ].Texture [ Q3Texture [ i ].TextureIndex ]);
1368 return m;
1369}
1370
1371
1372} // end namespace scene
1373} // end namespace irr
1374
1375#endif
1376