diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 3279a61689a1907c998c4e5b42e9849a8363af82..0ca6ac4895d40cd5554c83cafd9442b6eb43995e 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -55,6 +55,7 @@ struct hwdriver_s hwdriver;
 // ==========================================================================
 
 static void HWR_AddSprites(sector_t *sec);
+static void HWR_AddPrecipitationSprites(void);
 static void HWR_ProjectSprite(mobj_t *thing);
 static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing);
 static void HWR_ProjectBoundingBox(mobj_t *thing);
@@ -4318,7 +4319,6 @@ static UINT8 sectorlight;
 static void HWR_AddSprites(sector_t *sec)
 {
 	mobj_t *thing;
-	precipmobj_t *precipthing;
 	fixed_t limit_dist, hoop_limit_dist;
 
 	// BSP is traversed by subsector.
@@ -4350,18 +4350,45 @@ static void HWR_AddSprites(sector_t *sec)
 			HWR_ProjectBoundingBox(thing);
 		}
 	}
+}
+
+// --------------------------------------------------------------------------
+// HWR_AddPrecipitationSprites
+// This renders through the blockmap instead of BSP to avoid
+// iterating a huge amount of precipitation sprites in sectors
+// that are beyond drawdist.
+// --------------------------------------------------------------------------
+static void HWR_AddPrecipitationSprites(void)
+{
+	const fixed_t drawdist = ((fixed_t)cv_drawdist_precip.value << FRACBITS);
+
+	INT32 xl, xh, yl, yh, bx, by;
+	precipmobj_t *th;
 
 	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
-	if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS))
+	if (drawdist == 0)
+	{
+		return;
+	}
+
+	R_GetRenderBlockMapDimensions(drawdist, &xl, &xh, &yl, &yh);
+
+	for (bx = xl; bx <= xh; bx++)
 	{
-		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
+		for (by = yl; by <= yh; by++)
 		{
-			if (R_PrecipThingVisible(precipthing, limit_dist))
-				HWR_ProjectPrecipitationSprite(precipthing);
+			for (th = precipblocklinks[(by * bmapwidth) + bx]; th; th = th->bnext)
+			{
+				if (R_PrecipThingVisible(th))
+				{
+					HWR_ProjectPrecipitationSprite(th);
+				}
+			}
 		}
 	}
 }
 
+
 // --------------------------------------------------------------------------
 // HWR_ProjectSprite
 //  Generates a vissprite for a thing if it might be visible.
@@ -5612,8 +5639,11 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
 
+	HWR_AddPrecipitationSprites();
+
 	PS_STOP_TIMING(ps_bsptime);
 
+
 	if (cv_glbatching.value)
 		HWR_RenderBatches();
 
diff --git a/src/p_local.h b/src/p_local.h
index 85a31cf8982114068b0da41c7a59971d011929ba..4e2d84766d937b8499186263868d62d53adced29 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -460,6 +460,7 @@ extern INT32 bmapheight; // in mapblocks
 extern fixed_t bmaporgx;
 extern fixed_t bmaporgy; // origin of block map
 extern blocknode_t **blocklinks; // for thing chains
+extern precipmobj_t **precipblocklinks; // special blockmap for precip rendering
 
 //
 // P_INTER
diff --git a/src/p_maputl.c b/src/p_maputl.c
index 4c7b959270993a7117c58d83f71c86348206937e..1197f5f49be3ae5a9c5d35aa0c4e52c4213a9d21 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -804,15 +804,48 @@ void P_UnsetThingPosition(mobj_t *thing)
 
 void P_UnsetPrecipThingPosition(precipmobj_t *thing)
 {
-	precipmobj_t **sprev = thing->sprev;
-	precipmobj_t  *snext = thing->snext;
-	if ((*sprev = snext) != NULL)  // unlink from sector list
-		snext->sprev = sprev;
+	precipmobj_t **bprev = thing->bprev;
+	precipmobj_t  *bnext = thing->bnext;
+	if (bprev && (*bprev = bnext) != NULL)  // unlink from block map
+		bnext->bprev = bprev;
+
 
 	precipsector_list = thing->touching_sectorlist;
 	thing->touching_sectorlist = NULL; //to be restored by P_SetPrecipThingPosition
 }
 
+static void P_LinkToPrecipBlockMap(precipmobj_t *thing, precipmobj_t **bmap)
+{
+	const INT32 blockx = (unsigned)(thing->x - bmaporgx) >> MAPBLOCKSHIFT;
+	const INT32 blocky = (unsigned)(thing->y - bmaporgy) >> MAPBLOCKSHIFT;
+
+	if (blockx >= 0 && blockx < bmapwidth
+		&& blocky >= 0 && blocky < bmapheight)
+	{
+		// killough 8/11/98: simpler scheme using
+		// pointer-to-pointer prev pointers --
+		// allows head nodes to be treated like everything else
+
+		precipmobj_t **link = &bmap[(blocky * bmapwidth) + blockx];
+		precipmobj_t *bnext = *link;
+
+		thing->bnext = bnext;
+
+		if (bnext != NULL)
+			bnext->bprev = &thing->bnext;
+
+		thing->bprev = link;
+		*link = thing;
+	}
+	else // thing is off the map
+	{
+		thing->bnext = NULL, thing->bprev = NULL;
+	}
+}
+
+
+
+
 //
 // P_SetThingPosition
 // Links a thing into both a block and a subsector
@@ -962,18 +995,12 @@ void P_SetUnderlayPosition(mobj_t *thing)
 
 void P_SetPrecipitationThingPosition(precipmobj_t *thing)
 {
-	subsector_t *ss = thing->subsector = R_PointInSubsector(thing->x, thing->y);
-
-	precipmobj_t **link = &ss->sector->preciplist;
-	precipmobj_t *snext = *link;
-	if ((thing->snext = snext) != NULL)
-		snext->sprev = &thing->snext;
-	thing->sprev = link;
-	*link = thing;
+	thing->subsector = R_PointInSubsector(thing->x, thing->y);
 
 	P_CreatePrecipSecNodeList(thing, thing->x, thing->y);
 	thing->touching_sectorlist = precipsector_list; // Attach to Thing's precipmobj_t
 	precipsector_list = NULL; // clear for next time
+	P_LinkToPrecipBlockMap(thing, precipblocklinks);
 }
 
 //
diff --git a/src/p_mobj.h b/src/p_mobj.h
index fef6713a4b9c10de3286373411362562ab0eb83e..eb383723b28c677f6317b1f40840ba96b886d14f 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -468,6 +468,11 @@ typedef struct precipmobj_s
 
 	struct subsector_s *subsector; // Subsector the mobj resides in.
 
+	// Links in blocks (if needed).
+	// The blockmap is only used by precip to render.
+	struct precipmobj_s *bnext;
+	struct precipmobj_s **bprev; // killough 8/11/98: change to ptr-to-ptr
+
 	// The closest interval over all contacted sectors (or things).
 	fixed_t floorz; // Nearest floor below.
 	fixed_t ceilingz; // Nearest ceiling above.
diff --git a/src/p_setup.c b/src/p_setup.c
index d8e4e5d67c06b8d3bb9df58d53ad495458c3818d..27b8730c22312ffdff73f0c37eb1ecd683b9b3b6 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -139,6 +139,7 @@ INT32 *blockmaplump; // Big blockmap
 fixed_t bmaporgx, bmaporgy;
 // for thing chains
 blocknode_t **blocklinks;
+precipmobj_t **precipblocklinks;
 
 // REJECT
 // For fast sight rejection.
@@ -1024,7 +1025,7 @@ static void P_InitializeSector(sector_t *ss)
 
 	ss->floorspeed = ss->ceilspeed = 0;
 
-	ss->preciplist = NULL;
+
 	ss->touching_preciplist = NULL;
 
 	ss->f_slope = NULL;
@@ -3875,6 +3876,10 @@ static boolean P_LoadBlockMap(UINT8 *data, size_t count)
 	// haleyjd 2/22/06: setup polyobject blockmap
 	count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
 	polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
+
+	count = sizeof (*precipblocklinks)* bmapwidth*bmapheight;
+	precipblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
+
 	return true;
 }
 
@@ -4128,6 +4133,9 @@ static void P_CreateBlockMap(void)
 		// haleyjd 2/22/06: setup polyobject blockmap
 		count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
 		polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
+
+		count = sizeof (*precipblocklinks)* bmapwidth*bmapheight;
+		precipblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
 	}
 }
 
diff --git a/src/r_defs.h b/src/r_defs.h
index 39afb4e7faac6852903968a9b6c8a4bf984d0bc9..e20db2ab46a2ac72c141fbf38e80c43deb541c59 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -528,7 +528,6 @@ typedef struct sector_s
 	fixed_t floorspeed, ceilspeed;
 
 	// list of precipitation mobjs in sector
-	precipmobj_t *preciplist;
 	struct mprecipsecnode_s *touching_preciplist;
 
 	// Eternity engine slope
diff --git a/src/r_main.c b/src/r_main.c
index 52fc16e643b38ae98ef8a3d9350c5d0118b7d210..9691567d859cb27ddcf28fd328ed3432d0eac59d 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -455,6 +455,35 @@ boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixe
 	return false;
 }
 
+
+// Returns search dimensions within a blockmap, in the direction of viewangle and out to a certain distance.
+void R_GetRenderBlockMapDimensions(fixed_t drawdist, INT32 *xl, INT32 *xh, INT32 *yl, INT32 *yh)
+{
+	const angle_t left = viewangle - clipangle;
+	const angle_t right = viewangle + clipangle;
+
+	const fixed_t vxleft = viewx + FixedMul(drawdist, FCOS(left));
+	const fixed_t vyleft = viewy + FixedMul(drawdist, FSIN(left));
+
+	const fixed_t vxright = viewx + FixedMul(drawdist, FCOS(right));
+	const fixed_t vyright = viewy + FixedMul(drawdist, FSIN(right));
+
+	// Try to narrow the search to within only the field of view
+	*xl = (unsigned)(min(viewx, min(vxleft, vxright)) - bmaporgx)>>MAPBLOCKSHIFT;
+	*xh = (unsigned)(max(viewx, max(vxleft, vxright)) - bmaporgx)>>MAPBLOCKSHIFT;
+	*yl = (unsigned)(min(viewy, min(vyleft, vyright)) - bmaporgy)>>MAPBLOCKSHIFT;
+	*yh = (unsigned)(max(viewy, max(vyleft, vyright)) - bmaporgy)>>MAPBLOCKSHIFT;
+
+	if (*xh >= bmapwidth)
+		*xh = bmapwidth - 1;
+
+	if (*yh >= bmapheight)
+		*yh = bmapheight - 1;
+
+	BMBOUNDFIX(*xl, *xh, *yl, *yh);
+}
+
+
 //
 // R_InitTextureMapping
 //
@@ -1590,6 +1619,7 @@ void R_RenderPlayerView(player_t *player)
 	PS_START_TIMING(ps_bsptime);
 	R_RenderBSPNode((INT32)numnodes - 1);
 	PS_STOP_TIMING(ps_bsptime);
+	R_AddPrecipitationSprites();
 	Mask_Post(&masks[nummasks - 1]);
 
 	PS_START_TIMING(ps_sw_spritecliptime);
diff --git a/src/r_main.h b/src/r_main.h
index 3ad20fccde4304fcbc4e0a68fe6eeca04843e799..5ba3fca0afe179fb30b71aa8cefbce6390ed4e78 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -107,6 +107,8 @@ subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y);
 
 boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph);
 
+void R_GetRenderBlockMapDimensions(fixed_t drawdist, INT32 *xl, INT32 *xh, INT32 *yl, INT32 *yh);
+
 line_t *R_GetFFloorLine(const line_t *line, const ffloor_t *pfloor, const sector_t *sector);
 side_t *R_GetFFloorSide(const line_t *line, const ffloor_t *pfloor, const sector_t *sector);
 
diff --git a/src/r_things.c b/src/r_things.c
index 6fffa4f5c4e284086eb55045396207cc5eef448f..476a8348b2b577d9085f559f57faef236074ae96 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2711,7 +2711,6 @@ weatherthink:
 void R_AddSprites(sector_t *sec, INT32 lightlevel)
 {
 	mobj_t *thing;
-	precipmobj_t *precipthing; // Tails 08-25-2002
 	INT32 lightnum;
 	fixed_t limit_dist, hoop_limit_dist;
 
@@ -2769,18 +2768,44 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 			}
 		}
 	}
+}
+
+// R_AddPrecipitationSprites
+// This renders through the blockmap instead of BSP to avoid
+// iterating a huge amount of precipitation sprites in sectors
+// that are beyond drawdist.
+//
+void R_AddPrecipitationSprites(void)
+{
+	const fixed_t drawdist = ((fixed_t)cv_drawdist_precip.value << FRACBITS);
+
+	INT32 xl, xh, yl, yh, bx, by;
+	precipmobj_t *th;
 
 	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
-	if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS))
+	if (drawdist == 0)
+	{
+		return;
+	}
+
+	R_GetRenderBlockMapDimensions(drawdist, &xl, &xh, &yl, &yh);
+
+	for (bx = xl; bx <= xh; bx++)
 	{
-		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
+		for (by = yl; by <= yh; by++)
 		{
-			if (R_PrecipThingVisible(precipthing, limit_dist))
-				R_ProjectPrecipitationSprite(precipthing);
+			for (th = precipblocklinks[(by * bmapwidth) + bx]; th; th = th->bnext)
+			{
+				if (R_PrecipThingVisible(th))
+				{
+					R_ProjectPrecipitationSprite(th);
+				}
+			}
 		}
 	}
 }
 
+
 static boolean R_SortVisSpriteFunc(vissprite_t *ds, fixed_t bestscale, INT32 bestdispoffset)
 {
 	if (ds->sortscale < bestscale)
@@ -3738,19 +3763,15 @@ boolean R_ThingWithinDist (mobj_t *thing,
 }
 
 /* Check if precipitation may be drawn from our current view. */
-boolean R_PrecipThingVisible (precipmobj_t *precipthing,
-		fixed_t limit_dist)
+boolean R_PrecipThingVisible (precipmobj_t *precipthing)
 {
-	fixed_t approx_dist;
-
 	if (( precipthing->precipflags & PCF_INVISIBLE ))
 		return false;
 
-	approx_dist = P_AproxDistance(viewx-precipthing->x, viewy-precipthing->y);
-
-	return ( approx_dist <= limit_dist );
+	return true;
 }
 
+
 boolean R_ThingHorizontallyFlipped(mobj_t *thing)
 {
 	return (thing->frame & FF_HORIZONTALFLIP || thing->renderflags & RF_HORIZONTALFLIP);
diff --git a/src/r_things.h b/src/r_things.h
index 6f80a4e87e70215dd3da02a5a764d07640346b96..552d6e6927836224846db8f667157c8e9c964872 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -65,6 +65,7 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope);
 
 //SoM: 6/5/2000: Light sprites correctly!
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
+void R_AddPrecipitationSprites(void);
 void R_InitSprites(void);
 void R_ClearSprites(void);
 
@@ -77,8 +78,7 @@ boolean R_ThingWithinDist (mobj_t *thing,
 		fixed_t        draw_dist,
 		fixed_t nights_draw_dist);
 
-boolean R_PrecipThingVisible (precipmobj_t *precipthing,
-		fixed_t precip_draw_dist);
+boolean R_PrecipThingVisible (precipmobj_t *precipthing);
 
 boolean R_ThingHorizontallyFlipped (mobj_t *thing);
 boolean R_ThingVerticallyFlipped (mobj_t *thing);
diff --git a/src/tables.h b/src/tables.h
index 43817e25d3ed6ab78e695e8c8512fb6cb1ea61d7..74975c7573f54bd46b72cefd8e2f32fc5dccd5f5 100644
--- a/src/tables.h
+++ b/src/tables.h
@@ -25,6 +25,7 @@
 #define TANMASK 4095
 #define ANGLETOFINESHIFT 19 // 0x100000000 to 0x2000
 #define FINEANGLE_C(x) ((FixedAngle((x)*FRACUNIT)>>ANGLETOFINESHIFT) & FINEMASK) // ((x*(ANGLE_45/45))>>ANGLETOFINESHIFT) & FINEMASK
+#define ANGLETOFINE(x) (((x)>>ANGLETOFINESHIFT) & FINEMASK)
 
 // Effective size is 10240.
 extern fixed_t finesine[5*FINEANGLES/4];
@@ -121,4 +122,8 @@ matrix_t *FM_RotateZ(matrix_t *dest, angle_t rad);
 #define FINECOSINE(n) (finecosine[n]>>(FINE_FRACBITS-FRACBITS))
 #define FINETANGENT(n) (finetangent[n]>>(FINE_FRACBITS-FRACBITS))
 
+// FSIN(ANGLE_90) = FRACUNIT
+#define FSIN(n) FINESINE(ANGLETOFINE(n))
+#define FCOS(n) FINECOSINE(ANGLETOFINE(n))
+
 #endif