shithub: qk1

Download patch

ref: 7240eb5e3f0f766939ab5f14add6384b286d253a
parent: acad4d590f4d8484a9e5ec692bc57dc3dddacd8e
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Thu Oct 26 22:01:46 EDT 2023

.alpha: part 2 - drawing/blending

--- a/cl_parse.c
+++ b/cl_parse.c
@@ -590,6 +590,7 @@
 
 // copy it to the current state
 	ent->model = cl.model_precache[ent->baseline.modelindex];
+	ent->alpha = ent->baseline.alpha;
 	ent->frame = ent->baseline.frame;
 	ent->colormap = vid.colormap;
 	ent->skinnum = ent->baseline.skin;
--- /dev/null
+++ b/d_alpha.c
@@ -1,0 +1,65 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "quakedef.h"
+#include "fns.h"
+
+/* FIXME(sigrid): this is super dumb.
+ *
+ * how it SHOULD be done:
+ * 1) no point in 256 alphamaps, a'=255-a → (x->a->y) ≡ (y->a'->x)
+ * 1.1) these are 8-bit colors, no way there are *different* variations for each alpha
+ *      eg: x=3, y=9 → a≠a' → there is a very high chance (x->a->y) = (x->a'->y) if a is close to a'
+ * 2) use k-d (3-d in this case) tree to look up colors faster
+ * 3) if it's fast enough, can build all alpha maps just once on startup
+ * 4) try different color models, this alpha blending looks like crap with liquids
+ */
+byte *alphamap[256];
+
+void
+buildalpha(int alpha)
+{
+	extern s32int fbpal[256];
+	int a, b;
+	byte *ca, *cb, *p;
+	int rr, gg, bb;
+	int i, dst, x, best;
+
+	if(alphamap[alpha] != nil)
+		return;
+	alphamap[alpha] = malloc(256*256);
+	p = (byte*)fbpal;
+	ca = p;
+	for(a = 0; a < 256; a++, ca += 4){
+		cb = p;
+		for(b = 0; b < 256; b++, cb++){
+			rr = (alpha*ca[0] + (255 - alpha)*(*cb++))>>8;
+			gg = (alpha*ca[1] + (255 - alpha)*(*cb++))>>8;
+			bb = (alpha*ca[2] + (255 - alpha)*(*cb++))>>8;
+			dst = 9999999;
+			best = 255;
+			for(i = 0; i < 768; i += 4){
+				x = (rr-p[i+0])*(rr-p[i+0]) +
+					(gg-p[i+1])*(gg-p[i+1]) +
+					(bb-p[i+2])*(bb-p[i+2]);
+				if(x < dst){
+					dst = x;
+					best = i;
+				}
+			}
+			alphamap[alpha][a<<8 | b] = best/4;
+		}
+	}
+}
+
+float
+alphafor(int flags)
+{
+	if(flags & (SURF_TELE|SURF_FENCE))
+		return 1.0;
+	if(flags & SURF_LAVA)
+		return r_lavaalpha.value;
+	if(flags & SURF_SLIME)
+		return r_slimealpha.value;
+	return r_wateralpha.value;
+}
--- a/d_edge.c
+++ b/d_edge.c
@@ -144,18 +144,6 @@
 	bbextentt = ((pface->extents[1] << 16) >> miplevel) - 1;
 }
 
-static float
-alphafor(surf_t *s)
-{
-	if((s->flags & (SURF_LAVA|SURF_SLIME|SURF_TELE)) == 0)
-		return r_wateralpha.value;
-	if(s->flags & SURF_LAVA)
-		return r_lavaalpha.value;
-	if(s->flags & SURF_SLIME)
-		return r_slimealpha.value;
-	return 1.0;
-}
-
 /*
 ==============
 D_DrawSurfaces
@@ -168,6 +156,7 @@
 	surfcache_t		*pcurrentcache;
 	vec3_t			world_transformed_modelorg;
 	vec3_t			local_modelorg;
+	byte			alpha;
 
 	currententity = &cl_entities[0];
 	TransformVector (modelorg, transformed_modelorg);
@@ -196,8 +185,16 @@
 			if (!s->spans)
 				continue;
 
-			if((s->flags & SURF_TRANS) ^ r_drawflags)
+			if((surfdrawflags(s) | entdrawflags(s->entity)) ^ r_drawflags)
 				continue;
+			alpha = 255;
+			if(enthasalpha(s->entity) && s->entity->alpha != 255)
+				alpha = s->entity->alpha;
+			else if(s->flags & SURF_TRANS)
+				alpha *= alphafor(s->flags);
+			if(alpha < 1)
+				alpha = 255;
+			buildalpha(alpha);
 
 			r_drawnpolycount++;
 
@@ -245,7 +242,7 @@
 				}
 
 				D_CalcGradients (pface);
-				Turbulent8 (s->spans, alphafor(s));
+				Turbulent8 (s->spans, alpha);
 				D_DrawZSpans (s->spans);
 
 				if (s->insubmodel)
@@ -296,11 +293,11 @@
 				D_CalcGradients (pface);
 
 				if(s->flags & SURF_FENCE)
-					D_DrawSpans16_Fence(s->spans);
-				else
-					(*d_drawspans) (s->spans, 1.0);
-
-				D_DrawZSpans (s->spans);
+					D_DrawSpans16_Fence(s->spans, alpha);
+				else{
+					(*d_drawspans) (s->spans, alpha);
+					D_DrawZSpans (s->spans);
+				}
 
 				if (s->insubmodel)
 				{
--- a/d_iface.h
+++ b/d_iface.h
@@ -119,7 +119,7 @@
 
 void D_Aff8Patch (void *pcolormap);
 void D_PolysetDraw (void);
-void D_PolysetDrawFinalVerts (finalvert_t *fv, int numverts);
+void D_PolysetDrawFinalVerts (finalvert_t *fv, int numverts, byte alpha);
 void D_DrawParticle (particle_t *pparticle);
 void D_DrawPoly (void);
 void D_DrawSprite (void);
--- a/d_init.c
+++ b/d_init.c
@@ -18,7 +18,7 @@
 
 extern int			d_aflatcolor;
 
-void (*d_drawspans) (espan_t *pspan, float alpha);
+void (*d_drawspans) (espan_t *pspan, byte alpha);
 
 
 /*
--- a/d_local.h
+++ b/d_local.h
@@ -51,11 +51,10 @@
 fixed16_t	bbextents, bbextentt;
 
 void D_DrawSpans8 (espan_t *pspans);
-void D_DrawSpans16 (espan_t *pspans, float alpha);
+void D_DrawSpans16 (espan_t *pspans, byte alpha);
 void D_DrawZSpans (espan_t *pspans);
-void Turbulent8 (espan_t *pspan, float alpha);
-void D_SpriteDrawSpans (sspan_t *pspan);
-void D_DrawSpans16_Fence (espan_t *pspan);
+void Turbulent8 (espan_t *pspan, byte alpha);
+void D_DrawSpans16_Fence (espan_t *pspan, byte alpha);
 
 void D_DrawSkyScans8 (espan_t *pspan);
 void D_DrawSkyScans16 (espan_t *pspan);
@@ -83,5 +82,12 @@
 extern int		d_minmip;
 extern float	d_scalemip[3];
 
-extern void (*d_drawspans) (espan_t *pspan, float alpha);
+extern void (*d_drawspans) (espan_t *pspan, byte alpha);
 
+extern byte *alphamap[256];
+
+#define blendalpha(a, b, alpha) \
+	alphamap[alpha][(u16int)((a)<<8 | (b))]
+
+void buildalpha(int alpha);
+float alphafor(int flags);
--- a/d_polyse.c
+++ b/d_polyse.c
@@ -90,7 +90,7 @@
 void D_PolysetCalcGradients (int skinwidth);
 void D_DrawSubdiv (void);
 void D_DrawNonSubdiv (void);
-void D_PolysetRecursiveTriangle (int *p1, int *p2, int *p3);
+void D_PolysetRecursiveTriangle (int *p1, int *p2, int *p3, byte alpha);
 void D_PolysetSetEdgeTable (void);
 void D_RasterizeAliasPolySmooth (void);
 void D_PolysetScanLeftEdge (int height);
@@ -125,7 +125,7 @@
 D_PolysetDrawFinalVerts
 ================
 */
-void D_PolysetDrawFinalVerts (finalvert_t *fv, int numverts)
+void D_PolysetDrawFinalVerts (finalvert_t *fv, int numverts, byte alpha)
 {
 	int		i, z;
 	uzint	*zbuf;
@@ -139,14 +139,19 @@
 		{
 			z = fv->v[5]>>16;
 			zbuf = zspantable[fv->v[1]] + fv->v[0];
-			if (z >= *zbuf)
-			{
-				int		pix;
-				
-				*zbuf = z;
-				pix = skintable[fv->v[3]>>16][fv->v[2]>>16];
-				pix = ((byte *)acolormap)[pix + (fv->v[4] & 0xFF00) ];
-				d_viewbuffer[d_scantable[fv->v[1]] + fv->v[0]] = pix;
+			if (z >= *zbuf){
+				if(r_drawflags & DRAW_BLEND){
+					int n = d_scantable[fv->v[1]] + fv->v[0];
+					int pix = skintable[fv->v[3]>>16][fv->v[2]>>16];
+					pix = ((byte *)acolormap)[pix + (fv->v[4] & 0xFF00) ];
+					d_viewbuffer[n] = blendalpha(pix, d_viewbuffer[n], alpha);
+				}else{
+					int		pix;
+					*zbuf = z;
+					pix = skintable[fv->v[3]>>16][fv->v[2]>>16];
+					pix = ((byte *)acolormap)[pix + (fv->v[4] & 0xFF00) ];
+					d_viewbuffer[d_scantable[fv->v[1]] + fv->v[0]] = pix;
+				}
 			}
 		}
 	}
@@ -187,7 +192,7 @@
 
 		if (ptri[i].facesfront)
 		{
-			D_PolysetRecursiveTriangle(index0->v, index1->v, index2->v);
+			D_PolysetRecursiveTriangle(index0->v, index1->v, index2->v, currententity->alpha);
 		}
 		else
 		{
@@ -204,7 +209,7 @@
 			if (index2->flags & ALIAS_ONSEAM)
 				index2->v[2] += r_affinetridesc.seamfixupX16;
 
-			D_PolysetRecursiveTriangle(index0->v, index1->v, index2->v);
+			D_PolysetRecursiveTriangle(index0->v, index1->v, index2->v, currententity->alpha);
 
 			index0->v[2] = s0;
 			index1->v[2] = s1;
@@ -287,7 +292,7 @@
 D_PolysetRecursiveTriangle
 ================
 */
-void D_PolysetRecursiveTriangle (int *lp1, int *lp2, int *lp3)
+void D_PolysetRecursiveTriangle (int *lp1, int *lp2, int *lp3, byte alpha)
 {
 	int		*temp;
 	int		d;
@@ -349,19 +354,23 @@
 
 	z = new[5]>>16;
 	zbuf = zspantable[new[1]] + new[0];
-	if (z >= *zbuf)
-	{
-		int		pix;
-		
-		*zbuf = z;
-		pix = d_pcolormap[skintable[new[3]>>16][new[2]>>16]];
-		d_viewbuffer[d_scantable[new[1]] + new[0]] = pix;
+	if (z >= *zbuf){
+		if(r_drawflags & DRAW_BLEND){
+			int n = d_scantable[new[1]] + new[0];
+			int pix = d_pcolormap[skintable[new[3]>>16][new[2]>>16]];
+			d_viewbuffer[n] = blendalpha(pix, d_viewbuffer[n], alpha);
+		}else{
+			int		pix;
+			*zbuf = z;
+			pix = d_pcolormap[skintable[new[3]>>16][new[2]>>16]];
+			d_viewbuffer[d_scantable[new[1]] + new[0]] = pix;
+		}
 	}
 
 nodraw:
 // recursively continue
-	D_PolysetRecursiveTriangle (lp3, lp1, new);
-	D_PolysetRecursiveTriangle (lp3, new, lp2);
+	D_PolysetRecursiveTriangle (lp3, lp1, new, alpha);
+	D_PolysetRecursiveTriangle (lp3, new, lp2, alpha);
 }
 
 
@@ -547,26 +556,8 @@
 	a_ststepxwhole = skinwidth * (r_tstepx >> 16) + (r_sstepx >> 16);
 }
 
-
 /*
-byte gelmap[256];
-void InitGel (byte *palette)
-{
-	int		i;
-	int		r;
-
-	for (i=0 ; i<256 ; i++)
-	{
-//		r = (palette[i*3]>>4);
-		r = (palette[i*3] + palette[i*3+1] + palette[i*3+2])/(16*3);
-		gelmap[i] = 0 + r; // 64
-	}
-}
-*/
-
-
-/*
-================
+================
 D_PolysetDrawSpans8
 ================
 */
@@ -952,76 +943,3 @@
 
 	pedgetable = &edgetables[edgetableindex];
 }
-
-
-/*
-void D_PolysetRecursiveDrawLine (int *lp1, int *lp2)
-{
-	int		d;
-	int		new[6];
-	int 	ofs;
-	
-	d = lp2[0] - lp1[0];
-	if (d < -1 || d > 1)
-		goto split;
-	d = lp2[1] - lp1[1];
-	if (d < -1 || d > 1)
-		goto split;
-
-	return;	// line is completed
-
-split:
-// split this edge
-	new[0] = (lp1[0] + lp2[0]) >> 1;
-	new[1] = (lp1[1] + lp2[1]) >> 1;
-	new[5] = (lp1[5] + lp2[5]) >> 1;
-	new[2] = (lp1[2] + lp2[2]) >> 1;
-	new[3] = (lp1[3] + lp2[3]) >> 1;
-	new[4] = (lp1[4] + lp2[4]) >> 1;
-
-// draw the point
-	ofs = d_scantable[new[1]] + new[0];
-	if (new[5] > d_pzbuffer[ofs])
-	{
-		int		pix;
-		
-		d_pzbuffer[ofs] = new[5];
-		pix = skintable[new[3]>>16][new[2]>>16];
-//		pix = ((byte *)acolormap)[pix + (new[4] & 0xFF00)];
-		d_viewbuffer[ofs] = pix;
-	}
-
-// recursively continue
-	D_PolysetRecursiveDrawLine (lp1, new);
-	D_PolysetRecursiveDrawLine (new, lp2);
-}
-
-void D_PolysetRecursiveTriangle2 (int *lp1, int *lp2, int *lp3)
-{
-	int		d;
-	int		new[4];
-	
-	d = lp2[0] - lp1[0];
-	if (d < -1 || d > 1)
-		goto split;
-	d = lp2[1] - lp1[1];
-	if (d < -1 || d > 1)
-		goto split;
-	return;
-
-split:
-// split this edge
-	new[0] = (lp1[0] + lp2[0]) >> 1;
-	new[1] = (lp1[1] + lp2[1]) >> 1;
-	new[5] = (lp1[5] + lp2[5]) >> 1;
-	new[2] = (lp1[2] + lp2[2]) >> 1;
-	new[3] = (lp1[3] + lp2[3]) >> 1;
-	new[4] = (lp1[4] + lp2[4]) >> 1;
-
-	D_PolysetRecursiveDrawLine (new, lp3);
-
-// recursively continue
-	D_PolysetRecursiveTriangle (lp1, new, lp3);
-	D_PolysetRecursiveTriangle (new, lp2, lp3);
-}
-*/
--- a/d_scan.c
+++ b/d_scan.c
@@ -66,47 +66,6 @@
 	}
 }
 
-static byte *alphamap[256];
-
-static void
-buildalpha(int alpha)
-{
-	extern s32int fbpal[256];
-	int a, b;
-	byte *ca, *cb, *p;
-	int rr, gg, bb;
-	int i, dst, x, best;
-
-	if(alphamap[alpha] != nil)
-		return;
-	alphamap[alpha] = malloc(256*256);
-	p = (byte*)fbpal;
-	ca = p;
-	for(a = 0; a < 256; a++, ca += 4){
-		cb = p;
-		for(b = 0; b < 256; b++, cb++){
-			rr = (alpha*ca[0] + (255 - alpha)*(*cb++))>>8;
-			gg = (alpha*ca[1] + (255 - alpha)*(*cb++))>>8;
-			bb = (alpha*ca[2] + (255 - alpha)*(*cb++))>>8;
-			dst = 9999999;
-			best = 255;
-			for(i = 0; i < 768; i += 4){
-				x = (rr-p[i+0])*(rr-p[i+0]) +
-					(gg-p[i+1])*(gg-p[i+1]) +
-					(bb-p[i+2])*(bb-p[i+2]);
-				if(x < dst){
-					dst = x;
-					best = i;
-				}
-			}
-			alphamap[alpha][a<<8 | b] = best/4;
-		}
-	}
-}
-
-#define blendalpha(a, b, alpha) \
-	alphamap[alpha][(u16int)((a)<<8 | (b))]
-
 /*
 =============
 D_DrawTurbulent8Span
@@ -120,13 +79,8 @@
 	{
 		sturb = ((r_turb_s + r_turb_turb[(r_turb_t>>16)&(CYCLE-1)])>>16)&63;
 		tturb = ((r_turb_t + r_turb_turb[(r_turb_s>>16)&(CYCLE-1)])>>16)&63;
-		if (*r_turb_z <= (izi >> 16)){
-			if(alpha == 255 || alpha == 0)
-				*r_turb_pdest = *(r_turb_pbase + (tturb<<6) + sturb);
-			else
-				*r_turb_pdest = blendalpha(*(r_turb_pbase + (tturb<<6) + sturb), *r_turb_pdest, alpha);
-			*r_turb_z = (izi >> 16);
-		}
+		if (*r_turb_z <= (izi >> 16))
+			*r_turb_pdest = blendalpha(*(r_turb_pbase + (tturb<<6) + sturb), *r_turb_pdest, alpha);
 		r_turb_s += r_turb_sstep;
 		r_turb_t += r_turb_tstep;
 		r_turb_pdest++;
@@ -140,13 +94,12 @@
 Turbulent8
 =============
 */
-void Turbulent8 (espan_t *pspan, float alpha)
+void Turbulent8 (espan_t *pspan, byte alpha)
 {
 	int				count;
 	fixed16_t		snext, tnext;
 	float			sdivz, tdivz, zi, z, du, dv, spancountminus1;
 	float			sdivz16stepu, tdivz16stepu, zi16stepu;
-	byte			balpha;
 	
 	r_turb_turb = sintable + ((int)(cl.time*SPEED)&(CYCLE-1));
 
@@ -158,9 +111,6 @@
 	sdivz16stepu = d_sdivzstepu * 16;
 	tdivz16stepu = d_tdivzstepu * 16;
 	zi16stepu = d_zistepu * 16;
-	alpha = clamp(alpha, 0.0, 1.0);
-	balpha = alpha * 255;
-	buildalpha(balpha);
 
 	do
 	{
@@ -262,7 +212,7 @@
 			r_turb_s = r_turb_s & ((CYCLE<<16)-1);
 			r_turb_t = r_turb_t & ((CYCLE<<16)-1);
 
-			D_DrawTurbulent8Span ((int)(zi * 0x8000 * 0x10000), balpha);
+			D_DrawTurbulent8Span ((int)(zi * 0x8000 * 0x10000), alpha);
 
 			r_turb_s = snext;
 			r_turb_t = tnext;
@@ -274,14 +224,16 @@
 
 #define WRITEFENCE(i) do{ \
 	fencepix = *(pbase + (s >> 16) + (t >> 16) * cachewidth); \
-	if (pz[i] <= (izi >> 16) && fencepix != 255) \
-		pdest[i] = fencepix; pz[i] = (izi >> 16); \
+	if (pz[i] <= (izi >> 16) && fencepix != 255){ \
+		pdest[i] = blendalpha(fencepix, pdest[i], alpha); \
+		pz[i] = (izi >> 16); \
+	} \
 	izi += izistep; \
 	s += sstep; \
 	t += tstep; \
 }while(0)
 
-void D_DrawSpans16_Fence (espan_t *pspan)
+void D_DrawSpans16_Fence (espan_t *pspan, byte alpha)
 {
 	byte fencepix;
 	byte *pbase = (byte *)cacheblock, *pdest;
@@ -433,15 +385,15 @@
 D_DrawSpans16
 =============
 */
-void D_DrawSpans16 (espan_t *pspan, float alpha) //qbism- up it from 8 to 16
+void D_DrawSpans16 (espan_t *pspan, byte alpha) //qbism- up it from 8 to 16
 {
 	int				count, spancount;
 	unsigned char	*pbase, *pdest;
+	uzint			*pz;
 	fixed16_t		s, t, snext, tnext, sstep, tstep;
 	float			sdivz, tdivz, zi, z, du, dv, spancountminus1;
 	float			sdivzstepu, tdivzstepu, zistepu;
 
-	USED(alpha);
 	sstep = 0;	// keep compiler happy
 	tstep = 0;	// ditto
 
@@ -455,6 +407,7 @@
 	{
 		pdest = (unsigned char *)((byte *)d_viewbuffer +
 				(screenwidth * pspan->v) + pspan->u);
+		pz = d_pzbuffer + (d_zwidth * pspan->v) + pspan->u;
 
 		count = pspan->count;
 
@@ -549,9 +502,14 @@
 
 			if(spancount > 0){
 				void dospan(uchar *, uchar *, int, int, int, int, int, int);
-				dospan(pdest, pbase, s, t, sstep, tstep, spancount, cachewidth);
+				void dospan_alpha(uchar *, uchar *, int, int, int, int, int, int, byte, uzint *, int);
+				if(r_drawflags & DRAW_BLEND)
+					dospan_alpha(pdest, pbase, s, t, sstep, tstep, spancount, cachewidth, alpha, pz, (int)(zi * 0x8000 * 0x10000));
+				else
+					dospan(pdest, pbase, s, t, sstep, tstep, spancount, cachewidth);
 			}
 			pdest += spancount;
+			pz += spancount;
 			s = snext;
 			t = tnext;
 
@@ -573,6 +531,9 @@
 	unsigned		ltemp;
 	double			zi;
 	float			du, dv;
+
+	if((r_drawflags & DRAW_BLEND) != 0)
+		return;
 
 // FIXME: check for clamping/range problems
 // we count on FP exceptions being turned off to avoid range problems
--- a/d_sprite.c
+++ b/d_sprite.c
@@ -13,7 +13,7 @@
 D_SpriteDrawSpans
 =====================
 */
-void D_SpriteDrawSpans (sspan_t *pspan)
+void D_SpriteDrawSpans (sspan_t *pspan, byte alpha)
 {
 	int			count, spancount, izistep;
 	int			izi;
@@ -142,10 +142,13 @@
 				btemp = *(pbase + (s >> 16) + (t >> 16) * cachewidth);
 				if (btemp != 255)
 				{
-					if (*pz <= (izi >> 16))	/* FIXME: segfault: assumed 32bit ptr? */
-					{
-						*pz = izi >> 16;
-						*pdest = btemp;
+					if (*pz <= (izi >> 16)){	/* FIXME: segfault: assumed 32bit ptr? */
+						if(r_drawflags & DRAW_BLEND){
+							*pdest = blendalpha(btemp, *pdest, alpha);
+						}else{
+							*pz = izi >> 16;
+							*pdest = btemp;
+						}
 					}
 				}
 
@@ -414,6 +417,6 @@
 	D_SpriteCalculateGradients ();
 	D_SpriteScanLeftEdge ();
 	D_SpriteScanRightEdge ();
-	D_SpriteDrawSpans (sprite_spans);
+	D_SpriteDrawSpans (sprite_spans, currententity->alpha);
 }
 
--- a/mkfile
+++ b/mkfile
@@ -7,6 +7,7 @@
 OFILES=\
 	pal`{test -f pal_$objtype.s && echo -n _$objtype}.$O\
 	span`{test -f span_$objtype.s && echo -n _$objtype}.$O\
+	span_alpha.$O\
 	cd.$O\
 	cl_demo.$O\
 	cl_input.$O\
@@ -19,6 +20,7 @@
 	console.$O\
 	cvar.$O\
 	draw.$O\
+	d_alpha.$O\
 	d_edge.$O\
 	d_fill.$O\
 	d_init.$O\
--- a/model.c
+++ b/model.c
@@ -1172,7 +1172,14 @@
 		
 		mod->firstmodelsurface = bm->firstface;
 		mod->nummodelsurfaces = bm->numfaces;
-		
+		mod->blend = false;
+		for(j = bm->firstface; j < bm->firstface+bm->numfaces; j++){
+			if(surfdrawflags(&loadmodel->surfaces[j]) & DRAW_BLEND){
+				mod->blend = true;
+				break;
+			}
+		}
+
 		VectorCopy (bm->maxs, mod->maxs);
 		VectorCopy (bm->mins, mod->mins);
 		mod->radius = RadiusFromBounds (mod->mins, mod->maxs);
--- a/model.h
+++ b/model.h
@@ -292,6 +292,7 @@
 {
 	char		name[Npath];
 	qboolean	needload;		// bmodels and sprites don't cache normally
+	qboolean	blend;
 
 	modtype_t	type;
 	int			numframes;
--- a/r_alias.c
+++ b/r_alias.c
@@ -518,7 +518,7 @@
 	R_AliasTransformAndProjectFinalVerts (fv, pstverts);
 
 	if (r_affinetridesc.drawtype)
-		D_PolysetDrawFinalVerts (fv, r_anumverts);
+		D_PolysetDrawFinalVerts (fv, r_anumverts, currententity->alpha);
 
 	r_affinetridesc.pfinalverts = pfinalverts;
 	r_affinetridesc.ptriangles = (mtriangle_t *)
--- a/r_bsp.c
+++ b/r_bsp.c
@@ -313,6 +313,9 @@
 	bedge_t		*pbedge;
 	medge_t		*pedge, *pedges;
 
+	if(entdrawflags(currententity) ^ r_drawflags)
+		return;
+
 // FIXME: use bounding-box-based frustum clipping info?
 
 	psurf = &pmodel->surfaces[pmodel->firstmodelsurface];
@@ -321,8 +324,6 @@
 
 	for (i=0 ; i<numsurfaces ; i++, psurf++)
 	{
-		if((psurf->flags & SURF_TRANS) ^ r_drawflags)
-			continue;
 	// find which side of the node we are on
 		pplane = psurf->plane;
 
@@ -386,14 +387,14 @@
 
 // FIXME: use bounding-box-based frustum clipping info?
 
+	if(entdrawflags(currententity) ^ r_drawflags)
+		return;
+
 	psurf = &pmodel->surfaces[pmodel->firstmodelsurface];
 	numsurfaces = pmodel->nummodelsurfaces;
 
 	for (i=0 ; i<numsurfaces ; i++, psurf++)
 	{
-		if((psurf->flags & SURF_TRANS) ^ r_drawflags)
-			continue;
-
 	// find which side of the node we are on
 		pplane = psurf->plane;
 
--- a/r_draw.c
+++ b/r_draw.c
@@ -353,6 +353,8 @@
 	r_emitted = 1;
 }
 
+float alphafor(int flags);
+
 /*
 ================
 R_RenderFace
@@ -368,6 +370,9 @@
 	medge_t		*pedges, tedge;
 	clipplane_t	*pclip;
 
+	if(surfdrawflags(fa) ^ r_drawflags)
+		return;
+
 // skip out if no more surfs
 	if ((surface_p) >= surf_max)
 	{
@@ -381,9 +386,6 @@
 		r_outofedges += fa->numedges;
 		return;
 	}
-
-	if((fa->flags & SURF_TRANS) ^ r_drawflags)
-		return;
 
 	c_faceclip++;
 
--- a/r_efrag.c
+++ b/r_efrag.c
@@ -221,7 +221,6 @@
 void R_StoreEfrags (efrag_t **ppefrag)
 {
 	entity_t	*pent;
-	model_t		*clmodel;
 	efrag_t		*pefrag;
 
 
@@ -228,30 +227,15 @@
 	while ((pefrag = *ppefrag) != nil)
 	{
 		pent = pefrag->entity;
-		clmodel = pent->model;
 
-		switch (clmodel->type)
+		if ((pent->visframe != r_framecount) &&
+			(cl_numvisedicts < MAX_VISEDICTS))
 		{
-		case mod_alias:
-		case mod_brush:
-		case mod_sprite:
-			pent = pefrag->entity;
-
-			if ((pent->visframe != r_framecount) &&
-				(cl_numvisedicts < MAX_VISEDICTS))
-			{
-				cl_visedicts[cl_numvisedicts++] = pent;
-
-			// mark that we've recorded this entity for this frame
-				pent->visframe = r_framecount;
-			}
-
-			ppefrag = &pefrag->leafnext;
-			break;
-
-		default:	
-			fatal ("R_StoreEfrags: Bad entity type %d\n", clmodel->type);
+			cl_visedicts[cl_numvisedicts++] = pent;
+			pent->visframe = r_framecount;
 		}
+
+		ppefrag = &pefrag->leafnext;
 	}
 }
 
--- a/r_local.h
+++ b/r_local.h
@@ -2,6 +2,15 @@
 
 #include "r_shared.h"
 
+enum {
+	DRAW_BLEND = 1<<0,
+};
+
+#define surfdrawflags(s) (((s)->flags & SURF_TRANS) ? DRAW_BLEND : 0)
+#define enthasalpha(e) ((e) && !defalpha((e)->alpha))
+#define entdrawflags(e) (((e) && (!defalpha((e)->alpha) || ((e)->model && (e)->model != cl.worldmodel && (e)->model->blend))) ? DRAW_BLEND : 0)
+
+
 #define ALIAS_BASE_SIZE_RATIO		(1.0 / 11.0)
 					// normalizing factor so player model works out to about
 					//  1 pixel per triangle
--- a/r_main.c
+++ b/r_main.c
@@ -470,7 +470,13 @@
 
 		if (currententity == &cl_entities[cl.viewentity])
 			continue;	// don't draw the player
+		if(r_drawflags & DRAW_BLEND)
+			buildalpha(currententity->alpha);
 
+		// FIXME(sigrid): no trans-specific surfaces, hopefully?
+		if(entdrawflags(currententity) ^ r_drawflags)
+			continue;
+
 		switch (currententity->model->type)
 		{
 		case mod_sprite:
@@ -507,7 +513,7 @@
 							lighting.ambientlight += add;
 					}
 				}
-	
+
 			// clamp lighting so it doesn't overbright as much
 				if (lighting.ambientlight > 128)
 					lighting.ambientlight = 128;
@@ -682,6 +688,9 @@
 	{
 		currententity = cl_visedicts[i];
 
+		if(entdrawflags(currententity) ^ r_drawflags)
+			continue;
+
 		switch (currententity->model->type)
 		{
 		case mod_brush:
@@ -750,6 +759,8 @@
 
 					if (r_pefragtopnode)
 					{
+						if(r_drawflags & DRAW_BLEND)
+							R_BeginEdgeFrame();
 						currententity->topnode = r_pefragtopnode;
 	
 						if (r_pefragtopnode->contents >= 0)
@@ -767,6 +778,8 @@
 						}
 	
 						currententity->topnode = nil;
+						if(r_drawflags & DRAW_BLEND)
+							R_ScanEdges();
 					}
 				}
 
@@ -816,7 +829,8 @@
 		db_time1 = rw_time2;
 	}
 
-	R_DrawBEntitiesOnList ();
+	if((r_drawflags & DRAW_BLEND) == 0)
+		R_DrawBEntitiesOnList ();
 
 	if (r_dspeeds.value)
 	{
@@ -861,7 +875,6 @@
 
 	if (!cl_entities[0].model || !cl.worldmodel)
 		fatal ("R_RenderView: NULL worldmodel");
-	r_drawflags = 0;
 	R_EdgeDrawing ();
 
 	if (r_dspeeds.value)
@@ -888,8 +901,11 @@
 
 	R_DrawParticles ();
 
-	r_drawflags = SURF_TRANS;
+	r_drawflags = DRAW_BLEND;
+	r_worldpolysbacktofront = true;
 	R_EdgeDrawing ();
+	R_DrawBEntitiesOnList();
+	R_DrawEntitiesOnList ();
 	r_drawflags = 0;
 
 	if (r_dspeeds.value)
--- a/span.c
+++ b/span.c
@@ -1,15 +1,22 @@
 #include <u.h>
 #include <libc.h>
+#include "dat.h"
+#include "quakedef.h"
 
+#define P \
+	do{ \
+		*pdest++ = pbase[(s >> 16) + (t >> 16) * cachewidth]; \
+		s += sstep; \
+		t += tstep; \
+	}while(0)
+
 void
-dospan(uchar *pdest, uchar *pbase, int s, int t, int sstep, int tstep, int spancount, int cachewidth)
+dospan(uchar *pdest, uchar *pbase, int s, int t, int sstep, int tstep, int spancount, int cachewidth, u8int alpha, uzint *z, int izi)
 {
-#define P *pdest++ = pbase[(s >> 16) + (t >> 16) * cachewidth]; s += sstep; t += tstep
 	switch(spancount)
 	{
 	case 16: P; case 15: P; case 14: P; case 13: P; case 12: P; case 11: P; case 10: P; case 9: P;
 	case  8: P; case  7: P; case  6: P; case  5: P; case  4: P; case  3: P; case  2: P; case 1: P;
 	}
-#undef P
-	USED(pdest, s, t);
+	USED(pdest, s, t, z);
 }
--- /dev/null
+++ b/span_alpha.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "quakedef.h"
+
+#define P \
+	do{ \
+		if(*z <= (izi >> 16)) \
+			*pdest = blendalpha(pbase[(s >> 16) + (t >> 16) * cachewidth], *pdest, alpha); \
+		pdest++; \
+		z++; \
+		s += sstep; \
+		t += tstep; \
+	}while(0)
+
+void
+dospan_alpha(uchar *pdest, uchar *pbase, int s, int t, int sstep, int tstep, int spancount, int cachewidth, u8int alpha, uzint *z, int izi)
+{
+	switch(spancount)
+	{
+	case 16: P; case 15: P; case 14: P; case 13: P; case 12: P; case 11: P; case 10: P; case 9: P;
+	case  8: P; case  7: P; case  6: P; case  5: P; case  4: P; case  3: P; case  2: P; case 1: P;
+	}
+	USED(pdest, s, t, z);
+}