1 module game.renderer;
2 
3 import spasm.webgl;
4 import spasm.types;
5 import spasm.rt.memory;
6 import game.entity;
7 import game.math;
8 import std.range : only;
9 
10 nothrow:
11 @safe:
12 
13 immutable GLuint max_verts = 1024 * 64;
14 immutable GLint max_lights = 16;
15 __gshared float camera_shake = 0f;
16 
17 extern(C) void glSetContext(Handle);
18 
19 auto compile_shader(int shader_type, string shader_source) {
20     auto shader = glCreateShader(shader_type);
21     glShaderSource(shader, shader_source);
22     glCompileShader(shader);
23     return shader;
24 };
25 
26 auto enable_vertex_attrib(GLint shader_program, string attrib_name, int count, int vertex_size, int offset) {
27     auto location = glGetAttribLocation(shader_program, attrib_name);
28     glEnableVertexAttribArray(location);
29     glVertexAttribPointer(location, count, GL_FLOAT, false, vertex_size * 4, offset * 4);
30 };
31 
32 struct Renderer {
33   nothrow:
34 	GLuint vertex_buffer;
35 	GLuint shader_program;
36 
37 	immutable GLuint texture_size = 1024;
38   immutable GLuint tile_size = 16;
39   immutable GLfloat tile_fraction = cast(float)tile_size / texture_size;
40   immutable GLfloat px_nudge = 0.5 / texture_size;
41 	GLuint num_verts = 0;
42 	GLuint level_num_verts;
43 	float[] buffer_data;
44 
45 	GLint light_uniform;
46 	GLint num_lights = 0;
47 	float[] light_data;
48 
49 	GLfloat camera_x = 0, camera_y = 0, camera_z = 0;
50 	GLint camera_uniform;
51 
52 	immutable string shader_attribute_vec = "attribute vec";
53 	immutable string shader_varying =
54 		"precision highp float;" ~
55 		"varying vec3 vl;" ~
56 		"varying vec2 vuv;";
57 	immutable string shader_uniform = "uniform ";
58 	immutable string shader_const_mat4 = "const mat4 ";
59 
60 	immutable string vertex_shader =
61 		shader_varying ~
62 		shader_attribute_vec ~ "3 p;" ~
63 		shader_attribute_vec ~ "2 uv;" ~
64 		shader_attribute_vec ~ "3 n;" ~
65 		shader_uniform ~ "vec3 cam;" ~
66 		shader_uniform ~ "float l[7*16];" ~
67 		shader_const_mat4 ~ "v=mat4(1,0,0,0,0,.707,.707,0,0,-.707,.707,0,0,-22.627,-22.627,1);" ~ // view
68 		shader_const_mat4 ~ "r=mat4(.977,0,0,0,0,1.303,0,0,0,0,-1,-1,0,0,-2,0);"~ // projection
69 		"void main(void){" ~
70 			"vl=vec3(0.3,0.3,0.6);" ~ // ambient color
71 			"for(int i=0; i<16; i++) {"~
72 				"vec3 lp=vec3(l[i*7],l[i*7+1],l[i*7+2]);" ~ // light position
73 				"vl+=vec3(l[i*7+3],l[i*7+4],l[i*7+5])" ~ // light color *
74 					"*max(dot(n,normalize(lp-p)),0.)" ~ // diffuse *
75 					"*(1./(l[i*7+6]*(" ~ // attentuation *
76 						"length(lp-p)" ~ // distance
77 					")));" ~
78 			"}" ~
79 			"vuv=uv;" ~
80 			"gl_Position=r*v*(vec4(p+cam,1.));" ~
81 		"}";
82 
83 	immutable string fragment_shader =
84 		shader_varying ~
85 		shader_uniform ~ "sampler2D s;" ~
86 		"void main(void){" ~
87 			"vec4 t=texture2D(s,vuv);" ~
88 			"if(t.a<.8)" ~ // 1) discard alpha
89 				"discard;" ~
90 			"if(t.r>0.95&&t.g>0.25&&t.b==0.0)" ~ // 2) red glowing spider eyes
91 				"gl_FragColor=t;" ~
92 			"else{" ~  // 3) calculate color with lights and fog
93 				"gl_FragColor=t*vec4(vl,1.);" ~
94 				"gl_FragColor.rgb*=smoothstep(" ~
95 					"112.,16.," ~ // fog far, near
96 					"gl_FragCoord.z/gl_FragCoord.w" ~ // fog depth
97 				");" ~
98 			"}" ~
99 			"gl_FragColor.rgb=floor(gl_FragColor.rgb*6.35)/6.35;" ~ // reduce colors to ~256
100 		"}";
101 
102   auto begin_build_level() {
103     num_verts = 0;
104   }
105 
106   auto end_build_level(float x, float y, float z) {
107     camera_x = x;
108     camera_y = y;
109 		camera_z = z;
110     level_num_verts = num_verts;
111   }
112 
113   auto update_camera(ref Player player) @trusted {
114     // center camera on player, apply damping
115     camera_x = camera_x * 0.92 - player.entity.x * 0.08;
116     camera_y = camera_y * 0.92 - player.entity.y * 0.08;
117     camera_z = camera_z * 0.92 - player.entity.z * 0.08;
118 
119     // add camera shake
120     camera_shake *= 0.9;
121     camera_x += camera_shake * (random()-0.5);
122     camera_z += camera_shake * (random()-0.5);
123   }
124   auto renderer_init(int width, int height) @trusted {
125 
126     buffer_data = allocator.make!(float[max_verts*8]);
127     light_data = allocator.make!(float[max_lights*7]);
128     vertex_buffer = glCreateBuffer();
129     glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
130     glBufferData(GL_ARRAY_BUFFER, buffer_data, GL_DYNAMIC_DRAW);
131 
132     shader_program = glCreateProgram();
133     glAttachShader(shader_program, compile_shader(GL_VERTEX_SHADER, vertex_shader));
134     glAttachShader(shader_program, compile_shader(GL_FRAGMENT_SHADER, fragment_shader));
135     glLinkProgram(shader_program);
136     glUseProgram(shader_program);
137 
138     camera_uniform = glGetUniformLocation(shader_program, "cam");
139     light_uniform = glGetUniformLocation(shader_program, "l");
140 
141     glEnable(GL_DEPTH_TEST);
142     glEnable(GL_BLEND);
143     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
144     glViewport(0,0,width,height);
145 
146     shader_program.enable_vertex_attrib("p", 3, 8, 0);
147     shader_program.enable_vertex_attrib("uv", 2, 8, 3);
148     shader_program.enable_vertex_attrib("n", 3, 8, 5);
149   }
150 
151   void renderer_bind_image(Handle image) {
152     auto texture_2d = GL_TEXTURE_2D;
153     glBindTexture(texture_2d, glCreateTexture());
154     glTexImage2D(texture_2d, 0, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, image);
155     glTexParameteri(texture_2d, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
156     glTexParameteri(texture_2d, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
157     glTexParameteri(texture_2d, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
158     glTexParameteri(texture_2d, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
159   }
160   auto renderer_prepare_frame() {
161     num_verts = level_num_verts;
162     num_lights = 0;
163 
164     // reset all lights
165     light_data[] = 1f;
166   }
167 
168   auto renderer_end_frame() {
169     glUniform3f(camera_uniform, camera_x, camera_y - 10, camera_z-30);
170     glUniform1fv(light_uniform, light_data);
171 
172     glClearColor(0,0,0,1);
173     glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
174 
175     glBufferData(GL_ARRAY_BUFFER, buffer_data, GL_DYNAMIC_DRAW);
176     glDrawArrays(GL_TRIANGLES, 0, num_verts);
177   }
178 
179   auto push_quad(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4, float nx, float ny, float nz, int tile) {
180     auto u = tile * tile_fraction + px_nudge;
181   import std.range : only;
182   import std.algorithm : copy;
183     only(
184                      x1, y1, z1, u, 0, nx, ny, nz,
185                      x2, y2, z2, u + tile_fraction - px_nudge, 0, nx, ny, nz,
186                      x3, y3, z3, u, 1, nx, ny, nz,
187                      x2, y2, z2, u + tile_fraction - px_nudge, 0, nx, ny, nz,
188                      x3, y3, z3, u, 1, nx, ny, nz,
189                      x4, y4, z4, u + tile_fraction - px_nudge, 1, nx, ny, nz
190          ).copy(buffer_data[num_verts*8..$]);
191     num_verts += 6;
192   }
193   auto push_sprite(float x, float y, float z, int tile) {
194 	// Only push sprites near to the camera
195 	if (
196 		abs(-x - camera_x) < 128 &&
197 		abs(-z - camera_z) < 128
198 	) {
199 		auto tilt = 3+(camera_z + z)/12; // tilt sprite when closer to camera
200 		push_quad(x, y + 6, z, x + 6, y + 6, z, x, y, z + tilt, x + 6, y, z + tilt, 0, 0, 1, tile);
201 	}
202 }
203   auto push_floor(float x, float z, int tile) {
204 	push_quad(x, 0, z, x + 8, 0, z, x, 0, z + 8, x + 8, 0, z + 8, 0,1,0, tile);
205 };
206   auto push_block(float x, float z, int tile_top, int tile_sites) {
207 	// tall blocks for certain tiles
208     bool tall = tile_sites == 8 || tile_sites == 9 || tile_sites == 17;
209     float y = tall ? 16 : 8;
210 
211 	push_quad(x, y, z, x + 8, y, z, x, y, z + 8, x + 8, y, z + 8, 0, 1, 0, tile_top); // top
212 	push_quad(x + 8, y, z, x + 8, y, z + 8, x + 8, 0, z, x + 8, 0, z + 8, 1, 0, 0, tile_sites); // right
213 	push_quad(x, y, z + 8, x + 8, y, z + 8, x, 0, z + 8, x + 8, 0, z + 8, 0, 0, 1, tile_sites); // front
214 	push_quad(x, y, z, x, y, z + 8, x, 0, z, x, 0, z + 8, -1, 0, 0, tile_sites); // left
215 };
216   auto push_light(float x, float y, float z, float r, float g, float b, float falloff) {
217   import std.algorithm : copy;
218 	// Only push lights near to the camera
219 	float max_light_distance = (128f + 1f/falloff); // cheap ass approximation
220 	if (
221       num_lights < max_lights &&
222 		abs(-x - camera_x) < max_light_distance &&
223 		abs(-z - camera_z) < max_light_distance
224 	) {
225     only(x, y, z, r, g, b, falloff).copy(light_data[num_lights*7..$]);
226 		num_lights++;
227 	}
228 }
229 }
230