Пример #1
0
    void draw(SphereComponent hemisphere, Sky const &sky, int firstActiveLayer,
              LayerData const *layerData) const
    {
        DENG2_ASSERT(layerData);

        if(verts.isEmpty()) return;
        if(firstActiveLayer < 0) return;

        bool const yflip = (hemisphere == LowerHemisphere);
        if(yflip)
        {
            // The lower hemisphere must be flipped.
            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            glScalef(1.0f, -1.0f, 1.0f);
        }

        // First draw the cap and the background for fadeouts, if needed.
        bool drawFadeOut = true;
        drawCap(chooseCapColor(hemisphere, sky.layer(firstActiveLayer), &drawFadeOut), drawFadeOut);

        for(int i = firstActiveLayer; i < MAX_LAYERS; ++i)
        {
            SkyLayer const *skyLayer = sky.layer(i);
            LayerData const &ldata   = layerData[i];

            if(!ldata.active) continue;

            TextureVariant *layerTex = nullptr;
            if(Material *mat = chooseMaterialForSkyLayer(skyLayer))
            {
                MaterialSnapshot const &ms = mat->prepare(SkyDrawable::layerMaterialSpec(skyLayer->isMasked()));

                layerTex = &ms.texture(MTU_PRIMARY);
                GL_BindTexture(layerTex);

                glEnable(GL_TEXTURE_2D);
                glMatrixMode(GL_TEXTURE);
                glPushMatrix();
                glLoadIdentity();
                Vector2i const &texSize = layerTex->generalCase().dimensions();
                if(texSize.x > 0)
                {
                    glTranslatef(ldata.offset / texSize.x, 0, 0);
                    glScalef(1024.f / texSize.x, 1, 1);
                }
                if(yflip)
                {
                    glScalef(1, -1, 1);
                    glTranslatef(0, -1, 0);
                }
            }
            else
            {
                GL_SetNoTexture();
            }

#define WRITESKYVERTEX(r_, c_) { \
    svtx = &vertex(r_, c_); \
    if(layerTex) \
    { \
       glTexCoord2f((c_) / float(columns), (r_) / float(rows)); \
    } \
    if(drawFadeOut) \
    { \
        if((r_) == 0) glColor4f(1, 1, 1, 0); \
        else          glColor3f(1, 1, 1); \
    } \
    else \
    { \
        if((r_) == 0) glColor3f(0, 0, 0); \
        else          glColor3f(1, 1, 1); \
    } \
    glVertex3f(svtx->x, svtx->y, svtx->z); \
}

            Vector3f const *svtx;
            for(int r = 0; r < rows; ++r)
            {
                glBegin(GL_TRIANGLE_STRIP);
                WRITESKYVERTEX(r, 0);
                WRITESKYVERTEX(r + 1, 0);
                for(int c = 1; c <= columns; ++c)
                {
                    WRITESKYVERTEX(r, c);
                    WRITESKYVERTEX(r + 1, c);
                }
                glEnd();
            }

            if(layerTex)
            {
                glMatrixMode(GL_TEXTURE);
                glPopMatrix();
                glDisable(GL_TEXTURE_2D);
            }

#undef WRITESKYVERTEX
        }

        if(yflip)
        {
            glMatrixMode(GL_MODELVIEW);
            glPopMatrix();
        }
    }
Пример #2
0
void R_ProjectSprite(mobj_t &mob)
{
    /// @todo Lots of stuff here! This needs to be broken down into multiple functions
    /// and/or classes that handle preprocessing of visible entities. Keep in mind that
    /// data/state can persist across frames in the mobjs' private data. -jk

    // Not all objects can/will be visualized. Skip this object if:
    // ...hidden?
    if((mob.ddFlags & DDMF_DONTDRAW)) return;
    // ...not linked into the map?
    if(!Mobj_HasSubspace(mob)) return;
    // ...in an invalid state?
    if(!mob.state || !runtimeDefs.states.indexOf(mob.state)) return;
    // ...no sprite frame is defined?
    Record *spriteRec = Mobj_SpritePtr(mob);
    if(!spriteRec) return;
    // ...fully transparent?
    dfloat const alpha = Mobj_Alpha(mob);
    if(alpha <= 0) return;
    // ...origin lies in a sector with no volume?
    ConvexSubspace &subspace = Mobj_BspLeafAtOrigin(mob).subspace();
    SectorCluster &cluster   = subspace.cluster();
    if(!cluster.hasWorldVolume()) return;

    ClientMobjThinkerData const *mobjData = THINKER_DATA_MAYBE(mob.thinker, ClientMobjThinkerData);

    // Determine distance to object.
    Vector3d const moPos = mobjOriginSmoothed(&mob);
    coord_t const distFromEye = Rend_PointDist2D(moPos);

    // Should we use a 3D model?
    ModelDef *mf = nullptr, *nextmf = nullptr;
    dfloat interp = 0;

    MobjAnimator const *animator = nullptr; // GL2 model present?

    if(useModels)
    {
        mf = Mobj_ModelDef(mob, &nextmf, &interp);
        if(mf)
        {
            // Use a sprite if the object is beyond the maximum model distance.
            if(maxModelDistance && !(mf->flags & MFF_NO_DISTANCE_CHECK)
                    && distFromEye > maxModelDistance)
            {
                mf = nextmf = nullptr;
                interp = -1;
            }
        }

        if(mobjData)
        {
            animator = mobjData->animator();
        }
    }

    bool const hasModel = (mf || animator);

    // Decide which material to use according to the sprite's angle and position
    // relative to that of the viewer.
    Material *mat = nullptr;
    bool matFlipS = false;
    bool matFlipT = false;

    defn::Sprite sprite(*spriteRec);
    try
    {
        Record const &spriteView = sprite.nearestView(mob.angle, R_ViewPointToAngle(mob.origin), !!mf);
        mat      = resSys().materialPtr(de::Uri(spriteView.gets("material"), RC_NULL));
        matFlipS = spriteView.getb("mirrorX");
    }
    catch(defn::Sprite::MissingViewError const &er)
    {
        // Log but otherwise ignore this error.
        LOG_GL_WARNING("Projecting sprite '%i' frame '%i': %s")
                << mob.sprite << mob.frame << er.asText();
    }
    if(!mat) return;
    MaterialAnimator &matAnimator = mat->getAnimator(Rend_SpriteMaterialSpec(mob.tclass, mob.tmap));

    // Ensure we've up to date info about the material.
    matAnimator.prepare();

    Vector2i const &matDimensions = matAnimator.dimensions();
    TextureVariant *tex           = matAnimator.texUnit(MaterialAnimator::TU_LAYER0).texture;

    // A valid sprite texture in the "Sprites" scheme is required.
    if(!tex || tex->base().manifest().schemeName().compareWithoutCase("Sprites"))
    {
        return;
    }

    bool const fullbright = ((mob.state->flags & STF_FULLBRIGHT) != 0 || levelFullBright);
    // Align to the view plane? (Means scaling down Z with models)
    bool const viewAlign  = (!mf && ((mob.ddFlags & DDMF_VIEWALIGN) || alwaysAlign == 1))
                            || alwaysAlign == 3;

    // Perform visibility checking by projecting a view-aligned line segment
    // relative to the viewer and determining if the whole of the segment has
    // been clipped away according to the 360 degree angle clipper.
    coord_t const visWidth = Mobj_VisualRadius(mob) * 2;  /// @todo ignorant of rotation...
    Vector2d v1, v2;
    R_ProjectViewRelativeLine2D(moPos, mf || viewAlign, visWidth,
                                (mf? 0 : coord_t(-tex->base().origin().x) - (visWidth / 2.0f)),
                                v1, v2);

    // Not visible?
    if(!rendSys().angleClipper().checkRangeFromViewRelPoints(v1, v2))
    {
        coord_t const MAX_OBJECT_RADIUS = 128;

        // Sprite visibility is absolute.
        if(!hasModel) return;

        // If the model is close to the viewpoint we should still to draw it,
        // otherwise large models are likely to disappear too early.
        viewdata_t const *viewData = &viewPlayer->viewport();
        Vector2d delta(distFromEye, moPos.z + (mob.height / 2) - viewData->current.origin.z);
        if(M_ApproxDistance(delta.x, delta.y) > MAX_OBJECT_RADIUS)
            return;
    }

    // Store information in a vissprite.
    vissprite_t *vis = R_NewVisSprite(animator? VSPR_MODEL_GL2 :
                                      mf? VSPR_MODEL :
                                      VSPR_SPRITE);

    vis->pose.origin   = moPos;
    vis->pose.distance = distFromEye;

    // The Z origin of the visual should match that of the mobj. When smoothing
    // is enabled this requires examining all touched sector planes in the vicinity.
    Plane &floor     = cluster.visFloor();
    Plane &ceiling   = cluster.visCeiling();
    bool floorAdjust = false;
    if(!Mobj_OriginBehindVisPlane(&mob))
    {
        floorAdjust = de::abs(floor.heightSmoothed() - floor.height()) < 8;
        findMobjZOrigin(mob, floorAdjust, *vis);
    }

    coord_t topZ = vis->pose.origin.z + -tex->base().origin().y;  // global z top

    // Determine floor clipping.
    coord_t floorClip = mob.floorClip;
    if(mob.ddFlags & DDMF_BOB)
    {
        // Bobbing is applied using floorclip.
        floorClip += Mobj_BobOffset(mob);
    }

    // Determine angles.
    /// @todo Surely this can be done in a subclass/function. -jk
    dfloat yaw = 0, pitch = 0;
    if(animator)
    {
        // TODO: More angle options with GL2 models.

        yaw = Mobj_AngleSmoothed(&mob) / dfloat( ANGLE_MAX ) * -360;
    }
    else if(mf)
    {
        // Determine the rotation angles (in degrees).
        if(mf->testSubFlag(0, MFF_ALIGN_YAW))
        {
            // Transform the origin point.
            viewdata_t const *viewData = &viewPlayer->viewport();
            Vector2d delta(moPos.y - viewData->current.origin.y,
                           moPos.x - viewData->current.origin.x);

            yaw = 90 - (BANG2RAD(bamsAtan2(delta.x * 10, delta.y * 10)) - PI / 2) / PI * 180;
        }
        else if(mf->testSubFlag(0, MFF_SPIN))
        {
            yaw = modelSpinSpeed * 70 * App_WorldSystem().time() + MOBJ_TO_ID(&mob) % 360;
        }
        else if(mf->testSubFlag(0, MFF_MOVEMENT_YAW))
        {
            yaw = R_MovementXYYaw(mob.mom[0], mob.mom[1]);
        }
        else
        {
            yaw = Mobj_AngleSmoothed(&mob) / dfloat( ANGLE_MAX ) * -360;
        }

        // How about a unique offset?
        if(mf->testSubFlag(0, MFF_IDANGLE))
        {
            yaw += MOBJ_TO_ID(&mob) % 360;  // arbitrary
        }

        if(mf->testSubFlag(0, MFF_ALIGN_PITCH))
        {
            viewdata_t const *viewData = &viewPlayer->viewport();
            Vector2d delta(vis->pose.midZ() - viewData->current.origin.z, distFromEye);

            pitch = -BANG2DEG(bamsAtan2(delta.x * 10, delta.y * 10));
        }
        else if(mf->testSubFlag(0, MFF_MOVEMENT_PITCH))
        {
            pitch = R_MovementXYZPitch(mob.mom[0], mob.mom[1], mob.mom[2]);
        }
        else
        {
            pitch = 0;
        }
    }

    // Determine possible short-range visual offset.
    Vector3d visOff;
    if((hasModel && useSRVO > 0) || (!hasModel && useSRVO > 1))
    {
        if(mob.tics >= 0)
        {
            visOff = Vector3d(mob.srvo) * (mob.tics - frameTimePos) / (float) mob.state->tics;
        }

        if(!INRANGE_OF(mob.mom[0], 0, NOMOMENTUM_THRESHOLD) ||
                !INRANGE_OF(mob.mom[1], 0, NOMOMENTUM_THRESHOLD) ||
                !INRANGE_OF(mob.mom[2], 0, NOMOMENTUM_THRESHOLD))
        {
            // Use the object's speed to calculate a short-range offset.
            visOff += Vector3d(mob.mom) * frameTimePos;
        }
    }

    // Will it be drawn as a 2D sprite?
    if(!hasModel)
    {
        bool const brightShadow = (mob.ddFlags & DDMF_BRIGHTSHADOW) != 0;
        bool const fitTop       = (mob.ddFlags & DDMF_FITTOP)       != 0;
        bool const fitBottom    = (mob.ddFlags & DDMF_NOFITBOTTOM)  == 0;

        // Additive blending?
        blendmode_t blendMode;
        if(brightShadow)
        {
            blendMode = BM_ADD;
        }
        // Use the "no translucency" blending mode?
        else if(noSpriteTrans && alpha >= .98f)
        {
            blendMode = BM_ZEROALPHA;
        }
        else
        {
            blendMode = BM_NORMAL;
        }

        // We must find the correct positioning using the sector floor
        // and ceiling heights as an aid.
        if(matDimensions.y < ceiling.heightSmoothed() - floor.heightSmoothed())
        {
            // Sprite fits in, adjustment possible?
            if(fitTop && topZ > ceiling.heightSmoothed())
                topZ = ceiling.heightSmoothed();

            if(floorAdjust && fitBottom && topZ - matDimensions.y < floor.heightSmoothed())
                topZ = floor.heightSmoothed() + matDimensions.y;
        }
        // Adjust by the floor clip.
        topZ -= floorClip;

        Vector3d const origin(vis->pose.origin.x, vis->pose.origin.y, topZ - matDimensions.y / 2.0f);
        Vector4f ambientColor;
        duint vLightListIdx = 0;
        evaluateLighting(origin, subspace, vis->pose.distance, fullbright,
                         ambientColor, &vLightListIdx);

        // Apply uniform alpha (overwritting intensity factor).
        ambientColor.w = alpha;

        VisSprite_SetupSprite(vis,
                              VisEntityPose(origin, visOff, viewAlign),
                              VisEntityLighting(ambientColor, vLightListIdx),
                              floor.heightSmoothed(), ceiling.heightSmoothed(),
                              floorClip, topZ, *mat, matFlipS, matFlipT, blendMode,
                              mob.tclass, mob.tmap,
                              &Mobj_BspLeafAtOrigin(mob),
                              floorAdjust, fitTop, fitBottom);
    }
    else // It will be drawn as a 3D model.
    {
        Vector4f ambientColor;
        duint vLightListIdx = 0;
        evaluateLighting(vis->pose.origin, subspace, vis->pose.distance,
                         fullbright, ambientColor, &vLightListIdx);

        // Apply uniform alpha (overwritting intensity factor).
        ambientColor.w = alpha;

        if(animator)
        {
            // Set up a GL2 model for drawing.
            vis->pose = VisEntityPose(vis->pose.origin,
                                      Vector3d(visOff.x, visOff.y, visOff.z - floorClip),
                                      viewAlign, topZ, yaw, 0, pitch, 0);
            vis->light = VisEntityLighting(ambientColor, vLightListIdx);

            vis->data.model2.object   = &mob;
            vis->data.model2.animator = animator;
            vis->data.model2.model    = &animator->model();
            vis->data.model2.auxData  = &mobjData->auxiliaryModelData();
        }
        else
        {
            DENG2_ASSERT(mf);
            VisSprite_SetupModel(vis,
                                 VisEntityPose(vis->pose.origin,
                                               Vector3d(visOff.x, visOff.y, visOff.z - floorClip),
                                               viewAlign, topZ, yaw, 0, pitch, 0),
                                 VisEntityLighting(ambientColor, vLightListIdx),
                                 mf, nextmf, interp,
                                 mob.thinker.id, mob.selector,
                                 &Mobj_BspLeafAtOrigin(mob),
                                 mob.ddFlags, mob.tmap,
                                 fullbright && !(mf && mf->testSubFlag(0, MFF_DIM)), false);
        }
    }

    // Do we need to project a flare source too?
    if(mob.lumIdx != Lumobj::NoIndex && haloMode > 0)
    {
        /// @todo mark this light source visible for LensFx
        try
        {
            Record const &spriteView = sprite.nearestView(mob.angle, R_ViewPointToAngle(mob.origin));

            // Lookup the Material for this Sprite and prepare the animator.
            MaterialAnimator &matAnimator = resSys().material(de::Uri(spriteView.gets("material"), RC_NULL))
                                            .getAnimator(Rend_SpriteMaterialSpec(mob.tclass, mob.tmap));
            matAnimator.prepare();

            Vector2i const &matDimensions = matAnimator.dimensions();
            TextureVariant *tex           = matAnimator.texUnit(MaterialAnimator::TU_LAYER0).texture;

            // A valid sprite texture in the "Sprites" scheme is required.
            if(!tex || tex->base().manifest().schemeName().compareWithoutCase("Sprites"))
            {
                return;
            }

            auto const *pl = (pointlight_analysis_t const *) tex->base().analysisDataPointer(Texture::BrightPointAnalysis);
            DENG2_ASSERT(pl);

            Lumobj const &lob = cluster.sector().map().lumobj(mob.lumIdx);
            vissprite_t *vis  = R_NewVisSprite(VSPR_FLARE);

            vis->pose.distance = distFromEye;

            // Determine the exact center of the flare.
            vis->pose.origin = moPos + visOff;
            vis->pose.origin.z += lob.zOffset();

            dfloat flareSize = pl->brightMul;
            // X offset to the flare position.
            dfloat xOffset = matDimensions.x * pl->originX - -tex->base().origin().x;

            // Does the mobj have an active light definition?
            ded_light_t const *def = (mob.state? runtimeDefs.stateInfo[runtimeDefs.states.indexOf(mob.state)].light : 0);
            if(def)
            {
                if(def->size)
                    flareSize = def->size;
                if(def->haloRadius)
                    flareSize = def->haloRadius;
                if(def->offset[0])
                    xOffset = def->offset[0];

                vis->data.flare.flags = def->flags;
            }

            vis->data.flare.size = flareSize * 60 * (50 + haloSize) / 100.0f;
            if(vis->data.flare.size < 8)
                vis->data.flare.size = 8;

            // Color is taken from the associated lumobj.
            V3f_Set(vis->data.flare.color, lob.color().x, lob.color().y, lob.color().z);

            vis->data.flare.factor = mob.haloFactors[DoomsdayApp::players().indexOf(viewPlayer)];
            vis->data.flare.xOff = xOffset;
            vis->data.flare.mul = 1;
            vis->data.flare.tex = 0;

            if(def && def->flare)
            {
                de::Uri const &flaremapResourceUri = *def->flare;
                if(flaremapResourceUri.path().toStringRef().compareWithoutCase("-"))
                {
                    vis->data.flare.tex = GL_PrepareFlaremap(flaremapResourceUri);
                }
            }
        }
        catch(defn::Sprite::MissingViewError const &er)
        {
            // Log but otherwise ignore this error.
            LOG_GL_WARNING("Projecting flare source for sprite '%i' frame '%i': %s")
                    << mob.sprite << mob.frame << er.asText();
        }
        catch(res::System::MissingResourceManifestError const &er)
        {
            // Log but otherwise ignore this error.
            LOG_GL_WARNING("Projecting flare source for sprite '%i' frame '%i': %s")
                    << mob.sprite << mob.frame << er.asText();
        }
    }
}
Пример #3
0
void Rend_DrawSprite(vissprite_t const &spr)
{
    drawspriteparams_t const &parm = *VS_SPRITE(&spr);

    DENG_ASSERT_IN_MAIN_THREAD();
    DENG_ASSERT_GL_CONTEXT_ACTIVE();

    TextureVariant *tex = nullptr;
    Vector2f size;
    dfloat viewOffsetX = 0;  ///< View-aligned offset to center point.
    dfloat s = 1, t = 1;     ///< Bottom right coords.

    // Many sprite properties are inherited from the material.
    if(MaterialAnimator *matAnimator = parm.matAnimator)
    {
        // Ensure we have up to date info about the material.
        matAnimator->prepare();

        tex = matAnimator->texUnit(MaterialAnimator::TU_LAYER0).texture;
        dint const texBorder = tex->spec().variant.border;

        size        = matAnimator->dimensions() + Vector2i(texBorder * 2, texBorder * 2);
        viewOffsetX = -size.x / 2 + -tex->base().origin().x;

        tex->glCoords(&s, &t);
    }

    // We may want to draw using another material variant instead.
    if(renderTextures == 2)
    {
        // For lighting debug, render all solid surfaces using the gray texture.
        Material &debugMaterial       = resSys().material(de::Uri("System", Path("gray")));
        MaterialAnimator &matAnimator = debugMaterial.getAnimator(Rend_SpriteMaterialSpec());

        // Ensure we have up to date info about the material.
        matAnimator.prepare();

        tex = matAnimator.texUnit(MaterialAnimator::TU_LAYER0).texture;
    }

    if(renderTextures)
    {
        GL_BindTexture(tex);
        glEnable(GL_TEXTURE_2D);
    }
    else
    {
        GL_SetNoTexture();
    }

    // Coordinates to the center of the sprite (game coords).
    coord_t spriteCenter[3] = { spr.pose.origin[0] + spr.pose.srvo[0],
                                spr.pose.origin[1] + spr.pose.srvo[1],
                                spr.pose.origin[2] + spr.pose.srvo[2] };

    coord_t v1[3], v2[3], v3[3], v4[3];
    R_ProjectViewRelativeLine2D(spriteCenter, spr.pose.viewAligned,
                                size.x, viewOffsetX, v1, v4);

    v2[0] = v1[0];
    v2[1] = v1[1];
    v3[0] = v4[0];
    v3[1] = v4[1];

    v1[2] = v4[2] = spriteCenter[2] - size.y / 2;
    v2[2] = v3[2] = spriteCenter[2] + size.y / 2;

    // Calculate the surface normal.
    coord_t surfaceNormal[3];
    V3d_PointCrossProduct(surfaceNormal, v2, v1, v3);
    V3d_Normalize(surfaceNormal);

/*#if _DEBUG
    // Draw the surface normal.
    glBegin(GL_LINES);
    glColor4f(1, 0, 0, 1);
    glVertex3f(spriteCenter[0], spriteCenter[2], spriteCenter[1]);
    glColor4f(1, 0, 0, 0);
    glVertex3f(spriteCenter[0] + surfaceNormal[0] * 10,
               spriteCenter[2] + surfaceNormal[2] * 10,
               spriteCenter[1] + surfaceNormal[1] * 10);
    glEnd();
#endif*/

    // All sprite vertices are co-plannar, so just copy the surface normal.
    // @todo: Can we do something better here?
    dgl_color_t quadColors[4];
    dgl_vertex_t quadNormals[4];
    for(dint i = 0; i < 4; ++i)
    {
        V3f_Copyd(quadNormals[i].xyz, surfaceNormal);
    }

    if(!spr.light.vLightListIdx)
    {
        // Lit uniformly.
        applyUniformColor(4, quadColors, &spr.light.ambientColor[0]);
    }
    else
    {
        // Lit normally.
        Spr_VertexColors(4, quadColors, quadNormals, spr.light.vLightListIdx,
                         spriteLight + 1, &spr.light.ambientColor[0]);
    }

    // Do we need to do some aligning?
    bool restoreMatrix = false;
    bool restoreZ      = false;
    if(spr.pose.viewAligned || alwaysAlign >= 2)
    {
        // We must set up a modelview transformation matrix.
        restoreMatrix = true;
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();

        // Rotate around the center of the sprite.
        glTranslatef(spriteCenter[0], spriteCenter[2], spriteCenter[1]);
        if(!spr.pose.viewAligned)
        {
            dfloat s_dx = v1[0] - v2[0];
            dfloat s_dy = v1[1] - v2[1];

            if(alwaysAlign == 2)
            {
                // Restricted camera alignment.
                dfloat dx = spriteCenter[0] - vOrigin.x;
                dfloat dy = spriteCenter[1] - vOrigin.z;
                dfloat spriteAngle = BANG2DEG(
                    bamsAtan2(spriteCenter[2] - vOrigin.y, std::sqrt(dx * dx + dy * dy)));

                if(spriteAngle > 180)
                    spriteAngle -= 360;

                if(fabs(spriteAngle) > maxSpriteAngle)
                {
                    dfloat turnAngle = (spriteAngle > 0? spriteAngle - maxSpriteAngle :
                                                         spriteAngle + maxSpriteAngle);

                    // Rotate along the sprite edge.
                    glRotatef(turnAngle, s_dx, 0, s_dy);
                }
            }
            else
            {
                // Restricted view plane alignment.
                // This'll do, for now... Really it should notice both the
                // sprite angle and vpitch.
                glRotatef(vpitch * .5f, s_dx, 0, s_dy);
            }
        }
        else
        {
            // Normal rotation perpendicular to the view plane.
            glRotatef(vpitch, viewsidex, 0, viewsidey);
        }
        glTranslatef(-spriteCenter[0], -spriteCenter[2], -spriteCenter[1]);
    }

    // Need to change blending modes?
    if(parm.blendMode != BM_NORMAL)
    {
        GL_BlendMode(parm.blendMode);
    }

    // Transparent sprites shouldn't be written to the Z buffer.
    if(parm.noZWrite || spr.light.ambientColor[3] < .98f ||
       !(parm.blendMode == BM_NORMAL || parm.blendMode == BM_ZEROALPHA))
    {
        restoreZ = true;
        glDepthMask(GL_FALSE);
    }

    dgl_vertex_t vs[4], *v = vs;
    dgl_texcoord_t tcs[4], *tc = tcs;

    //  1---2
    //  |   |  Vertex layout.
    //  0---3

    v[0].xyz[0] = v1[0];
    v[0].xyz[1] = v1[2];
    v[0].xyz[2] = v1[1];

    v[1].xyz[0] = v2[0];
    v[1].xyz[1] = v2[2];
    v[1].xyz[2] = v2[1];

    v[2].xyz[0] = v3[0];
    v[2].xyz[1] = v3[2];
    v[2].xyz[2] = v3[1];

    v[3].xyz[0] = v4[0];
    v[3].xyz[1] = v4[2];
    v[3].xyz[2] = v4[1];

    tc[0].st[0] = s *  (parm.matFlip[0]? 1:0);
    tc[0].st[1] = t * (!parm.matFlip[1]? 1:0);
    tc[1].st[0] = s *  (parm.matFlip[0]? 1:0);
    tc[1].st[1] = t *  (parm.matFlip[1]? 1:0);
    tc[2].st[0] = s * (!parm.matFlip[0]? 1:0);
    tc[2].st[1] = t *  (parm.matFlip[1]? 1:0);
    tc[3].st[0] = s * (!parm.matFlip[0]? 1:0);
    tc[3].st[1] = t * (!parm.matFlip[1]? 1:0);

    drawQuad(v, quadColors, tc);

    if(renderTextures)
    {
        glDisable(GL_TEXTURE_2D);
    }

    if(devMobjVLights && spr.light.vLightListIdx)
    {
        // Draw the vlight vectors, for debug.
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_CULL_FACE);

        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();

        glTranslatef(spr.pose.origin[0], spr.pose.origin[2], spr.pose.origin[1]);

        coord_t const distFromViewer = de::abs(spr.pose.distance);
        rendSys().forAllVectorLights(spr.light.vLightListIdx, [&distFromViewer] (VectorLightData const &vlight)
        {
            if(distFromViewer < 1600 - 8)
            {
                Rend_DrawVectorLight(vlight, 1 - distFromViewer / 1600);
            }
            return LoopContinue;
        });

        glMatrixMode(GL_MODELVIEW);
        glPopMatrix();

        glEnable(GL_CULL_FACE);
        glEnable(GL_DEPTH_TEST);
    }

    // Need to restore the original modelview matrix?
    if(restoreMatrix)
    {
        glPopMatrix();
    }

    // Change back to normal blending?
    if(parm.blendMode != BM_NORMAL)
    {
        GL_BlendMode(BM_NORMAL);
    }

    // Enable Z-writing again?
    if(restoreZ)
    {
        glDepthMask(GL_TRUE);
    }
}