5373e633ac3c696ed7196de9080fb568f6011ff9
[xonotic/darkplaces.git] / r_lightning.c
1
2 #include "quakedef.h"
3 #include "image.h"
4
5 cvar_t r_lightningbeam_thickness = {CVAR_SAVE, "r_lightningbeam_thickness", "8", "thickness of the lightning beam effect"};
6 cvar_t r_lightningbeam_scroll = {CVAR_SAVE, "r_lightningbeam_scroll", "5", "speed of texture scrolling on the lightning beam effect"};
7 cvar_t r_lightningbeam_repeatdistance = {CVAR_SAVE, "r_lightningbeam_repeatdistance", "128", "how far to stretch the texture along the lightning beam effect"};
8 cvar_t r_lightningbeam_color_red = {CVAR_SAVE, "r_lightningbeam_color_red", "1", "color of the lightning beam effect"};
9 cvar_t r_lightningbeam_color_green = {CVAR_SAVE, "r_lightningbeam_color_green", "1", "color of the lightning beam effect"};
10 cvar_t r_lightningbeam_color_blue = {CVAR_SAVE, "r_lightningbeam_color_blue", "1", "color of the lightning beam effect"};
11 cvar_t r_lightningbeam_qmbtexture = {CVAR_SAVE, "r_lightningbeam_qmbtexture", "0", "load the qmb textures/particles/lightning.pcx texture instead of generating one, can look better"};
12
13 static texture_t cl_beams_externaltexture;
14 static texture_t cl_beams_builtintexture;
15
16 static void r_lightningbeams_start(void)
17 {
18         memset(&cl_beams_externaltexture, 0, sizeof(cl_beams_externaltexture));
19         memset(&cl_beams_builtintexture, 0, sizeof(cl_beams_builtintexture));
20 }
21
22 static void CL_Beams_SetupExternalTexture(void)
23 {
24         if (Mod_LoadTextureFromQ3Shader(r_main_mempool, "r_lightning.c", &cl_beams_externaltexture, "textures/particles/lightning", false, false, TEXF_ALPHA | TEXF_FORCELINEAR))
25                 cl_beams_externaltexture.basematerialflags = cl_beams_externaltexture.currentmaterialflags = MATERIALFLAG_WALL | MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOCULLFACE;
26         else
27                 Cvar_SetValueQuick(&r_lightningbeam_qmbtexture, false);
28 }
29
30 static void CL_Beams_SetupBuiltinTexture(void)
31 {
32         // beam direction is horizontal in the lightning texture
33         int texwidth = 128;
34         int texheight = 64;
35         float r, g, b, intensity, thickness = texheight * 0.25f, border = thickness + 2.0f, ithickness = 1.0f / thickness, center, n;
36         int x, y;
37         unsigned char *data;
38         skinframe_t *skinframe;
39         float centersamples[17][2];
40
41         // make a repeating noise pattern for the beam path
42         for (x = 0; x < 16; x++)
43         {
44                 centersamples[x][0] = lhrandom(border, texheight - border);
45                 centersamples[x][1] = lhrandom(0.2f, 1.00f);
46         }
47         centersamples[16][0] = centersamples[0][0];
48         centersamples[16][1] = centersamples[0][1];
49
50         data = (unsigned char *)Mem_Alloc(tempmempool, texwidth * texheight * 4);
51
52         // iterate by columns and draw the entire column of pixels
53         for (x = 0; x < texwidth; x++)
54         {
55                 r = x * 16.0f / texwidth;
56                 y = (int)r;
57                 g = r - y;
58                 center = centersamples[y][0] * (1.0f - g) + centersamples[y+1][0] * g;
59                 n = centersamples[y][1] * (1.0f - g) + centersamples[y + 1][1] * g;
60                 for (y = 0; y < texheight; y++)
61                 {
62                         intensity = 1.0f - fabs((y - center) * ithickness);
63                         if (intensity > 0)
64                         {
65                                 intensity = pow(intensity * n, 2);
66                                 r = intensity * 1.000f * 255.0f;
67                                 g = intensity * 2.000f * 255.0f;
68                                 b = intensity * 4.000f * 255.0f;
69                                 data[(y * texwidth + x) * 4 + 2] = (unsigned char)(bound(0, r, 255));
70                                 data[(y * texwidth + x) * 4 + 1] = (unsigned char)(bound(0, g, 255));
71                                 data[(y * texwidth + x) * 4 + 0] = (unsigned char)(bound(0, b, 255));
72                         }
73                         else
74                                 intensity = 0.0f;
75                         data[(y * texwidth + x) * 4 + 3] = (unsigned char)255;
76                 }
77         }
78
79         skinframe = R_SkinFrame_LoadInternalBGRA("lightningbeam", TEXF_FORCELINEAR, data, texwidth, texheight, false);
80         Mod_LoadCustomMaterial(r_main_mempool, &cl_beams_builtintexture, "cl_beams_builtintexture", 0, MATERIALFLAG_WALL | MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOCULLFACE, skinframe);
81         Mem_Free(data);
82 }
83
84 static void r_lightningbeams_shutdown(void)
85 {
86         memset(&cl_beams_externaltexture, 0, sizeof(cl_beams_externaltexture));
87         memset(&cl_beams_builtintexture, 0, sizeof(cl_beams_builtintexture));
88 }
89
90 static void r_lightningbeams_newmap(void)
91 {
92         if (cl_beams_externaltexture.currentskinframe)
93                 R_SkinFrame_MarkUsed(cl_beams_externaltexture.currentskinframe);
94         if (cl_beams_builtintexture.currentskinframe)
95                 R_SkinFrame_MarkUsed(cl_beams_builtintexture.currentskinframe);
96 }
97
98 void R_LightningBeams_Init(void)
99 {
100         Cvar_RegisterVariable(&r_lightningbeam_thickness);
101         Cvar_RegisterVariable(&r_lightningbeam_scroll);
102         Cvar_RegisterVariable(&r_lightningbeam_repeatdistance);
103         Cvar_RegisterVariable(&r_lightningbeam_color_red);
104         Cvar_RegisterVariable(&r_lightningbeam_color_green);
105         Cvar_RegisterVariable(&r_lightningbeam_color_blue);
106         Cvar_RegisterVariable(&r_lightningbeam_qmbtexture);
107         R_RegisterModule("R_LightningBeams", r_lightningbeams_start, r_lightningbeams_shutdown, r_lightningbeams_newmap, NULL, NULL);
108 }
109
110 static void CL_Beam_AddQuad(dp_model_t *mod, msurface_t *surf, const vec3_t start, const vec3_t end, const vec3_t offset, float t1, float t2)
111 {
112         int e0, e1, e2, e3;
113         vec3_t n;
114         vec3_t dir;
115         float c[4];
116
117         Vector4Set(c, r_lightningbeam_color_red.value, r_lightningbeam_color_green.value, r_lightningbeam_color_blue.value, 1.0f);
118
119         VectorSubtract(end, start, dir);
120         CrossProduct(dir, offset, n);
121         VectorNormalize(n);
122
123         e0 = Mod_Mesh_IndexForVertex(mod, surf, start[0] + offset[0], start[1] + offset[1], start[2] + offset[2], n[0], n[1], n[2], t1, 0, 0, 0, c[0], c[1], c[2], c[3]);
124         e1 = Mod_Mesh_IndexForVertex(mod, surf, start[0] - offset[0], start[1] - offset[1], start[2] - offset[2], n[0], n[1], n[2], t1, 1, 0, 0, c[0], c[1], c[2], c[3]);
125         e2 = Mod_Mesh_IndexForVertex(mod, surf, end[0] - offset[0], end[1] - offset[1], end[2] - offset[2], n[0], n[1], n[2], t2, 1, 0, 0, c[0], c[1], c[2], c[3]);
126         e3 = Mod_Mesh_IndexForVertex(mod, surf, end[0] + offset[0], end[1] + offset[1], end[2] + offset[2], n[0], n[1], n[2], t2, 0, 0, 0, c[0], c[1], c[2], c[3]);
127         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
128         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
129 }
130
131 void CL_Beam_AddPolygons(const beam_t *b)
132 {
133         vec3_t beamdir, right, up, offset, start, end;
134         vec_t beamscroll = r_refdef.scene.time * -r_lightningbeam_scroll.value;
135         vec_t beamrepeatscale = 1.0f / r_lightningbeam_repeatdistance.value;
136         float length, t1, t2;
137         dp_model_t *mod;
138         msurface_t *surf;
139
140         if (r_lightningbeam_qmbtexture.integer && cl_beams_externaltexture.currentskinframe == NULL)
141                 CL_Beams_SetupExternalTexture();
142         if (!r_lightningbeam_qmbtexture.integer && cl_beams_builtintexture.currentskinframe == NULL)
143                 CL_Beams_SetupBuiltinTexture();
144
145         // calculate beam direction (beamdir) vector and beam length
146         // get difference vector
147         CL_Beam_CalculatePositions(b, start, end);
148         VectorSubtract(end, start, beamdir);
149         // find length of difference vector
150         length = sqrt(DotProduct(beamdir, beamdir));
151         // calculate scale to make beamdir a unit vector (normalized)
152         t1 = 1.0f / length;
153         // scale beamdir so it is now normalized
154         VectorScale(beamdir, t1, beamdir);
155
156         // calculate up vector such that it points toward viewer, and rotates around the beamdir
157         // get direction from start of beam to viewer
158         VectorSubtract(r_refdef.view.origin, start, up);
159         // remove the portion of the vector that moves along the beam
160         // (this leaves only a vector pointing directly away from the beam)
161         t1 = -DotProduct(up, beamdir);
162         VectorMA(up, t1, beamdir, up);
163         // generate right vector from forward and up, the result is unnormalized
164         CrossProduct(beamdir, up, right);
165         // now normalize the right vector and up vector
166         VectorNormalize(right);
167         VectorNormalize(up);
168
169         // calculate T coordinate scrolling (start and end texcoord along the beam)
170         t1 = beamscroll;
171         t1 = t1 - (int)t1;
172         t2 = t1 + beamrepeatscale * length;
173
174         // the beam is 3 polygons in this configuration:
175         //  *   2
176         //   * *
177         // 1*****
178         //   * *
179         //  *   3
180         // they are showing different portions of the beam texture, creating an
181         // illusion of a beam that appears to curl around in 3D space
182         // (and realize that the whole polygon assembly orients itself to face
183         //  the viewer)
184
185         mod = &cl_meshentitymodels[MESH_PARTICLES];
186         surf = Mod_Mesh_AddSurface(mod, r_lightningbeam_qmbtexture.integer ? &cl_beams_externaltexture : &cl_beams_builtintexture, false);
187         // polygon 1
188         VectorM(r_lightningbeam_thickness.value, right, offset);
189         CL_Beam_AddQuad(mod, surf, start, end, offset, t1, t2);
190         // polygon 2
191         VectorMAM(r_lightningbeam_thickness.value * 0.70710681f, right, r_lightningbeam_thickness.value * 0.70710681f, up, offset);
192         CL_Beam_AddQuad(mod, surf, start, end, offset, t1 + 0.33f, t2 + 0.33f);
193         // polygon 3
194         VectorMAM(r_lightningbeam_thickness.value * 0.70710681f, right, r_lightningbeam_thickness.value * -0.70710681f, up, offset);
195         CL_Beam_AddQuad(mod, surf, start, end, offset, t1 + 0.66f, t2 + 0.66f);
196 }