/* Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company. Copyright (C) 2009 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "SDL_opengl.h" #include "doomtype.h" #include "w_wad.h" #include "m_argv.h" #include "d_event.h" #include "v_video.h" #include "doomstat.h" #include "r_bsp.h" #include "r_main.h" #include "r_draw.h" #include "r_sky.h" #include "r_plane.h" #include "r_data.h" #include "r_things.h" #include "r_fps.h" #include "p_maputl.h" #include "m_bbox.h" #include "lprintf.h" #include "gl_intern.h" #include "gl_struct.h" // If the Doom levels had been built with realistic visibility // taken into account for the sky areas, we could just draw the // sky first and then the walls, but that gives artifacts where // you see some sectors floating in the sky now. This causes // the walls to draw extended top and bottom sections for skies. #define SKYWALLS #define MINZ (FRACUNIT*4) #define BASEYCENTER 100 #define MINZ_FLOAT 4 typedef struct { GLTexture *tex; side_t *side; int flag; // GLDWF_TOP, GLDWF_M1S, etc } sortLine_t; #define MAX_SORT_LINES 4096 sortLine_t sortLines[MAX_SORT_LINES]; int numSortLines; typedef struct { GLTexture *texture; sector_t *sector; boolean ceiling; } sortSectorPlane_t; #define MAX_SECTOR_PLANES 1024 sortSectorPlane_t sectorPlanes[MAX_SECTOR_PLANES]; int numSectorPlanes; typedef struct { GLTexture *tex; } sortSprite_t; // Cleared to 0 at frame start. // Individual columns will be set to 1 as occluding segments are processed. // An occluding segment is either a one-sided line, a line that has a back // sector with equal floor and ceiling heights, a line with a back ceiling // height lower than the fron floor height, or a line with a back floor height // higher than the front ceiling height. // Entire nodes are culled when their bounds does not include a 0 column. // Individual line segments are culled when their span does not include 0 columns. // Sprites could be checked against it, but it may not be worth it. char occlusion[MAX_SCREENWIDTH+2]; // +2 for guard columns to avoid clamping // when the iphone is upside down, the occlusion segments are reversed boolean reversedLandscape; // this matrix is exactly what GL uses, but there will still // be floating point differences between the GPU and CPU float glMVPmatrix[16]; // if any sector textures are the sky texture, we will draw the sky and // ignore those sector geometries boolean skyIsVisible; // these should really be initialized based on viewwidth somewhere... float halfWidthFloat = 240.0f; // used during debugging to isolate incorrect culling int failCount; // Some of the two sided line segments in the original game don't have a valid // texture, so stick something there instead of leaving a gaping hole in the world. GLTexture *defaultTexture; // just for the sky texture setup float yaw; // counters int c_occludedSprites; int c_sectors; int c_subsectors; // test options int testClear = 0; int testNewRenderer = 1; int showRenderTime; int blendAll; void BuildIndexedTriangles(); void BuildSideSegs(); void IR_MergeSectors( int fromSector, int intoSector ) { // E3M8 (and possibly others somewhere) has a bad sector // classification with two stray lines in sector 2 that // should be a part of sector 1. This makes both of the // sectors "broken" and unable to be properly tesselated. assert( (unsigned)fromSector < numsectors ); assert( (unsigned)intoSector < numsectors ); sector_t *fromSectorPtr = §ors[fromSector]; sector_t *intoSectorPtr = §ors[intoSector]; int moveLines = 0; for ( int i = 0 ; i < numlines ; i++ ) { if ( lines[i].frontsector == fromSectorPtr ) { moveLines++; } else if ( lines[i].backsector == fromSectorPtr ) { moveLines++; } } // add these lines to intoSector // Unfortunately, the sector->lines list is not allocated per-sector, but // is a single block for the entire level, so we can't realloc it. I'm // going to just let the new table leak. line_t **newLines = Z_Malloc( ( intoSectorPtr->linecount + moveLines ) * sizeof( *intoSectorPtr->lines ), PU_LEVEL,0); memcpy( newLines, intoSectorPtr->lines, intoSectorPtr->linecount * sizeof( *newLines ) ); intoSectorPtr->lines = newLines; for ( int i = 0 ; i < numlines ; i++ ) { if ( lines[i].frontsector == fromSectorPtr ) { intoSectorPtr->lines[intoSectorPtr->linecount++] = &lines[i]; lines[i].frontsector = intoSectorPtr; } else if ( lines[i].backsector == fromSectorPtr ) { intoSectorPtr->lines[intoSectorPtr->linecount++] = &lines[i]; lines[i].backsector = intoSectorPtr; } } // change all the segs for ( int i = 0 ; i < numsegs ; i++ ) { if ( segs[i].frontsector == fromSectorPtr ) { segs[i].frontsector = intoSectorPtr; } if ( segs[i].backsector == fromSectorPtr ) { segs[i].backsector = intoSectorPtr; } } // change all the sides to point to the new one for ( int i = 0 ; i < numsides ; i++ ) { if ( sides[i].sector == fromSectorPtr ) { sides[i].sector = intoSectorPtr; } } // change all the subsectors to point to the new one for ( int i = 0 ; i < numsubsectors ; i++ ) { if ( subsectors[i].sector == fromSectorPtr ) { subsectors[i].sector = intoSectorPtr; } } // make fromSector vestigial so it doesn't get tesselated fromSectorPtr->linecount = 0; } void IR_InitLevel() { BuildIndexedTriangles(); // convert the loops into indexed triangles BuildSideSegs(); // create a seg_t for each side_t so we can draw the // unclipped versions that fit perfectly with the sectors // find something else used in the level for a default texture for ( int i = 0 ; i < numsides ; i++ ) { if ( sides[i].toptexture ) { defaultTexture=gld_RegisterTexture(sides[i].toptexture, true, false); if ( defaultTexture ) { break; } } } assert( defaultTexture ); } float lightDistance = 10.0f; // in prBoom MAP_SCALE units, increasing this makes things get dimmer faster #define MAX_LIGHT_DROP 96 float lightingVector[3]; // transform and scale [ x y 1 ] to get color units to subtract static int FadedLighting( float x, float y, int sectorLightLevel ) { // Ramp down the lightover lightDistance world units. // Triangles that extend across behind the view origin and past // the lightDistance clamping boundary will not have completely linear fading, // but nobody should notice. // A proportional drop in lighting sounds like a better idea, but // this linear drop seems to look nicer. It's not like Doom's // lighting is realistic in any case... int idist = x * lightingVector[0] + y * lightingVector[1] + lightingVector[2]; if ( idist < 0 ) { idist = 0; } else if ( idist > MAX_LIGHT_DROP ) { idist = MAX_LIGHT_DROP; } sectorLightLevel -= idist; if ( sectorLightLevel < 0 ) { sectorLightLevel = 0; } if ( sectorLightLevel > 255 ) { sectorLightLevel = 255; } return sectorLightLevel | (sectorLightLevel<<8) | (sectorLightLevel<<16) | (255<<24); } // // IR_ProjectSprite // Generates a vissprite for a thing if it might be visible. // static void IR_ProjectSprite (mobj_t* thing, int lightlevel) { fixed_t gzt; // killough 3/27/98 fixed_t tx; fixed_t xscale; int x1; int x2; spritedef_t *sprdef; spriteframe_t *sprframe; int lump; boolean flip; // transform the origin point fixed_t tr_x, tr_y; fixed_t fx, fy, fz; fixed_t gxt, gyt; fixed_t tz; int width; fx = thing->x; fy = thing->y; fz = thing->z; tr_x = fx - viewx; tr_y = fy - viewy; gxt = FixedMul(tr_x,viewcos); gyt = -FixedMul(tr_y,viewsin); tz = gxt-gyt; // thing is behind view plane? if (tz < MINZ) return; xscale = FixedDiv(projection, tz); gxt = -FixedMul(tr_x,viewsin); gyt = FixedMul(tr_y,viewcos); tx = -(gyt+gxt); // too far off the side? if (D_abs(tx)>(tz<<2)) return; // decide which patch to use for sprite relative to player #ifdef RANGECHECK if ((unsigned) thing->sprite >= (unsigned)numsprites) I_Error ("R_ProjectSprite: Invalid sprite number %i", thing->sprite); #endif sprdef = &sprites[thing->sprite]; #ifdef RANGECHECK if ((thing->frame&FF_FRAMEMASK) >= sprdef->numframes) I_Error ("R_ProjectSprite: Invalid sprite frame %i : %i", thing->sprite, thing->frame); #endif if (!sprdef->spriteframes) I_Error ("R_ProjectSprite: Missing spriteframes %i : %i", thing->sprite, thing->frame); sprframe = &sprdef->spriteframes[thing->frame & FF_FRAMEMASK]; if (sprframe->rotate) { // choose a different rotation based on player view // JDC: this could be better... angle_t ang = R_PointToAngle(fx, fy); unsigned rot = (ang-thing->angle+(unsigned)(ANG45/2)*9)>>29; lump = sprframe->lump[rot]; flip = (boolean) sprframe->flip[rot]; } else { // use single rotation for all views lump = sprframe->lump[0]; flip = (boolean) sprframe->flip[0]; } { const rpatch_t* patch = R_CachePatchNum(lump+firstspritelump); /* calculate edges of the shape * cph 2003/08/1 - fraggle points out that this offset must be flipped * if the sprite is flipped; e.g. FreeDoom imp is messed up by this. */ if (flip) { tx -= (patch->width - patch->leftoffset) << FRACBITS; } else { tx -= patch->leftoffset << FRACBITS; } x1 = (centerxfrac + FixedMul(tx,xscale)) >> FRACBITS; tx += patch->width<> FRACBITS) - 1; gzt = fz + (patch->topoffset << FRACBITS); width = patch->width; // JDC: we don't care if they never get freed, // so don't bother changing the zone tag status each time //R_UnlockPatchNum(lump+firstspritelump); } // off the side? if (x1 > viewwidth || x2 < 0) return; // killough 4/9/98: clip things which are out of view due to height // e6y: fix of hanging decoration disappearing in Batman Doom MAP02 // centeryfrac -> viewheightfrac if (fz > viewz + FixedDiv(viewheightfrac, xscale) || gzt < viewz - FixedDiv(viewheightfrac-viewheight, xscale)) return; // JDC: clip to the occlusio buffer int testLow = x1 < 0 ? 0 : x1; int testHigh = x2 >= viewwidth ? viewwidth - 1 : x2; if ( reversedLandscape ) { testLow = viewwidth-1-testLow; testHigh = viewwidth-1-testHigh; } if ( !memchr( occlusion+testLow, 0, testHigh - testLow ) ) { c_occludedSprites++; return; } // ------------ gld_AddSprite ---------- mobj_t *pSpr= thing; GLSprite sprite; float voff,hoff; sprite.scale= FixedDiv(projectiony, tz); if (pSpr->frame & FF_FULLBRIGHT) sprite.light = 255; else sprite.light = pSpr->subsector->sector->lightlevel+(extralight<<5); sprite.cm=CR_LIMIT+(int)((pSpr->flags & MF_TRANSLATION) >> (MF_TRANSSHIFT)); sprite.gltexture=gld_RegisterPatch(lump+firstspritelump,sprite.cm); if (!sprite.gltexture) return; sprite.shadow = (pSpr->flags & MF_SHADOW) != 0; sprite.trans = (pSpr->flags & MF_TRANSLUCENT) != 0; if (movement_smooth) { sprite.x = (float)(-pSpr->PrevX + FixedMul (tic_vars.frac, -pSpr->x - (-pSpr->PrevX)))/MAP_SCALE; sprite.y = (float)(pSpr->PrevZ + FixedMul (tic_vars.frac, pSpr->z - pSpr->PrevZ))/MAP_SCALE; sprite.z = (float)(pSpr->PrevY + FixedMul (tic_vars.frac, pSpr->y - pSpr->PrevY))/MAP_SCALE; } else { sprite.x=-(float)pSpr->x/MAP_SCALE; sprite.y= (float)pSpr->z/MAP_SCALE; sprite.z= (float)pSpr->y/MAP_SCALE; } sprite.vt=0.0f; sprite.vb=(float)sprite.gltexture->height/(float)sprite.gltexture->tex_height; if (flip) { sprite.ul=0.0f; sprite.ur=(float)sprite.gltexture->width/(float)sprite.gltexture->tex_width; } else { sprite.ul=(float)sprite.gltexture->width/(float)sprite.gltexture->tex_width; sprite.ur=0.0f; } hoff=(float)sprite.gltexture->leftoffset/(float)(MAP_COEFF); voff=(float)sprite.gltexture->topoffset/(float)(MAP_COEFF); sprite.x1=hoff-((float)sprite.gltexture->realtexwidth/(float)(MAP_COEFF)); sprite.x2=hoff; sprite.y1=voff; sprite.y2=voff-((float)sprite.gltexture->realtexheight/(float)(MAP_COEFF)); // JDC: don't let sprites poke below the ground level. // Software rendering Doom didn't use depth buffering, // so sprites always got drawn on top of the flat they // were on, but in GL they tend to get a couple pixel // rows clipped off. if ( sprite.y2 < 0 ) { sprite.y1 -= sprite.y2; sprite.y2 = 0; } if (gld_drawinfo.num_sprites>=gld_drawinfo.max_sprites) { gld_drawinfo.max_sprites+=128; gld_drawinfo.sprites=Z_Realloc(gld_drawinfo.sprites,gld_drawinfo.max_sprites*sizeof(GLSprite),PU_LEVEL,0); } gld_drawinfo.sprites[gld_drawinfo.num_sprites++]=sprite; } // JDC: removed the 0.001f epsilons that were presumably added // to try to hide T-junction cracks, but now that we are drawing // source lines instead of clipped segs, it is a non-problem. #define LINE seg->linedef #define CALC_Y_VALUES(w, lineheight, floor_height, ceiling_height)\ (w).ytop=((float)(ceiling_height)/(float)MAP_SCALE);\ (w).ybottom=((float)(floor_height)/(float)MAP_SCALE);\ lineheight=((float)fabs(((ceiling_height)/(float)FRACUNIT)-((floor_height)/(float)FRACUNIT))) #define OU(w,seg) (((float)((seg)->sidedef->textureoffset+(seg)->offset)/(float)FRACUNIT)/(float)(w).gltexture->buffer_width) #define OV(w,seg) (((float)((seg)->sidedef->rowoffset)/(float)FRACUNIT)/(float)(w).gltexture->buffer_height) #define OV_PEG(w,seg,v_offset) (OV((w),(seg))-(((float)(v_offset)/(float)FRACUNIT)/(float)(w).gltexture->buffer_height)) #define CALC_TEX_VALUES_TOP(w, seg, peg, linelength, lineheight)\ (w).flag=GLDWF_TOP;\ (w).ul=OU((w),(seg))+(0.0f);\ (w).ur=OU((w),(seg))+((linelength)/(float)(w).gltexture->buffer_width);\ (peg)?\ (\ (w).vb=OV((w),(seg))+((float)(w).gltexture->height/(float)(w).gltexture->tex_height),\ (w).vt=((w).vb-((float)(lineheight)/(float)(w).gltexture->buffer_height))\ ):(\ (w).vt=OV((w),(seg))+(0.0f),\ (w).vb=OV((w),(seg))+((float)(lineheight)/(float)(w).gltexture->buffer_height)\ ) #define CALC_TEX_VALUES_MIDDLE1S(w, seg, peg, linelength, lineheight)\ (w).flag=GLDWF_M1S;\ (w).ul=OU((w),(seg))+(0.0f);\ (w).ur=OU((w),(seg))+((linelength)/(float)(w).gltexture->buffer_width);\ (peg)?\ (\ (w).vb=OV((w),(seg))+((float)(w).gltexture->height/(float)(w).gltexture->tex_height),\ (w).vt=((w).vb-((float)(lineheight)/(float)(w).gltexture->buffer_height))\ ):(\ (w).vt=OV((w),(seg))+(0.0f),\ (w).vb=OV((w),(seg))+((float)(lineheight)/(float)(w).gltexture->buffer_height)\ ) #define CALC_TEX_VALUES_MIDDLE2S(w, seg, peg, linelength, lineheight)\ (w).flag=GLDWF_M2S;\ (w).ul=OU((w),(seg))+(0.0f);\ (w).ur=OU((w),(seg))+((linelength)/(float)(w).gltexture->buffer_width);\ (peg)?\ (\ (w).vb=((float)(w).gltexture->height/(float)(w).gltexture->tex_height),\ (w).vt=((w).vb-((float)(lineheight)/(float)(w).gltexture->buffer_height))\ ):(\ (w).vt=(0.0f),\ (w).vb=((float)(lineheight)/(float)(w).gltexture->buffer_height)\ ) #define CALC_TEX_VALUES_BOTTOM(w, seg, peg, linelength, lineheight, v_offset)\ (w).flag=GLDWF_BOT;\ (w).ul=OU((w),(seg))+(0.0f);\ (w).ur=OU((w),(seg))+((linelength)/(float)(w).gltexture->realtexwidth);\ (peg)?\ (\ (w).vb=OV_PEG((w),(seg),(v_offset))+((float)(w).gltexture->height/(float)(w).gltexture->tex_height),\ (w).vt=((w).vb-((float)(lineheight)/(float)(w).gltexture->buffer_height))\ ):(\ (w).vt=OV((w),(seg))+(0.0f),\ (w).vb=OV((w),(seg))+((float)(lineheight)/(float)(w).gltexture->buffer_height)\ ) // e6y // Sky textures with a zero index should be forced // See third episode of requiem.wad #define SKYTEXTURE_PRBOOM(sky1,sky2)\ if ((sky1) & PL_SKYFLAT)\ {\ const line_t *l = &lines[sky1 & ~PL_SKYFLAT];\ const side_t *s = *l->sidenum + sides;\ wall.gltexture=gld_RegisterTexture(texturetranslation[s->toptexture], false, texturetranslation[s->toptexture]==skytexture);\ wall.skyyaw=-2.0f*((-(float)((viewangle+s->textureoffset)>>ANGLETOFINESHIFT)*360.0f/FINEANGLES)/90.0f);\ wall.skyymid = 200.0f/319.5f*(((float)s->rowoffset/(float)FRACUNIT - 28.0f)/100.0f);\ wall.flag = l->special==272 ? GLDWF_SKY : GLDWF_SKYFLIP;\ }\ else\ if ((sky2) & PL_SKYFLAT)\ {\ const line_t *l = &lines[sky2 & ~PL_SKYFLAT];\ const side_t *s = *l->sidenum + sides;\ wall.gltexture=gld_RegisterTexture(texturetranslation[s->toptexture], false, texturetranslation[s->toptexture]==skytexture);\ wall.skyyaw=-2.0f*((-(float)((viewangle+s->textureoffset)>>ANGLETOFINESHIFT)*360.0f/FINEANGLES)/90.0f);\ wall.skyymid = 200.0f/319.5f*(((float)s->rowoffset/(float)FRACUNIT - 28.0f)/100.0f);\ wall.flag = l->special==272 ? GLDWF_SKY : GLDWF_SKYFLIP;\ }\ else\ {\ wall.gltexture=gld_RegisterTexture(skytexture, false, true);\ wall.skyyaw=-2.0f*((yaw+90.0f)/90.0f);\ wall.skyymid = 200.0f/319.5f*((100.0f)/100.0f);\ wall.flag = GLDWF_SKY;\ }; #define SKYTEXTURE(sky1,sky2)\ wall.gltexture=NULL;\ wall.flag = GLDWF_SKY; #define ADDWALL(wall)\ {\ if (gld_drawinfo.num_walls>=gld_drawinfo.max_walls)\ {\ gld_drawinfo.max_walls+=128;\ gld_drawinfo.walls=Z_Realloc(gld_drawinfo.walls,gld_drawinfo.max_walls*sizeof(GLWall),PU_LEVEL,0);\ }\ gld_drawinfo.walls[gld_drawinfo.num_walls++]=*wall;\ }; extern GLSeg *gl_segs; extern byte rendermarker; extern byte *segrendered; void IR_AddWall(seg_t *seg) { GLWall wall; GLTexture *temptex; sector_t *frontsector; sector_t *backsector; float lineheight; int rellight = 0; wall.glseg=NULL; wall.side = seg->sidedef; frontsector = seg->frontsector; // JDC: improve this lighting tweak rellight = seg->linedef->dx==0? +8 : seg->linedef->dy==0 ? -8 : 0; int light = frontsector->lightlevel+rellight+(extralight<<5); wall.light = MAX(MIN((light),255),0); wall.alpha=1.0f; wall.gltexture=NULL; if (!seg->backsector) /* onesided */ { #ifdef SKYWALLS if (frontsector->ceilingpic==skyflatnum) { wall.ytop=255.0f; wall.ybottom=(float)frontsector->ceilingheight/MAP_SCALE; SKYTEXTURE(frontsector->sky,frontsector->sky); ADDWALL(&wall); } if (frontsector->floorpic==skyflatnum) { wall.ytop=(float)frontsector->floorheight/MAP_SCALE; wall.ybottom=-255.0f; SKYTEXTURE(frontsector->sky,frontsector->sky); ADDWALL(&wall); } #endif temptex=gld_RegisterTexture(texturetranslation[seg->sidedef->midtexture], true, false); if (temptex) { wall.gltexture=temptex; CALC_Y_VALUES(wall, lineheight, frontsector->floorheight, frontsector->ceilingheight); CALC_TEX_VALUES_MIDDLE1S( wall, seg, (LINE->flags & ML_DONTPEGBOTTOM)>0, seg->length, lineheight ); ADDWALL(&wall); } } else /* twosided */ { int floor_height,ceiling_height; backsector=seg->backsector; /* toptexture */ ceiling_height=frontsector->ceilingheight; floor_height=backsector->ceilingheight; #ifdef SKYWALLS if (frontsector->ceilingpic==skyflatnum) { wall.ytop=255.0f; if ( // e6y // Fix for HOM in the starting area on Memento Mori map29 and on map30. // old code: (backsector->ceilingheight==backsector->floorheight) && (backsector->ceilingheight==backsector->floorheight||(backsector->ceilingheight<=frontsector->floorheight)) && (backsector->ceilingpic==skyflatnum) ) { wall.ybottom=(float)backsector->floorheight/MAP_SCALE; SKYTEXTURE(frontsector->sky,backsector->sky); ADDWALL(&wall); } else { if ( (texturetranslation[seg->sidedef->toptexture]!=NO_TEXTURE) ) { // e6y // It corrects some problem with sky, but I do not remember which one // old code: wall.ybottom=(float)frontsector->ceilingheight/MAP_SCALE; wall.ybottom=(float)MAX(frontsector->ceilingheight,backsector->ceilingheight)/MAP_SCALE; SKYTEXTURE(frontsector->sky,backsector->sky); ADDWALL(&wall); } else if ( (backsector->ceilingheight <= frontsector->floorheight) || (backsector->ceilingpic != skyflatnum) ) { wall.ybottom=(float)backsector->ceilingheight/MAP_SCALE; SKYTEXTURE(frontsector->sky,backsector->sky); ADDWALL(&wall); } } } #endif if (floor_heightceilingpic==skyflatnum) && (backsector->ceilingpic==skyflatnum))) { temptex=gld_RegisterTexture(texturetranslation[seg->sidedef->toptexture], true, false); if ( !temptex ) { temptex = defaultTexture; // it seems some line segments have bad data... } wall.gltexture=temptex; CALC_Y_VALUES(wall, lineheight, floor_height, ceiling_height); CALC_TEX_VALUES_TOP( wall, seg, (LINE->flags & (ML_DONTPEGBOTTOM | ML_DONTPEGTOP))==0, seg->length, lineheight ); ADDWALL(&wall); } } /* midtexture */ //e6y if (comp[comp_maskedanim]) temptex=gld_RegisterTexture(seg->sidedef->midtexture, true, false); else // e6y // Animated middle textures with a zero index should be forced // See spacelab.wad (http://www.doomworld.com/idgames/index.php?id=6826) temptex=gld_RegisterTexture(texturetranslation[seg->sidedef->midtexture], true, true); if (temptex && seg->sidedef->midtexture != NO_TEXTURE) { wall.gltexture=temptex; if ( (LINE->flags & ML_DONTPEGBOTTOM) >0) { if (seg->backsector->ceilingheight<=seg->frontsector->floorheight) goto bottomtexture; floor_height=MAX(seg->frontsector->floorheight,seg->backsector->floorheight)+(seg->sidedef->rowoffset); ceiling_height=floor_height+(wall.gltexture->realtexheight<backsector->ceilingheight<=seg->frontsector->floorheight) goto bottomtexture; ceiling_height=MIN(seg->frontsector->ceilingheight,seg->backsector->ceilingheight)+(seg->sidedef->rowoffset); floor_height=ceiling_height-(wall.gltexture->realtexheight<flags & ML_DONTPEGBOTTOM)>0, segs[seg->iSegID].length, lineheight );*/ { int floormax, ceilingmin, linelen; float mip; mip = (float)wall.gltexture->realtexheight/(float)wall.gltexture->buffer_height; // if ( (texturetranslation[seg->sidedef->bottomtexture]!=R_TextureNumForName("-")) ) if (seg->sidedef->bottomtexture) floormax=MAX(seg->frontsector->floorheight,seg->backsector->floorheight); else floormax=floor_height; if (seg->sidedef->toptexture) ceilingmin=MIN(seg->frontsector->ceilingheight,seg->backsector->ceilingheight); else ceilingmin=ceiling_height; linelen=abs(ceiling_height-floor_height); wall.ytop=((float)MIN(ceilingmin, ceiling_height)/(float)MAP_SCALE); wall.ybottom=((float)MAX(floormax, floor_height)/(float)MAP_SCALE); wall.flag=GLDWF_M2S; wall.ul=OU((wall),(seg))+(0.0f); wall.ur=OU(wall,(seg))+((seg->length)/(float)wall.gltexture->buffer_width); if (floormax<=floor_height) wall.vb=mip*1.0f; else wall.vb=mip*((float)(ceiling_height - floormax))/linelen; if (ceilingmin>=ceiling_height) wall.vt=0.0f; else wall.vt=mip*((float)(ceiling_height - ceilingmin))/linelen; } wall.alpha=1.0f; ADDWALL(&wall); } bottomtexture: /* bottomtexture */ ceiling_height=backsector->floorheight; floor_height=frontsector->floorheight; #ifdef SKYWALLS if (frontsector->floorpic==skyflatnum) { wall.ybottom=-255.0f; if ( (backsector->ceilingheight==backsector->floorheight) && (backsector->floorpic==skyflatnum) ) { wall.ytop=(float)backsector->floorheight/MAP_SCALE; SKYTEXTURE(frontsector->sky,backsector->sky); ADDWALL(&wall); } else { if ( (texturetranslation[seg->sidedef->bottomtexture]!=NO_TEXTURE) ) { wall.ytop=(float)frontsector->floorheight/MAP_SCALE; SKYTEXTURE(frontsector->sky,backsector->sky); ADDWALL(&wall); } else if ( (backsector->floorheight >= frontsector->ceilingheight) || (backsector->floorpic != skyflatnum) ) { wall.ytop=(float)backsector->floorheight/MAP_SCALE; SKYTEXTURE(frontsector->sky,backsector->sky); ADDWALL(&wall); } } } #endif if (floor_heightsidedef->bottomtexture], true, false); if ( !temptex ) { temptex = defaultTexture; // it seems some line segments have bad data... } wall.gltexture=temptex; CALC_Y_VALUES(wall, lineheight, floor_height, ceiling_height); CALC_TEX_VALUES_BOTTOM( wall, seg, (LINE->flags & ML_DONTPEGBOTTOM)>0, seg->length, lineheight, floor_height-frontsector->ceilingheight ); ADDWALL(&wall); } } } #undef LINE #undef CALC_Y_VALUES #undef OU #undef OV #undef OV_PEG #undef CALC_TEX_VALUES_TOP #undef CALC_TEX_VALUES_MIDDLE1S #undef CALC_TEX_VALUES_MIDDLE2S #undef CALC_TEX_VALUES_BOTTOM #undef SKYTEXTURE #undef ADDWALL /* TransformAndClipSegment Converts a world coordinate line segment to screen space. Returns false if the segment is off screen. There would be some savings if all the points in a subsector were transformed and clip tested as a unit, instead of as discrete segments. */ boolean TransformAndClipSegment( float v[2][2], float ends[2] ) { float clip[2][4]; // if we are in iphone reverse-landscape mode, we need // to flip the coordinates around float *v0, *v1; if ( reversedLandscape ) { v0 = v[1]; v1 = v[0]; } else { v0 = v[0]; v1 = v[1]; } // transform from model to clip space // because the iPhone screen hardware is portrait mode, // we need to look at the Y axis for the segment ends, // not the X axis. clip[0][1] = v0[0] * glMVPmatrix[1] + v0[1] * glMVPmatrix[2*4+1] + glMVPmatrix[3*4+1]; clip[0][3] = v0[0] * glMVPmatrix[3] + v0[1] * glMVPmatrix[2*4+3] + glMVPmatrix[3*4+3]; clip[1][1] = v1[0] * glMVPmatrix[1] + v1[1] * glMVPmatrix[2*4+1] + glMVPmatrix[3*4+1]; clip[1][3] = v1[0] * glMVPmatrix[3] + v1[1] * glMVPmatrix[2*4+3] + glMVPmatrix[3*4+3]; float d0, d1; // clip to the near plane float nearClip = 0.01f; d0 = clip[0][3] - nearClip; d1 = clip[1][3] - nearClip; if ( d0 < 0 && d1 < 0 ) { // near clipped return false; } if ( d0 < 0 ) { float f = d0 / ( d0 - d1 ); clip[0][1] = clip[0][1] + f * ( clip[1][1] - clip[0][1] ); clip[0][3] = nearClip; } else if ( d1 < 0 ) { float f = d1 / ( d1 - d0 ); clip[1][1] = clip[1][1] + f * ( clip[0][1] - clip[1][1] ); clip[1][3] = nearClip; } if ( clip[0][1] > clip[0][3] ) { // entire segment is off the right side of the screen return false; } if ( clip[1][1] < -clip[1][3] ) { // entire segment is off the left side of the screen return false; } // project for ( int i = 0 ; i < 2 ; i++ ) { float x = viewwidth * ( ( clip[i][1] / clip[i][3] ) * 0.5 + 0.5 ); if ( x < 0 ) { x = 0; } else if ( x > viewwidth ) { x = viewwidth; } ends[i] = x; } // part of the segment is on screen return true; } /* IR_Subsector All possible culling should be performed here, but most calculations should be deferred until draw time, rather than storing intermediate values that are later referenced. Don't make this static, or the compiler inlines it in the recursive node function, which bloats the stack. */ void IR_Subsector(int num) { subsector_t *sub = &subsectors[num]; c_subsectors++; // at this point we know that at least part of the subsector is // not covered in the occlusion array // if the sector that this subsector is a part of has not already had its // planes and sprites added, add them now. sector_t *frontsector = sub->sector; int lightlevel = frontsector->lightlevel+(extralight<<5); // There can be several subsectors in each sector due to non-convex // sectors or BSP splits, but we draw the floors, ceilings and lines // with a single draw call for the entire thing, so ensure that they // are only added once per frame. if ( frontsector->validcount != validcount ) { frontsector->validcount = validcount; c_sectors++; GLFlat flat; flat.sectornum = frontsector->iSectorID; flat.light = lightlevel; flat.uoffs= 0; // no support in standard doom flat.voffs= 0; if ( frontsector->floorheight < viewz ) { if (frontsector->floorpic == skyflatnum) { skyIsVisible = true; } else { // get the texture. flattranslation is maintained by doom and // contains the number of the current animation frame GLTexture *tex = gld_RegisterFlat(flattranslation[frontsector->floorpic], true); if ( tex ) { sectorPlanes[numSectorPlanes].texture = tex; sectorPlanes[numSectorPlanes].ceiling = false; sectorPlanes[numSectorPlanes].sector = frontsector; numSectorPlanes++; } } } if ( frontsector->ceilingheight > viewz ) { if (frontsector->ceilingpic == skyflatnum) { skyIsVisible = true; } else { // get the texture. flattranslation is maintained by doom and // contains the number of the current animation frame GLTexture *tex = gld_RegisterFlat(flattranslation[frontsector->ceilingpic], true); if ( tex ) { sectorPlanes[numSectorPlanes].texture = tex; sectorPlanes[numSectorPlanes].ceiling = true; sectorPlanes[numSectorPlanes].sector = frontsector; numSectorPlanes++; } } } // Add all the sprites in this sector. // It would be better if they were linked into all the subsectors, because // we could do more accurate occlusion culling. With non-convex sectors, // occasionally a sprite will be added in a rear portion of the sector that // would have been occluded away if everything was done in BSP subsector order. for ( mobj_t *thing = frontsector->thinglist; thing; thing = thing->snext) { IR_ProjectSprite( thing, lightlevel ); } } // If a segment in this subsector is not fully occluded, mark // the line that it is a part of as needing to be drawn. Because // we are using a depth buffer, we can draw complete line segments // instead of just segments. for ( int i = 0 ; i < sub->numlines ; i++ ) { seg_t *seg = &segs[sub->firstline+i]; line_t *line = seg->linedef; // Determine if it will completely occlude farther objects. // Given that changing sector heights is much less common than // traversing lines during every render, it would be marginally better if // lines had an "occluder" flag on them that was updated as sectors // moved, but it hardly matters. boolean occluder; if ( seg->backsector == NULL || seg->backsector->floorheight >= seg->backsector->ceilingheight || seg->backsector->floorheight >= seg->frontsector->ceilingheight || seg->backsector->ceilingheight <= seg->frontsector->floorheight ) { // this segment can't be seen past, so fill in the occlusion table occluder = true; } else { // If the line has already been made visible and we don't need to // update the occlusion buffer, we don't need to do anything else here. // This happens when a line is split into multiple segs, and also // when the line is reached from the backsector. In the backsector // case, it would be back-face culled, but this test throws it out // without having to transform and clip the ends. if ( line->validcount == validcount ) { continue; } // check to see if the seg won't draw any walls at all // we won't fill in the occlusion table for this occluder = false; } // transform and clip the two endpoints float v[2][2]; float floatEnds[2]; v[0][0] = -seg->v1->x/MAP_SCALE; v[0][1] = seg->v1->y/MAP_SCALE; v[1][0] = -seg->v2->x/MAP_SCALE; v[1][1] = seg->v2->y/MAP_SCALE; if ( !TransformAndClipSegment( v, floatEnds ) ) { // the line is off to the side or facing away continue; } // Allow segs that we consider to be slightly back // facing to still pass through, because GPU floating // point calculations may not see them exactly the same. if ( floatEnds[0] > floatEnds[1] + 1.0f ) { // back face continue; } // Check it against the occlusion buffer. // Because the perspective divide is not going to be bit-exact between // the CPU and GPU, we check an extra column here. That will result // in an occasional line being drawn that might not need to be, but // it avoids missing columns. int checkMin = floor( floatEnds[0] - 1 ); int checkMax = ceil( floatEnds[1] + 1 ); if ( checkMin < 0 ) { checkMin = 0; } if ( checkMax > viewwidth ) { checkMax = viewwidth; } if ( !memchr( occlusion + checkMin, 0, checkMax - checkMin ) ) { failCount++; // every column it would touch is already solid, so it isn't visible continue; } if ( occluder ) { // It is important to update the occlusion array as individual // segs are processed to maintain pure front to back order. If // the occlusion buffer was updated by complete lines, it would // result in some elements being incorrectly occlusion culled. // Use a consistant fill rule for the occlusion, which is only // referenced by the CPU, and should be water tight. int fillMin = (int) (floatEnds[0]+0.5); int fillMax = (int) (floatEnds[1]+0.5); if ( fillMax > fillMin ) { memset( occlusion + fillMin, 1, fillMax-fillMin ); } } if ( line->validcount == validcount ) { continue; } line->validcount = validcount; // this line can show up on the automap now line->flags |= ML_MAPPED; // Adding a line may generate up to four drawn walls -- a top wall, // a bottom wall, a perforated middle wall, and a sky wall. // Use the complete, unclipped segment for the side IR_AddWall( &seg->sidedef->sideSeg ); } } PUREFUNC static int IR_PointOnSide(fixed_t x, fixed_t y, const node_t *node) { // JDC: these early tests probably aren't worth it on iphone... if (!node->dx) return x <= node->x ? node->dy > 0 : node->dy < 0; if (!node->dy) return y <= node->y ? node->dx < 0 : node->dx > 0; x -= node->x; y -= node->y; // Try to quickly decide by looking at sign bits. if ((node->dy ^ node->dx ^ x ^ y) < 0) return (node->dy ^ x) < 0; // (left is negative) return (__int64_t)y * (__int64_t)node->dx >= (__int64_t)x * (__int64_t)node->dy; } static const int checkcoord[12][4] = // killough -- static const { {3,0,2,1}, {3,0,2,0}, {3,1,2,0}, {0}, {2,0,2,1}, {0,0,0,0}, {3,1,3,0}, {0}, {2,0,3,1}, {2,1,3,1}, {2,1,3,0} }; static boolean IR_IsBBoxCompletelyOccluded(const fixed_t *bspcoord) { // conservatively accept if close to the box, so // we don't need to worry about the near clip plane // in TrnasformAndClipSegment. Mapscale is 128*fracunit // and nearclip is 0.1, so accepting 2 fracunits away works. if ( viewx > bspcoord[BOXLEFT]-2*FRACUNIT && viewx < bspcoord[BOXRIGHT] + 2*FRACUNIT && viewy > bspcoord[BOXBOTTOM]-2*FRACUNIT && viewy < bspcoord[BOXTOP] + 2*FRACUNIT ) { return false; } // Find the corners of the box // that define the edges from current viewpoint. int boxpos = (viewx <= bspcoord[BOXLEFT] ? 0 : viewx < bspcoord[BOXRIGHT ] ? 1 : 2) + (viewy >= bspcoord[BOXTOP ] ? 0 : viewy > bspcoord[BOXBOTTOM] ? 4 : 8); const int *check = checkcoord[boxpos]; float v[2][2]; v[0][0] = -bspcoord[check[0]]/MAP_SCALE; v[0][1] = bspcoord[check[1]]/MAP_SCALE; v[1][0] = -bspcoord[check[2]]/MAP_SCALE; v[1][1] = bspcoord[check[3]]/MAP_SCALE; float ends[2]; if ( !TransformAndClipSegment( v, ends ) ) { // the line is off to the side or facing away return true; } if ( ends[0] >= ends[1] ) { return true; } assert( ends[0] >= 0 ); assert( ends[1] <= viewwidth ); // Check it against the occlusion buffer, with an extra column // of slop to account for GPU / CPU floating point differences. if ( !memchr( occlusion + (int)ends[0], 0, (int)ends[1]-(int)ends[0]+1 ) ) { // every column it would touch is already solid, so it isn't visible return true; } // there are gaps, so we may need to draw something return false; } /* RenderBSPNode Renders all subsectors below a given node, traversing subtree recursively. Because this function is recursive, avoid doing work that would give a large stack frame. Important that the compiler doesn't inline big functions. */ static void IR_RenderBSPNode( int bspnum ) { while (!(bspnum & NF_SUBSECTOR)) { // decision node const node_t *bsp = &nodes[bspnum]; // Decide which side the view point is on. int side = IR_PointOnSide(viewx, viewy, bsp); // check the front space if ( !IR_IsBBoxCompletelyOccluded(bsp->bbox[side]) ) { IR_RenderBSPNode( bsp->children[side] ); } // continue down the back space if ( IR_IsBBoxCompletelyOccluded( bsp->bbox[side^1]) ) { return; } bspnum = bsp->children[side^1]; } // subsector with contents // add all the drawable elements in the subsector if ( bspnum == -1 ) { bspnum = 0; } bspnum &= ~NF_SUBSECTOR; IR_Subsector( bspnum ); } typedef struct { void *texture; void *user; } texSort_t; static int TexSort( const void *a, const void *b ) { if ( ((texSort_t *)a)->texture < ((texSort_t *)b)->texture ) { return -1; } return 1; } int SysIphoneMicroseconds(); void SetImmediateModeGLVertexArrays(); extern float yaw; extern GLTexture **gld_GLTextures; extern GLTexture **gld_GLPatchTextures; extern GLVertex *gld_vertexes; extern GLTexcoord *gld_texcoords; #define MAX_DRAW_INDEXES 0x10000 unsigned short drawIndexes[MAX_DRAW_INDEXES]; int numDrawIndexes; drawVert_t drawVerts[MAX_DRAW_VERTS]; int numDrawVerts; void NewDrawScene(player_t *player) // JDC: new version { int i,k; glDisable( GL_ALPHA_TEST ); glDisable(GL_CULL_FACE); glDisable(GL_FOG); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); // Use the 2x texture combiner mode to allow the game to be brightened up // above the normal maximum. All calculated color values for lighting will // be multiplied by a value ranging from 0.5 for original brightness, up to // 1.0 for 2x brightness. This combine mode will be canceled after all // the 3D and view weapon drawing is completed, so the hud elements are // drawn at normal brightness. glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); glTexEnvf( GL_TEXTURE_ENV, GL_RGB_SCALE, 2.0 ); // opaque skies, flats, and walls write to the depth buffer and don't blend glDisable( GL_BLEND ); glDepthMask( 1 ); // debug tool to check for things being drawn that shouldn't be if ( blendAll ) { glClearColor( 0, 0, 0, 0 ); glClear( GL_COLOR_BUFFER_BIT ); glEnable( GL_BLEND ); glDisable( GL_DEPTH_TEST ); glBlendFunc( GL_ONE, GL_ONE ); skyIsVisible = false; } // debug tool to look for pixel cracks if ( testClear ) { glClearColor( 1, 0, 0, 0 ); glClear( GL_COLOR_BUFFER_BIT ); skyIsVisible = false; } //----------------------------------------- // draw the sky if needed //----------------------------------------- if ( skyIsVisible ) { float s; float y; // Note that these texcoords would have to be corrected // for different screen aspect ratios or fields of view! s = ((yaw+90.0f)/90.0f); y = 1 - 2 * 128.0 / 200; // With identity matricies, the vertex coordinates // can just be in the 0-1 range. glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadIdentity(); glMatrixMode( GL_PROJECTION ); glPushMatrix(); glLoadIdentity(); iphoneRotateForLandscape(); gld_BindTexture( gld_RegisterTexture( skytexture, true, true ) ); glColor4f( 0.5, 0.5, 0.5, 1.0 ); // native texture color, not double bright glBegin(GL_TRIANGLE_STRIP); glTexCoord2f( s, 1 ); glVertex3f(-1,y,0.999); glTexCoord2f( s, 0 ); glVertex3f(-1,1,0.999); glTexCoord2f( s+1, 1 ); glVertex3f(1,y,0.999); glTexCoord2f( s+1, 0 ); glVertex3f(1,1,0.999); glEnd(); // back to the normal drawing matrix glPopMatrix(); glMatrixMode( GL_MODELVIEW ); glPopMatrix(); } // walls and flats use the drawVerts array for everything glTexCoordPointer(2,GL_FLOAT,sizeof(drawVert_t),drawVerts[0].st); glVertexPointer(3,GL_FLOAT,sizeof(drawVert_t),drawVerts[0].xyz); glColorPointer(4,GL_UNSIGNED_BYTE,sizeof(drawVert_t),drawVerts[0].rgba); // everything will draw at full brightness in this case if (player->fixedcolormap) { glColor4f(1.0f, 1.0f, 1.0f, 1.0f ); glDisableClientState(GL_COLOR_ARRAY); } else { glEnableClientState( GL_COLOR_ARRAY ); } int c_drawElements = 0; numDrawIndexes = 0; //----------------------------------------- // draw all the walls, sky walls sorted first //----------------------------------------- // sort the walls by texture texSort_t *wallSort = alloca( sizeof( wallSort[0] ) * gld_drawinfo.num_walls ); for (i=0 ; i < gld_drawinfo.num_walls ; i++ ) { GLWall *wall = &gld_drawinfo.walls[i]; wallSort[i].texture = wall->gltexture; wallSort[i].user = wall; } qsort( wallSort, gld_drawinfo.num_walls, sizeof( wallSort[0] ), TexSort ); // continue building drawverts after the floor and ceiling data int tempDrawVert = numDrawVerts; // alpha tested walls will use half alpha to get the best edging effects glAlphaFunc( GL_GREATER, 0.5 ); for (i=0 ; i < gld_drawinfo.num_walls ; i++ ) { texSort_t *sort = &wallSort[i]; GLWall *wall = sort->user; rendered_segs++; // add two tris to draw the wall drawIndexes[numDrawIndexes+0] = tempDrawVert; drawIndexes[numDrawIndexes+1] = tempDrawVert+1; drawIndexes[numDrawIndexes+2] = tempDrawVert+2; drawIndexes[numDrawIndexes+3] = tempDrawVert+1; drawIndexes[numDrawIndexes+4] = tempDrawVert+2; drawIndexes[numDrawIndexes+5] = tempDrawVert+3; numDrawIndexes += 6; unsigned char rgba[4]; rgba[0] = rgba[1] = rgba[2] = wall->light; rgba[3] = 255; int lightInt = *(int *)rgba; drawVerts[tempDrawVert].st[0] = wall->ul; drawVerts[tempDrawVert].st[1] = wall->vt; drawVerts[tempDrawVert].xyz[0] = -wall->side->sideSeg.v1->x / MAP_SCALE; drawVerts[tempDrawVert].xyz[1] = wall->ytop; drawVerts[tempDrawVert].xyz[2] = wall->side->sideSeg.v1->y / MAP_SCALE; lightInt = FadedLighting( drawVerts[tempDrawVert].xyz[0], drawVerts[tempDrawVert].xyz[2], wall->light ); *(int *)drawVerts[tempDrawVert].rgba = lightInt; tempDrawVert++; drawVerts[tempDrawVert].st[0] = wall->ul; drawVerts[tempDrawVert].st[1] = wall->vb; drawVerts[tempDrawVert].xyz[0] = -wall->side->sideSeg.v1->x / MAP_SCALE; drawVerts[tempDrawVert].xyz[1] = wall->ybottom; drawVerts[tempDrawVert].xyz[2] = wall->side->sideSeg.v1->y / MAP_SCALE; *(int *)drawVerts[tempDrawVert].rgba = lightInt; tempDrawVert++; drawVerts[tempDrawVert].st[0] = wall->ur; drawVerts[tempDrawVert].st[1] = wall->vt; drawVerts[tempDrawVert].xyz[0] = -wall->side->sideSeg.v2->x / MAP_SCALE; drawVerts[tempDrawVert].xyz[1] = wall->ytop; drawVerts[tempDrawVert].xyz[2] = wall->side->sideSeg.v2->y / MAP_SCALE; lightInt = FadedLighting( drawVerts[tempDrawVert].xyz[0], drawVerts[tempDrawVert].xyz[2], wall->light ); *(int *)drawVerts[tempDrawVert].rgba = lightInt; tempDrawVert++; drawVerts[tempDrawVert].st[0] = wall->ur; drawVerts[tempDrawVert].st[1] = wall->vb; drawVerts[tempDrawVert].xyz[0] = -wall->side->sideSeg.v2->x / MAP_SCALE; drawVerts[tempDrawVert].xyz[1] = wall->ybottom; drawVerts[tempDrawVert].xyz[2] = wall->side->sideSeg.v2->y / MAP_SCALE; *(int *)drawVerts[tempDrawVert].rgba = lightInt; tempDrawVert++; // only draw when textures change if ( i == gld_drawinfo.num_walls-1 || sort->texture != (sort+1)->texture ) { if ( numDrawIndexes ) { if ( wall->flag == GLDWF_SKY ) { // we aren't actually drawing anything with this, // we are just masking off areas in the depth // buffer so nothing can overwrite the already // drawn sky image glColorMask( 0, 0, 0, 0 ); } if ( wall->flag == GLDWF_M2S ) { glEnable( GL_ALPHA_TEST ); } if ( wall->gltexture ) { // skies are texture = NULL gld_BindTexture( wall->gltexture ); } glDrawElements( GL_TRIANGLES, numDrawIndexes, GL_UNSIGNED_SHORT, drawIndexes ); if ( wall->flag == GLDWF_M1S ) { glDisable( GL_ALPHA_TEST ); } if ( wall->flag == GLDWF_SKY ) { glColorMask( 1, 1, 1, 1 ); } numDrawIndexes = 0; tempDrawVert = numDrawVerts; c_drawElements++; } } } //----------------------------------------- // draw all the flats // // If we were able to directly fill the GPU command buffers, // we would be using multiple DrawArrays instead of a single DrawElements, // and we would fill the plane height and color in as we copied vertexes // from a single set of verts per sector, but because the driver validation // overhead is the main poison on the iPhone currently, it is better to // duplicate the windings for the floors and ceilings, patch the // vertex data, and generate new index lists to minimize the number of // draw calls. //----------------------------------------- // sort the flats by texture qsort( sectorPlanes, numSectorPlanes, sizeof( sectorPlanes[0] ), TexSort ); // draw them in texture order for (i=0 ; i < numSectorPlanes ; i++ ) { sortSectorPlane_t *sort = §orPlanes[i]; sector_t *sector = sort->sector; drawVert_t *dv = sector->verts[(int)sort->ceiling]; // Patch the height values if they have changed since the last draw. float y = sort->ceiling ? sector->ceilingheight : sector->floorheight; y *= ( 1.0 / MAP_SCALE ); if ( y != dv->xyz[1] ) { for ( int j = 0 ; j < sector->numVerts ; j++ ) { (dv+j)->xyz[1] = y; } } // per-vertex faded light color int light = sector->lightlevel + (extralight<<5); if ( light > 255 ) { light = 255; } for ( int j = 0 ; j < sector->numVerts ; j++ ) { *(int *)(dv+j)->rgba = FadedLighting( (dv+j)->xyz[0], (dv+j)->xyz[2], light ); } // copy the indexes assert( numDrawIndexes + sector->numIndexes < MAX_DRAW_INDEXES ); memcpy( drawIndexes + numDrawIndexes, sector->indexes[(int)sort->ceiling], sector->numIndexes*sizeof(drawIndexes[0]) ); numDrawIndexes += sector->numIndexes; // only change textures when necessary if ( i == numSectorPlanes - 1 || sort->texture != (sort+1)->texture ) { if ( numDrawIndexes ) { gld_BindFlat( sort->texture ); glDrawElements( GL_TRIANGLES, numDrawIndexes, GL_UNSIGNED_SHORT, drawIndexes ); numDrawIndexes = 0; c_drawElements++; } } } glDisableClientState( GL_COLOR_ARRAY ); // back to our immediate mode vertex arrays SetImmediateModeGLVertexArrays(); //----------------------------------------- // draw all the sprites //----------------------------------------- // transparent sprites blend and don't write to the depth buffer glEnable( GL_BLEND ); glDepthMask( 0 ); glEnable( GL_ALPHA_TEST ); // get the screen space vector for sprites float yaws = -sin( yaw * 3.141592657 / 180.0 ); float yawc = -cos( yaw * 3.141592657 / 180.0 ); int c_spriteBind = 0; int c_spriteDraw = 0; while( 1 ) { // pick out the sprites from farthest to nearest fixed_t max_scale=INT_MAX; k=-1; for (i=0 ; i < gld_drawinfo.num_sprites ; i++ ) { GLSprite *sprite = &gld_drawinfo.sprites[i]; if (sprite->scalescale; k=i; } } if ( k == -1 ) { break; } GLSprite *sprite = &gld_drawinfo.sprites[k]; sprite->scale=INT_MAX; if ( sprite->gltexture != last_gltexture ) { c_spriteBind++; } c_spriteDraw++; gld_BindPatch(sprite->gltexture,sprite->cm); if(sprite->shadow) { glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glColor4f(0.2f,0.2f,0.2f,0.33f); glAlphaFunc( GL_GREATER, 0.1 ); // don't alpha test away the blended-down version } else { float flight = (float)sprite->light*(1.0f/255); // We could do the distance-lighting here, but leaving the sprites // brighter is a good accent in most cases. There are a few places // where environmental sprites look a little wrong, but it is probably // better in general. if (player->fixedcolormap) { flight = 1.0; // light amp goggles } glColor4f(flight, flight, flight, 1.0f ); } glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(sprite->ul, sprite->vt); glVertex3f(sprite->x + sprite->x1 * yawc, sprite->y + sprite->y1, sprite->z + sprite->x1 * yaws ); glTexCoord2f(sprite->ur, sprite->vt); glVertex3f(sprite->x + sprite->x2 * yawc, sprite->y + sprite->y1, sprite->z + sprite->x2 * yaws ); glTexCoord2f(sprite->ul, sprite->vb); glVertex3f(sprite->x + sprite->x1 * yawc, sprite->y + sprite->y2, sprite->z + sprite->x1 * yaws ); glTexCoord2f(sprite->ur, sprite->vb); glVertex3f(sprite->x + sprite->x2 * yawc, sprite->y + sprite->y2, sprite->z + sprite->x2 * yaws ); glEnd(); if(sprite->shadow) { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glAlphaFunc( GL_GREATER, 0.5 ); } } glDisable( GL_ALPHA_TEST ); glDepthMask( 1 ); } static float roll = 0.0f; /* JDC static */ float yaw = 0.0f; static float inv_yaw = 0.0f; static float pitch = 0.0f; #define __glPi 3.14159265358979323846 static void infinitePerspective(GLdouble fovy, GLdouble aspect, GLdouble znear) { GLfloat left, right, bottom, top; // JDC: was GLdouble GLfloat m[16]; // JDC: was GLdouble top = znear * tan(fovy * __glPi / 360.0); bottom = -top; left = bottom * aspect; right = top * aspect; //qglFrustum(left, right, bottom, top, znear, zfar); m[ 0] = (2 * znear) / (right - left); m[ 4] = 0; m[ 8] = (right + left) / (right - left); m[12] = 0; m[ 1] = 0; m[ 5] = (2 * znear) / (top - bottom); m[ 9] = (top + bottom) / (top - bottom); m[13] = 0; m[ 2] = 0; m[ 6] = 0; //m[10] = - (zfar + znear) / (zfar - znear); //m[14] = - (2 * zfar * znear) / (zfar - znear); m[10] = -1; m[14] = -2 * znear; m[ 3] = 0; m[ 7] = 0; m[11] = -1; m[15] = 0; glMultMatrixf(m); // JDC: was glMultMatrixd } /* IR_RenderPlayerView Replace the prBoom rendering code with a higher performance version. Most of the fancy new features are gone, because I have no idea what the reight test cases would be for them. */ void IR_RenderPlayerView (player_t* player) { int start = 0; if ( showRenderTime ) { start = SysIphoneMicroseconds(); } viewplayer = player; tic_vars.frac = FRACUNIT; viewx = player->mo->x; viewy = player->mo->y; viewz = player->viewz; viewangle = player->mo->angle; extralight = player->extralight; // gun flashes yaw=270.0f-(float)(viewangle>>ANGLETOFINESHIFT)*360.0f/FINEANGLES; viewsin = finesine[viewangle>>ANGLETOFINESHIFT]; viewcos = finecosine[viewangle>>ANGLETOFINESHIFT]; // IR goggles if (player->fixedcolormap) { fixedcolormap = fullcolormap + player->fixedcolormap*256*sizeof(lighttable_t); } else { fixedcolormap = 0; } // this is used to tell if a line, sector, or sprite is going to be drawn this frame validcount++; // clear the 1D occlusion buffer, set a final guard column to occluded so we can // check an extra pixel to account for slight floating point differences between // the GPU and CPU transformations assert( viewwidth <= MAX_SCREENWIDTH ); memset( occlusion, 0, sizeof( occlusion ) ); occlusion[viewwidth] = 1; float trY ; float xCamera,yCamera; extern int screenblocks; int height; gld_SetPalette(-1); if (screenblocks == 11) height = SCREENHEIGHT; else if (screenblocks == 10) height = SCREENHEIGHT; else height = (screenblocks*SCREENHEIGHT/10) & ~7; glViewport(viewwindowx, SCREENHEIGHT-(height+viewwindowy-((height-viewheight)/2)), viewwidth, height); glScissor(viewwindowx, SCREENHEIGHT-(viewheight+viewwindowy), viewwidth, viewheight); glEnable(GL_SCISSOR_TEST); glEnable(GL_DEPTH_TEST); glDisable(GL_FOG); glShadeModel(GL_SMOOTH); // Player coordinates xCamera=-(float)viewx/MAP_SCALE; yCamera=(float)viewy/MAP_SCALE; trY=(float)viewz/MAP_SCALE; yaw=270.0f-(float)(viewangle>>ANGLETOFINESHIFT)*360.0f/FINEANGLES; inv_yaw=-90.0f+(float)(viewangle>>ANGLETOFINESHIFT)*360.0f/FINEANGLES; #ifdef _DEBUG glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #else glClear(GL_DEPTH_BUFFER_BIT); #endif // To make it easier to accurately mimic the GL model to screen transformation, // this is set up so that the projection transformation is also done in the // modelview matrix, leaving the projection matrix as an identity. This means // that things done in eye space, like lighting and fog, won't work, but // we don't need them. glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // make the 320x480 hardware seem like 480x320 in two different orientations // and note if the occlusion segmenrs need to be reversed reversedLandscape = iphoneRotateForLandscape(); infinitePerspective(64.0f, 320.0f/200.0f, 5.0f/100.0f); glRotatef(roll, 0.0f, 0.0f, 1.0f); glRotatef(pitch, 1.0f, 0.0f, 0.0f); glRotatef(yaw, 0.0f, 1.0f, 0.0f); glTranslatef(-xCamera, -trY, -yCamera); // read back the matrix so we can do exact calculations that match // what GL is doing. It would probably be better to build the matricies // ourselves and just do a loadMatrix... glGetFloatv( GL_MODELVIEW_MATRIX, glMVPmatrix ); // setup the vector for calculating light fades, which is just a scale // of the forward vector lightingVector[0] = lightDistance * glMVPmatrix[2]; lightingVector[1] = lightDistance * glMVPmatrix[10]; lightingVector[2] = lightDistance * glMVPmatrix[14]; rendermarker++; gld_drawinfo.num_walls=0; gld_drawinfo.num_flats=0; gld_drawinfo.num_sprites=0; gld_drawinfo.num_drawitems=0; c_occludedSprites = 0; c_sectors = 0; c_subsectors = 0; numSectorPlanes = 0; failCount = 0; // Find everything we need to draw, but don't draw anything yet, // because we want to sort by texture to reduce GL driver overhead. IR_RenderBSPNode( numnodes-1 ); NewDrawScene(player); gld_EndDrawScene(); if ( showRenderTime ) { int end = SysIphoneMicroseconds(); printf( "%i usec\n", end - start ); } }