3 // Written by Harald JOHNSEN, started April 2005.
5 // Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
24 # include <simgear_config.h>
27 #include <simgear/compiler.h>
31 #include <simgear/misc/sg_path.hxx>
33 #include STL_ALGORITHM
36 #include "newcloud.hxx"
42 static ssgTexture *cloudTextures[SGNewCloud::CLTexture_max];
45 bool SGNewCloud::useAnisotropic = true;
46 SGBbCache *SGNewCloud::cldCache = 0;
47 static bool texturesLoaded = false;
48 float SGNewCloud::nearRadius = 2500.0f;
49 bool SGNewCloud::lowQuality = false;
50 sgVec3 SGNewCloud::sunlight = {0.5f, 0.5f, 0.5f};
51 sgVec3 SGNewCloud::ambLight = {0.5f, 0.5f, 0.5f};
52 sgVec3 SGNewCloud::modelSunDir = {0,1,0};
56 SGNewCloud::SGNewCloud() :
59 minx(999), miny(999), minz(999), maxx(-999), maxy(-999), maxz(-999)
63 sgSetVec3(center, 0.0f, 0.0f, 0.0f);
64 sgSetVec3(cloudpos, 0.0f, 0.0f, 0.0f);
65 list_spriteContainer.reserve(8);
66 list_spriteDef.reserve(40);
67 // if( ! texturesLoaded ) {}
69 cldCache = new SGBbCache;
74 SGNewCloud::~SGNewCloud() {
75 list_spriteDef.clear();
76 list_spriteContainer.clear();
77 cldCache->free( bbId, cloudId );
81 // load all textures used to draw cloud sprites
82 void SGNewCloud::loadTextures(const string &tex_path) {
85 texturesLoaded = true;
89 cloud_path.set(tex_path);
90 cloud_path.append("cl_cumulus.rgb");
91 cloudTextures[ CLTexture_cumulus ] = new ssgTexture( cloud_path.str().c_str(), false, false, false );
92 cloudTextures[ CLTexture_cumulus ]->ref();
94 cloud_path.set(tex_path);
95 cloud_path.append("cl_stratus.rgb");
96 cloudTextures[ CLTexture_stratus ] = new ssgTexture( cloud_path.str().c_str(), false, false, false );
97 cloudTextures[ CLTexture_stratus ]->ref();
101 void SGNewCloud::startFade(bool direction, float duration, float pauseLength) {
103 void SGNewCloud::setFade(float howMuch) {
107 static float rayleighCoeffAngular(float cosAngle) {
108 return 3.0f / 4.0f * (1.0f + cosAngle * cosAngle);
111 // cp is normalized (len==1)
112 static void CartToPolar3d(sgVec3 cp, sgVec3 polar) {
113 polar[0] = atan2(cp[1], cp[0]);
114 polar[1] = SG_PI / 2.0f - atan2(sqrtf (cp[0] * cp[0] + cp[1] * cp[1]), cp[2]);
118 static void PolarToCart3d(sgVec3 p, sgVec3 cart) {
119 float tmp = cos(p[1]);
120 cart[0] = cos(p[0]) * tmp;
121 cart[1] = sin(p[0]) * tmp;
126 // compute the light for a cloud sprite corner
127 // from the normal and the sun, scaled by the Rayleigh factor
128 // and finaly added to the ambient light
129 static void lightFunction(sgVec3 normal, sgVec4 light, float pf) {
130 float cosAngle = sgScalarProductVec3( normal, SGNewCloud::modelSunDir);
131 float vl = (1.0f - 0.1f + cosAngle / 10.0f) * pf;
132 sgScaleVec3( light, SGNewCloud::sunlight, vl );
133 sgAddVec3( light, SGNewCloud::ambLight );
134 // we need to clamp or else the light will bug when adding transparency
135 if( light[0] > 1.0 ) light[0] = 1.0;
136 if( light[1] > 1.0 ) light[1] = 1.0;
137 if( light[2] > 1.0 ) light[2] = 1.0;
141 // compute the light for a cloud sprite
142 // we use ambient light and orientation versus sun position
143 // TODO:check sun pos and check code
144 void SGNewCloud::computeSimpleLight(sgVec3 FakeEyePos) {
145 // constant Rayleigh factor if we are not doing Anisotropic lighting
147 const float ang = 45.0f * SG_PI / 180.0f;
148 list_of_spriteDef::iterator iSprite;
149 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
150 if( useAnisotropic ) {
152 sgSubVec3(eyeDir, iSprite->pos, FakeEyePos);
153 sgNormaliseVec3(eyeDir);
154 float cosAngle = sgScalarProductVec3(eyeDir, modelSunDir);
155 pf = rayleighCoeffAngular(cosAngle);
157 // compute the vector going from the container box center to the sprite
158 // TODO : this is a constant except for cloudpos, compute the normal in setpos function
160 spriteContainer *thisSpriteContainer = &list_spriteContainer[iSprite->box];
161 sgSubVec3(normal, iSprite->pos, thisSpriteContainer->pos);
162 sgSubVec3(normal, thisSpriteContainer->center);
163 sgSubVec3(normal, cloudpos);
164 sgNormaliseVec3(normal);
166 // juste use the traditional normal to compute some lightning
168 lightFunction(normal, centerColor, pf);
169 sgCopyVec4(iSprite->l0, centerColor);
170 sgCopyVec4(iSprite->l1, centerColor);
171 sgCopyVec4(iSprite->l2, centerColor);
172 sgCopyVec4(iSprite->l3, centerColor);
175 // use exotic lightning function, this will give more 'relief' to the clouds
176 // compute a normal for each vextex this will simulate a smooth shading for a round shape
177 sgVec3 polar, cart, pt;
178 // I suspect this code to be bugged...
179 CartToPolar3d(normal, polar);
181 // offset the normal vector by some angle for each vertex
182 sgSetVec3(pt, polar[0] - ang, polar[1] - ang, polar[2]);
183 PolarToCart3d(pt, cart);
184 lightFunction(cart, iSprite->l0, pf);
185 sgSetVec3(pt, polar[0] + ang, polar[1] - ang, polar[2]);
186 PolarToCart3d(pt, cart);
187 lightFunction(cart, iSprite->l1, pf);
188 sgSetVec3(pt, polar[0] + ang, polar[1] + ang, polar[2]);
189 PolarToCart3d(pt, cart);
190 lightFunction(cart, iSprite->l2, pf);
191 sgSetVec3(pt, polar[0] - ang, polar[1] + ang, polar[2]);
192 PolarToCart3d(pt, cart);
193 lightFunction(cart, iSprite->l3, pf);
199 // add a new box to the cloud
200 void SGNewCloud::addContainer (float x, float y, float z, float r, CLbox_type type) {
201 spriteContainer cont;
202 sgSetVec3( cont.pos, x, y, z );
204 cont.cont_type = type;
205 sgSetVec3( cont.center, 0.0f, 0.0f, 0.0f);
206 list_spriteContainer.push_back( cont );
209 // add a sprite inside a box
210 void SGNewCloud::addSprite(float x, float y, float z, float r, CLbox_type type, int box) {
211 spriteDef newSpriteDef;
212 int rank = list_spriteDef.size();
213 sgSetVec3( newSpriteDef.pos, x, y, z);
214 newSpriteDef.box = box;
215 newSpriteDef.sprite_type = type;
216 newSpriteDef.rank = rank;
218 list_spriteDef.push_back( newSpriteDef );
219 spriteContainer *thisBox = &list_spriteContainer[box];
221 sgSubVec3( deltaPos, newSpriteDef.pos, thisBox->pos );
222 sgAddVec3( thisBox->center, deltaPos );
224 r = r * 0.6f; // 0.5 * 1.xxx
240 // return a random number between -n/2 and n/2
241 static float Rnd(float n) {
242 return n * (-0.5f + rand() / (float) RAND_MAX);
245 // generate all sprite with defined boxes
246 void SGNewCloud::genSprites( void ) {
249 N = list_spriteContainer.size();
250 for(int i = 0 ; i < N ; i++ ) {
251 spriteContainer *thisBox = & list_spriteContainer[i];
252 // the type defines how the sprites can be positioned inside the box, their size, etc
253 switch(thisBox->cont_type) {
255 for( sc = 0 ; sc <= 4 ; sc ++ ) {
256 r = thisBox->r + Rnd(0.2f);
257 x = thisBox->pos[SG_X] + Rnd(thisBox->r);
258 y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.2f);
259 z = thisBox->pos[SG_Z] + Rnd(thisBox->r);
260 addSprite(x, y, z, r, thisBox->cont_type, i);
266 x = thisBox->pos[SG_X];
267 y = thisBox->pos[SG_Y];
268 z = thisBox->pos[SG_Z];
269 addSprite(x, y, z, r, thisBox->cont_type, i);
272 for( sc = 0 ; sc <= 4 ; sc ++ ) {
273 r = thisBox->r + Rnd(0.2f);
274 x = thisBox->pos[SG_X] + Rnd(thisBox->r * 0.75f);
275 y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.5f);
276 z = thisBox->pos[SG_Z] + Rnd(thisBox->r * 0.75f);
277 if ( y < thisBox->pos[SG_Y] - thisBox->r / 10.0f )
278 y = thisBox->pos[SG_Y] - thisBox->r / 10.0f;
279 addSprite(x, y, z, r, thisBox->cont_type, i);
283 for( sc = 0 ; sc <= 4 ; sc ++ ) {
284 r = thisBox->r + Rnd(0.2f);
285 x = thisBox->pos[SG_X] + Rnd(thisBox->r);
286 y = thisBox->pos[SG_Y] + Rnd(thisBox->r);
287 z = thisBox->pos[SG_Z] + Rnd(thisBox->r);
288 addSprite(x, y, z, r, thisBox->cont_type, i);
292 sgScaleVec3(thisBox->center, 1.0f / sc);
295 radius = maxx - minx;
296 if ( (maxy - miny) > radius )
297 radius = (maxy - miny);
298 if ( (maxz - minz) > radius )
299 radius = (maxz - minz);
301 sgSetVec3( center, (maxx + minx) / 2.0f, (maxy + miny) / 2.0f, (maxz + minz) / 2.0f );
304 ' fadingrank = UBound(tbSpriteDef()) * 10
306 // TODO : compute initial sprite normals for lighting function
310 // definition of a cu cloud, only for testing
311 void SGNewCloud::new_cu(void) {
313 float r = Rnd(1.0) + 0.5;
315 addContainer(0.0f, 0.0f, 0.0f, s, CLbox_cumulus);
316 addContainer(s, 0, 0, s, CLbox_cumulus);
317 addContainer(0, 0, 2 * s, s, CLbox_cumulus);
318 addContainer(s, 0, 2 * s, s, CLbox_cumulus);
320 addContainer(-1.2f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
321 addContainer(0.2f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
322 addContainer(1.6f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
323 } else if ( r < 0.90f ) {
324 addContainer(0, 0, 0, s * 1.2, CLbox_cumulus);
325 addContainer(s, 0, 0, s, CLbox_cumulus);
326 addContainer(0, 0, s, s, CLbox_cumulus);
327 addContainer(s * 1.1, 0, s, s * 1.2, CLbox_cumulus);
329 addContainer(-1.2 * s, 1 + 0.2 * s, s * 0.5, s * 1.4, CLbox_standard);
330 addContainer(0.2 * s, 1 + 0.25 * s, s * 0.5, s * 1.5, CLbox_standard);
331 addContainer(1.6 * s, 1 + 0.2 * s, s * 0.5, s * 1.4, CLbox_standard);
336 addContainer(0, 0, 0, s, CLbox_cumulus);
337 addContainer(0, 0, s, s, CLbox_cumulus);
338 addContainer(s, 0, s, s, CLbox_cumulus);
339 addContainer(s, 0, 0, s, CLbox_cumulus);
341 addContainer(s / 2, s, s / 2, s * 1.5, CLbox_standard);
343 addContainer(0, 2 * s, 0, s, CLbox_standard);
344 addContainer(0, 2 * s, s, s, CLbox_standard);
345 addContainer(s, 2 * s, s, s, CLbox_standard);
346 addContainer(s, 2 * s, 0, s, CLbox_standard);
353 // define the new position of the cloud (inside the cloud field, not on sphere)
354 void SGNewCloud::SetPos(sgVec3 newPos) {
355 int N = list_spriteDef.size();
357 sgSubVec3( deltaPos, newPos, cloudpos );
360 for(int i = 0 ; i < N ; i ++) {
361 sgAddVec3( list_spriteDef[i].pos, deltaPos );
363 sgAddVec3( center, deltaPos );
364 sgSetVec3( cloudpos, newPos[SG_X], newPos[SG_Y], newPos[SG_Z]);
365 // TODO : recompute sprite normal so we don't have to redo that each frame
372 Public Sub drawContainers()
373 Dim N As Integer, i As Integer
374 N = UBound(tbSpriteCont())
376 Call glPolygonMode(faceFrontAndBack, pgmLine)
378 Call glColor3f(0.9, 0.9, 0.9)
383 Call glTranslatef(.x * c_scale + cloudpos(0), .y * c_scale + cloudpos(1), .z * c_scale + cloudpos(2))
384 Call gCtl.Shapes.box(.r * c_scale, .r * c_scale, .r * c_scale)
390 Call glTranslatef(ccenter(0), ccenter(1), ccenter(2))
391 Call gCtl.Shapes.Sphere(cradius, 8, 8)
396 void SGNewCloud::drawContainers() {
405 //bool SGNewCloud::compareSpriteFunction(const spriteDef &a, const spriteDef &b) {
406 // return (a.dist > b.dist);
409 // sort on distance to eye because of transparency
410 void SGNewCloud::sortSprite( sgVec3 eye ) {
411 list_of_spriteDef::iterator iSprite;
413 // compute distance from sprite to eye
414 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
416 sgSubVec3( dist, iSprite->pos, eye );
417 iSprite->dist = -(dist[0]*dist[0] + dist[1]*dist[1] + dist[2]*dist[2]);
419 sort( list_spriteDef.begin(), list_spriteDef.end() );
422 // render the cloud on screen or on the RTT texture to build the impostor
423 void SGNewCloud::Render3Dcloud( bool drawBB, sgVec3 FakeEyePos, sgVec3 deltaPos, float dist_center ) {
425 /* int clrank = fadingrank / 10;
426 int clfadeinrank = fadingrank - clrank * 10;*/
429 GLint previousTexture = -1, thisTexture;
430 list_of_spriteDef::iterator iSprite;
431 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
432 // choose texture to use depending on sprite type
433 switch(iSprite->sprite_type) {
435 thisTexture = CLTexture_stratus;
438 thisTexture = CLTexture_cumulus;
441 // in practice there is no texture switch (atm)
442 if( previousTexture != thisTexture ) {
443 previousTexture = thisTexture;
444 glBindTexture(GL_TEXTURE_2D, cloudTextures[thisTexture]->getHandle());
449 sgCopyVec3( translate, iSprite->pos);
450 sgSubVec3( translate, iSprite->pos, deltaPos );
453 sgSubVec3( translate, iSprite->pos, deltaPos);
456 // flipx and flipy are random texture flip flags, this gives more random clouds
457 float flipx = (float) ( iSprite->rank & 1 );
458 float flipy = (float) ( (iSprite->rank >> 1) & 1 );
459 // cu texture have a flat bottom so we can't do a vertical flip
460 if( iSprite->sprite_type == CLbox_cumulus || iSprite->sprite_type == CLbox_stratus )
462 if( iSprite->sprite_type == CLbox_stratus )
464 // adjust colors depending on cloud type
465 // TODO : rewrite that later, still experimental
466 switch(iSprite->sprite_type) {
469 sgScaleVec3(iSprite->l0, 0.6f);
470 sgScaleVec3(iSprite->l1, 0.6f);
474 sgScaleVec3(iSprite->l0, 0.8f);
475 sgScaleVec3(iSprite->l1, 0.8f);
476 sgScaleVec3(iSprite->l2, 0.8f);
477 sgScaleVec3(iSprite->l3, 0.8f);
480 // darker bottom than top
481 sgScaleVec3(iSprite->l0, 0.8f);
482 sgScaleVec3(iSprite->l1, 0.8f);
485 float r = iSprite->r * 0.5f;
487 sgVec4 l0, l1, l2, l3;
488 sgCopyVec4 ( l0, iSprite->l0 );
489 sgCopyVec4 ( l1, iSprite->l1 );
490 sgCopyVec4 ( l2, iSprite->l2 );
491 sgCopyVec4 ( l3, iSprite->l3 );
493 // blend clouds with sky based on distance to limit the contrast of distant cloud
494 // TODO:use cloudfield vis, not hardcoded value
495 float t = 1.0f - dist_center / (15000.0f * 2.0 );
497 t = 0.0f; // no, it should have been culled
498 // now clouds at the far plane are half blended
499 sgScaleVec4( l0, t );
500 sgScaleVec4( l1, t );
501 sgScaleVec4( l2, t );
502 sgScaleVec4( l3, t );
504 // compute the rotations so that the quad is facing the camera
506 sgSetVec3( pos, translate[SG_X], translate[SG_Z], translate[SG_Y] );
507 sgCopyVec3( translate, pos );
508 sgNormaliseVec3( translate );
509 sgVec3 x, y, up = {0.0f, 0.0f, 1.0f};
510 sgVectorProductVec3(x, translate, up);
513 sgVectorProductVec3(y, x, translate);
519 sgSetVec3( left, iSprite->pos[SG_X], iSprite->pos[SG_Z], iSprite->pos[SG_Y]);
521 sgCopyVec3( left, pos );
523 sgAddVec3 (right, left, x);
528 glTexCoord2f(flipx, 1.0f - flipy);
531 glTexCoord2f(1.0f - flipx, 1.0f - flipy);
533 sgScaleVec3( y, 2.0 );
535 sgAddVec3( right, y);
537 glTexCoord2f(1.0f - flipx, flipy);
540 glTexCoord2f(flipx, flipy);
549 // compute rotations so that a quad is facing the camera
550 // TODO:change obsolete code because we dont use glrotate anymore
551 void SGNewCloud::CalcAngles(sgVec3 refpos, sgVec3 FakeEyePos, float *angleY, float *angleX) {
552 sgVec3 upAux, lookAt, objToCamProj, objToCam;
555 sgSetVec3(objToCamProj, -FakeEyePos[SG_X] + refpos[SG_X], -FakeEyePos[SG_Z] + refpos[SG_Z], 0.0f);
556 sgNormaliseVec3(objToCamProj);
558 sgSetVec3(lookAt, 0.0f, 1.0f, 0.0f);
559 sgVectorProductVec3(upAux, lookAt, objToCamProj);
560 angle = sgScalarProductVec3(lookAt, objToCamProj);
561 if( (angle < 0.9999f) && (angle > -0.9999f) ) {
562 angle = acos(angle) * 180.0f / SG_PI;
563 if( upAux[2] < 0.0f )
568 sgSetVec3(objToCam, -FakeEyePos[SG_X] + refpos[SG_X], -FakeEyePos[SG_Z] + refpos[SG_Z], -FakeEyePos[SG_Y] + refpos[SG_Y]);
569 sgNormaliseVec3(objToCam);
571 angle2 = sgScalarProductVec3(objToCamProj, objToCam);
572 if( (angle2 < 0.9999f) && (angle2 > -0.9999f) ) {
573 angle2 = -acos(angle2) * 180.0f / SG_PI;
574 if( objToCam[2] > 0.0f )
585 // draw a cloud but this time we use the impostor texture
586 void SGNewCloud::RenderBB(sgVec3 deltaPos, float angleY, float angleX, float dist_center) {
587 // TODO:glrotate is not needed
589 glTranslatef(center[SG_X] - deltaPos[SG_X], center[SG_Z] - deltaPos[SG_Z], center[SG_Y] - deltaPos[SG_Y]);
590 glRotatef(angleY, 0.0f, 0.0f, 1.0f);
591 glRotatef(angleX, 1.0f, 0.0f, 0.0f);
593 // blend clouds with sky based on distance to limit the contrast of distant cloud
594 // TODO:use cloudfield vis, not hardcoded value
595 float t = 1.0f - dist_center / (15000.0f * 2.0 );
596 // err the alpha value is not good for impostor, debug that
601 glColor4f(t, t, t, t);
604 glTexCoord2f(0.0f, 1.0f);
606 glTexCoord2f(1.0f, 1.0f);
608 glTexCoord2f(1.0f, 0.0f);
610 glTexCoord2f(0.0f, 0.0f);
615 int age = cldCache->queryImpostorAge(bbId);
616 // draw a red border for the newly generated BBs else draw a white border
622 glBindTexture(GL_TEXTURE_2D, 0);
623 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
630 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
638 // determine if it is a good idea to use an impostor to render the cloud
639 bool SGNewCloud::isBillboardable(float dist) {
641 if( dist <= ( 2.1f * radius ) ) {
645 if( (dist-radius) <= nearRadius ) {
646 // near clouds we don't want to use BB
655 // render the cloud, fakepos is a relative position inside the cloud field
656 void SGNewCloud::Render(sgVec3 fakepos) {
657 sgVec3 eyePos, FakeEyePos;
661 glColor3f(1.0f, 1.0f, 1.0f);
664 sgCopyVec3( eyePos, fakepos );
666 sgCopyVec3( FakeEyePos, fakepos);
668 // sgSubVec3( deltaPos, eyePos, FakeEyePos);
669 sgCopyVec3( deltaPos, FakeEyePos);
670 sgSubVec3( dist, center, FakeEyePos);
671 float dist_center = sgLengthVec3(dist);
673 // eeeek don't do that so early, perhaps we will use an impostor
674 computeSimpleLight( FakeEyePos );
676 // view point sort, we sort because of transparency
677 // eeeek don't do that so early, perhaps we will use an impostor
678 sortSprite( FakeEyePos );
681 if( !isBillboardable(dist_center) ) {
682 // not a good candidate for impostors, draw a real cloud
683 Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
686 // lets use our impostor
688 texID = cldCache->QueryTexID(cloudId, bbId);
690 // ok someone took our impostor, so allocate a new one
692 // allocate a new Impostor
693 bbId = cldCache->alloc(cloudId);
694 texID = cldCache->QueryTexID(cloudId, bbId);
697 // no more free texture in the pool
698 Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
700 float angleX, angleY;
701 CalcAngles(center, FakeEyePos, &angleY, &angleX);
702 if( ! cldCache->isBbValid( cloudId, bbId, angleY, angleX) ) {
703 // we must build or rebuild this billboard
704 // start render to texture
705 cldCache->beginCapture();
706 // set transformation matrices
707 cldCache->setRadius(radius, dist_center);
708 gluLookAt(FakeEyePos[SG_X], FakeEyePos[SG_Z], FakeEyePos[SG_Y], center[SG_X], center[SG_Z], center[SG_Y], 0.0, 0.0, 1.0);
710 Render3Dcloud(true, FakeEyePos, deltaPos, dist_center);
711 // save rotation angles for later use
712 // TODO:this is not ok
713 cldCache->setReference(cloudId, bbId, angleY, angleX);
714 // save the rendered cloud into the cache
715 cldCache->setTextureData( bbId );
716 // finish render to texture and go back into standard context
717 cldCache->endCapture();
719 // draw the newly built BB or an old one
720 glBindTexture(GL_TEXTURE_2D, texID);
721 RenderBB(deltaPos, angleY, angleX, dist_center);