liquids rewritten; better glutils
[dd2d.git] / xmain_d2d.d
blob7a5e00fd4ee6b8a241f034e1ddf02c5e80db0e8f
1 module xmain_d2d is aliced;
3 private:
4 import core.atomic;
5 import core.thread;
6 import core.time;
8 import glbinds;
9 import glutils;
10 import console;
11 import wadarc;
13 import iv.stream;
15 import d2dmap;
16 import d2dtpl;
17 import d2dactors;
18 import d2dgfx;
21 // ////////////////////////////////////////////////////////////////////////// //
22 import arsd.color;
23 import arsd.png;
26 // ////////////////////////////////////////////////////////////////////////// //
27 __gshared LevelMap map;
30 // ////////////////////////////////////////////////////////////////////////// //
31 __gshared SimpleWindow sdwindow;
34 public enum vlWidth = 800;
35 public enum vlHeight = 800;
36 __gshared int scale = 1;
37 __gshared bool scanlines = false;
40 // ////////////////////////////////////////////////////////////////////////// //
41 enum MaxLightRadius = 512;
44 // ////////////////////////////////////////////////////////////////////////// //
45 // for light
46 __gshared FBO[MaxLightRadius+1] fboOccluders, fboShadowMap;
47 __gshared Shader shadToPolar, shadBlur;
49 __gshared FBO fboLevel, fboOrigBack;
50 __gshared Shader shadScanlines;
51 __gshared Shader shadLiquidDistort, shadLiquidColor;
54 // ////////////////////////////////////////////////////////////////////////// //
55 void initOpenGL () {
56 glEnable(GL_TEXTURE_2D);
57 glDisable(GL_LIGHTING);
58 glDisable(GL_DITHER);
59 glDisable(GL_BLEND);
60 glDisable(GL_DEPTH_TEST);
62 // create shaders
63 shadScanlines = new Shader("scanlines", loadTextFile("data/shaders/srscanlines.frag"));
65 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("data/shaders/srliquid_distort.frag"));
66 shadLiquidDistort.exec({ shadLiquidDistort["tex0"] = 0; });
68 shadLiquidColor = new Shader("liquid_color", loadTextFile("data/shaders/srliquid_color.frag"));
69 shadLiquidColor.exec({ shadLiquidColor["tex0"] = 0; });
71 // lights
72 shadToPolar = new Shader("topolar", loadTextFile("data/shaders/srlight_topolar.frag"));
73 shadBlur = new Shader("blur", loadTextFile("data/shaders/srlight_blur.frag"));
74 shadBlur.exec({
75 shadBlur["tex0"] = 0;
76 shadBlur["tex1"] = 1;
77 });
78 //TODO: this sux!
79 foreach (int sz; 2..MaxLightRadius+1) {
80 fboOccluders[sz] = new FBO(sz*2, sz*2, Texture.Option.Clamp); // create occluders FBO
81 fboShadowMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp); // create 1d shadowmap FBO
84 // setup matrices
85 glMatrixMode(GL_MODELVIEW);
86 glLoadIdentity();
88 map.oglBuildMega();
90 fboLevel = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // final level render will be here
91 fboOrigBack = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // original background
93 // build noise texture for liquid shaders
95 glActiveTexture(GL_TEXTURE0+1);
97 import std.random : uniform;
98 auto img = new TrueColorImage(64, 64);
99 foreach (int y; 0..img.width) {
100 foreach (int x; 0..img.height) {
101 ubyte b = cast(ubyte)uniform(0, 256);
102 img.imageData.colors[y*img.width+x] = Color(b, b, b, 255);
105 auto tex = new Texture(img);
106 tex.activate;
110 glActiveTexture(GL_TEXTURE0+0);
111 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
112 orthoCamera(vlWidth, vlHeight);
114 map.initActors(); // this will precache sprites too
115 //map.doAllActorTicks();
117 { import core.memory : GC; GC.collect(); }
121 // ////////////////////////////////////////////////////////////////////////// //
122 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
123 if (lightRadius < 2) return;
124 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
125 int lightSize = lightRadius*2;
126 // is this light visible?
127 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
129 // draw shadow casters to fboOccludersId, light should be in the center
130 glUseProgram(0);
131 glDisable(GL_BLEND);
132 fboOccluders[lightRadius].exec({
133 glColor3f(0.0f, 0.0f, 0.0f);
134 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
135 glClear(GL_COLOR_BUFFER_BIT);
136 orthoCamera(lightSize, lightSize);
137 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
140 // build 1d shadow map to fboShadowMapId
141 fboShadowMap[lightRadius].exec({
142 shadToPolar.exec({
143 glColor3f(0.0f, 0.0f, 0.0f);
144 shadToPolar["lightTexSize"] = SVec2F(lightSize, lightSize);
145 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
146 glClear(GL_COLOR_BUFFER_BIT);
147 orthoCamera(lightSize, 1);
148 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
152 // blend light
153 fboLevel.exec({
154 shadBlur.exec({
155 shadBlur["lightTexSize"] = SVec2F(lightSize, lightSize);
156 glEnable(GL_BLEND);
157 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
158 // set light color
159 glColor4f(lcol.x, lcol.y, lcol.z, lcol.w);
160 orthoCamera(map.width*8, map.height*8);
161 drawAtXY(fboShadowMap[lightRadius].tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize);
167 // ////////////////////////////////////////////////////////////////////////// //
168 __gshared int lightX = vlWidth/2, lightY = vlHeight/2;
169 __gshared int mapOfsX, mapOfsY;
170 __gshared bool movement = false;
173 void renderScene () {
174 enum BackIntens = 0.05f;
176 glUseProgram(0);
178 fboLevel.exec({
179 glDisable(GL_BLEND);
180 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
181 glClear(GL_COLOR_BUFFER_BIT);
184 // build background layer
185 glUseProgram(0);
186 fboOrigBack.exec({
187 glDisable(GL_BLEND);
188 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
189 glClear(GL_COLOR_BUFFER_BIT);
190 //glEnable(GL_BLEND);
191 //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
192 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
193 orthoCamera(map.width*8, map.height*8);
194 glEnable(GL_BLEND);
195 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
196 // draw sky
198 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
199 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
200 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
201 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
203 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
204 // draw background
205 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
206 // draw distorted liquid areas
207 shadLiquidDistort.exec({
208 __gshared float iGlobalTime = 0.0;
209 iGlobalTime += 0.04;
210 shadLiquidDistort["iGlobalTime"] = iGlobalTime;
211 shadLiquidDistort["liquidColorMul"] = SVec4F(1.0f, 1.0f, 1.0f, 1.0f);
212 drawAtXY(map.texgl.ptr[map.Water], 0, 0);
213 shadLiquidDistort["liquidColorMul"] = SVec4F(0.6f, 0.6f, 0.6f, 1.0f);
214 drawAtXY(map.texgl.ptr[map.Lava], 0, 0);
215 shadLiquidDistort["liquidColorMul"] = SVec4F(1.0f, 1.0f, 1.0f, 1.0f);
216 drawAtXY(map.texgl.ptr[map.Acid], 0, 0);
218 // monsters, items
219 glColor3f(1.0f, 1.0f, 1.0f);
220 foreach (auto actor; actors) {
221 if (actor.frame is null || actor.dead) continue;
222 //conwriteln("(", actor.x, ", ", actor.y, ") ", actor.frame.tex.tid, " ", actor.frame.tex.width, "x", actor.frame.tex.height);
223 //drawAtXY(actor.frame.tex, actor.x/*-actor.frame.vga.sx*/, actor.y/*-actor.frame.vga.sy*/);
224 //int y = LevelMap.MapSize*8-actor.y-1/*-(actor.height-actor.frame.vga.sy)*/;
225 //int y = LevelMap.MapSize*8-actor.y+(actor.frame.vga.sy-actor.frame.vga.height);
226 int y = actor.y-actor.frame.vga.sy;
227 drawAtXY(actor.frame.tex, actor.x-actor.frame.vga.sx, y);
231 // additive lights
232 glEnable(GL_BLEND);
233 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
235 glActiveTexture(GL_TEXTURE0+1);
236 glBindTexture(GL_TEXTURE_2D, /*map.texgl.ptr[map.Back].tid*/fboOrigBack.tex.tid);
237 //glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.Back].tid);
238 glActiveTexture(GL_TEXTURE0+0);
240 renderLight( 27, 391-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 100);
241 renderLight(542, 424-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 100);
242 renderLight(377, 368-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 32);
243 renderLight(147, 288-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 64);
244 renderLight( 71, 200-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
245 renderLight(249, 200-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
246 renderLight(426, 200-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
247 renderLight(624, 200-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
248 renderLight(549, 298-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 64);
249 renderLight( 74, 304-0, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 32);
251 renderLight(24*8+4, (24+18)*8-2, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
252 foreach (immutable _; 0..1) {
253 renderLight(lightX, lightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 256);
256 //glActiveTexture(GL_TEXTURE0+1);
257 //glBindTexture(GL_TEXTURE_2D, 0);
258 //glActiveTexture(GL_TEXTURE0+0);
260 // draw final opaque and full-lit parts
261 fboLevel.exec({
262 glEnable(GL_BLEND);
263 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
264 glColor3f(1.0f, 1.0f, 1.0f);
265 orthoCamera(map.width*8, map.height*8);
266 // various occluders
267 drawAtXY(map.texgl.ptr[map.LightMask], 0, 0);
268 // foreground -- hide secrets, draw lifts and such
269 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
270 // now do liquid coloring
271 shadLiquidColor.exec({
272 // additive blend
273 //glEnable(GL_BLEND);
274 // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
275 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
276 //glDisable(GL_BLEND);
277 //glColor3f(1.0f, 1.0f, 1.0f);
278 shadLiquidColor["liquidColor"] = SVec4F(0.0f, 0.0f, 0.4f, 0.5f);
279 drawAtXY(map.texgl.ptr[map.Water], 0, 0);
280 shadLiquidColor["liquidColor"] = SVec4F(0.6f, 0.0f, 0.0f, 0.4f);
281 drawAtXY(map.texgl.ptr[map.Lava], 0, 0);
282 shadLiquidColor["liquidColor"] = SVec4F(0.1f, 0.4f, 0.0f, 0.5f);
283 drawAtXY(map.texgl.ptr[map.Acid], 0, 0);
287 // draw scaled level
288 shadScanlines.exec({
289 shadScanlines["scanlines"] = scanlines;
290 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
291 glClear(GL_COLOR_BUFFER_BIT);
292 orthoCamera(vlWidth, vlHeight);
293 //orthoCamera(map.width*8*scale, map.height*8*scale);
294 //glMatrixMode(GL_MODELVIEW);
295 //glLoadIdentity();
296 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
297 // somehow, FBO objects are mirrored; wtf?!
298 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
299 //glLoadIdentity();
304 // ////////////////////////////////////////////////////////////////////////// //
305 // rendering thread
306 shared int diedie = 0;
309 void renderThread () {
310 try {
311 MonoTime prevFrameStartTime = MonoTime.currTime;
312 version(use_vsync) {} else MonoTime ltt = MonoTime.currTime;
313 MonoTime lasttime = MonoTime.currTime;
314 MonoTime time;
315 enum MaxFPSFrames = 16;
316 float frtimes = 0.0f;
317 int framenum = 0;
318 int prevFPS = -1;
319 bool first = true;
320 for (;;) {
321 if (sdwindow.closed) break;
322 if (atomicLoad(diedie) > 0) break;
324 time = MonoTime.currTime;
325 version(use_vsync) {
326 } else {
327 // max 60 FPS; capped by vsync
328 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
329 if (!first && (time-ltt).total!"msecs" < 16) {
330 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
331 import core.sys.posix.signal : timespec;
332 import core.sys.posix.time : nanosleep;
333 timespec ts = void;
334 ts.tv_sec = 0;
335 ts.tv_nsec = (16-cast(int)((time-ltt).total!"msecs"))*1000*1000; // milli to nano
336 nanosleep(&ts, null); // idc how much time was passed
337 time = MonoTime.currTime;
339 ltt = time;
340 first = false;
344 auto frameTime = cast(float)(time-prevFrameStartTime).total!"msecs"/1000.0f;
345 prevFrameStartTime = time;
347 //{ import core.stdc.stdio; printf("frametime: %f\n", frameTime*1000.0f); }
348 //{ import core.stdc.stdio; printf("FPS: %d\n", cast(int)(1.0f/frameTime)); }
349 frtimes += frameTime;
350 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
351 import std.string : format;
352 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
353 if (newFPS != prevFPS) {
354 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
355 prevFPS = newFPS;
357 framenum = 0;
358 frtimes = 0.0f;
361 bool ctset;
363 sdwindow.mtLock();
364 scope(exit) sdwindow.mtUnlock();
365 ctset = sdwindow.setAsCurrentOpenGlContextNT;
367 if (!ctset) {
368 { import core.stdc.stdio; printf(" FUUUU\n"); }
369 //glXMakeCurrent(sdwindow.display, 0, null);
370 import core.sys.posix.signal : timespec;
371 import core.sys.posix.time : nanosleep;
372 timespec ts = void;
373 ts.tv_sec = 0;
374 ts.tv_nsec = 16*1000*1000; // milli to nano
375 nanosleep(&ts, null); // idc how much time was passed
376 time = MonoTime.currTime;
377 } else {
378 __gshared frp = 0;
379 if (--frp <= 0) {
380 frp = 3;
381 map.doAllActorTicks();
383 renderScene();
385 sdwindow.mtLock();
386 scope(exit) sdwindow.mtUnlock();
387 sdwindow.swapOpenGlBuffers();
388 glFinish();
389 sdwindow.releaseCurrentOpenGlContext();
394 } catch (Exception e) {
395 import core.stdc.stdio;
396 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
397 for (;;) {
398 if (sdwindow.closed) break;
399 if (atomicLoad(diedie) > 0) break;
400 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
401 import core.sys.posix.signal : timespec;
402 import core.sys.posix.time : nanosleep;
403 timespec ts = void;
404 ts.tv_sec = 0;
405 ts.tv_nsec = 100*1000*1000; // milli to nano
406 nanosleep(&ts, null); // idc how much time was passed
409 atomicStore(diedie, 2);
413 // ////////////////////////////////////////////////////////////////////////// //
414 void closeWindow () {
415 if (atomicLoad(diedie) != 2) {
416 atomicStore(diedie, 1);
417 while (atomicLoad(diedie) != 2) {}
419 if (!sdwindow.closed) {
420 flushGui();
421 sdwindow.close();
426 // ////////////////////////////////////////////////////////////////////////// //
427 __gshared Thread renderTid;
430 void main () {
431 static void setDP () {
432 version(rdmd) {
433 setDataPath("data");
434 } else {
435 import std.file : thisExePath;
436 import std.path : dirName;
437 setDataPath(thisExePath.dirName);
439 addWad("/home/ketmar/k8prj/doom2d-tl/data/doom2d.wad");
440 //addWad("/home/ketmar/k8prj/doom2d-tl/data/meat.wad");
441 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm.wad");
442 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm1.wad");
443 //addWad("/home/ketmar/k8prj/doom2d-tl/data/superdm.wad");
444 //addWad("/home/ketmar/k8prj/doom2d-tl/data/zadoomka.wad");
447 setDP();
448 loadTemplates();
449 loadPalette();
451 setOpenGLContextVersion(3, 2); // up to GLSL 150
452 //openGLContextCompatible = false;
454 map = new LevelMap("maps/map01.d2m");
456 //mapOfsX = 8*26;
457 //mapOfsY = 8*56;
458 map.getThingPos(ThingId.Player1, &mapOfsX, &mapOfsY);
459 // fix viewport
460 mapOfsX = (mapOfsX*2)-vlWidth/2;
461 if (mapOfsX+vlWidth > map.width*16) mapOfsX = map.width*16-vlWidth;
462 if (mapOfsX < 0) mapOfsX = 0;
463 mapOfsY = (mapOfsY*2)-vlHeight/2;
464 if (mapOfsY+vlHeight > map.height*16) mapOfsY = map.height*16-vlHeight;
465 if (mapOfsY < 0) mapOfsY = 0;
466 scale = 2;
468 sdwindow = new SimpleWindow(vlWidth, vlHeight, "D2D", OpenGlOptions.yes, Resizablity.fixedSize);
470 sdwindow.visibleForTheFirstTime = delegate () {
471 sdwindow.setAsCurrentOpenGlContext(); // make this window active
472 glbindLoadFunctions();
475 import core.stdc.stdio;
476 printf("GL version: %s\n", glGetString(GL_VERSION));
477 GLint l, h;
478 glGetIntegerv(GL_MAJOR_VERSION, &h);
479 glGetIntegerv(GL_MINOR_VERSION, &l);
480 printf("version: %d.%d\n", h, l);
481 printf("shader version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
482 GLint svcount;
483 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
484 if (svcount > 0) {
485 printf("%d shader versions supported:\n", svcount);
486 foreach (GLuint n; 0..svcount) printf(" %d: %s\n", n, glGetStringi(GL_SHADING_LANGUAGE_VERSION, n));
489 GLint ecount;
490 glGetIntegerv(GL_NUM_EXTENSIONS, &ecount);
491 if (ecount > 0) {
492 printf("%d extensions supported:\n", ecount);
493 foreach (GLuint n; 0..ecount) printf(" %d: %s\n", n, glGetStringi(GL_EXTENSIONS, n));
498 // check if we have sufficient shader version here
500 bool found = false;
501 GLint svcount;
502 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
503 if (svcount > 0) {
504 foreach (GLuint n; 0..svcount) {
505 import core.stdc.string : strncmp;
506 auto v = glGetStringi(GL_SHADING_LANGUAGE_VERSION, n);
507 if (v is null) continue;
508 if (strncmp(v, "120", 3) != 0) continue;
509 if (v[3] > ' ') continue;
510 found = true;
511 break;
514 if (!found) assert(0, "can't find OpenGL GLSL 120");
516 auto adr = glGetProcAddress("glTexParameterf");
517 if (adr is null) assert(0);
520 sdwindow.vsync = false;
521 //sdwindow.useGLFinish = false;
522 initOpenGL();
523 //sdwindow.redrawOpenGlScene();
524 if (!sdwindow.releaseCurrentOpenGlContext()) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
525 if (!renderTid) {
526 renderTid = new Thread(&renderThread);
527 renderTid.start();
531 //sdwindow.redrawOpenGlScene = delegate () { renderScene(); };
533 enum MSecsPerFrame = 1000/30; /* 30 is FPS */
535 uint[8] frameTimes = 1000;
536 enum { Left, Right, Up, Down }
537 bool[4] pressed = false;
539 sdwindow.eventLoop(MSecsPerFrame,
540 delegate () {
541 if (sdwindow.closed) return;
542 if (pressed[Left]) mapOfsX -= 8;
543 if (pressed[Right]) mapOfsX += 8;
544 if (pressed[Up]) mapOfsY -= 8;
545 if (pressed[Down]) mapOfsY += 8;
546 import std.math : cos, sin;
547 __gshared float itime = 0.0;
548 itime += 0.02;
549 if (movement) {
550 mapOfsX = cast(int)(800.0/2.0+cos(itime)*220.0);
551 mapOfsY = cast(int)(800.0/2.0+120.0+sin(itime)*160.0);
553 if (scale == 1) mapOfsX = mapOfsY = 0;
554 //sdwindow.redrawOpenGlSceneNow();
556 delegate (KeyEvent event) {
557 if (sdwindow.closed) return;
558 if (event.pressed && event.key == Key.Escape) { closeWindow(); return; }
559 switch (event.key) {
560 case Key.Left: pressed[Left] = event.pressed; break;
561 case Key.Right: pressed[Right] = event.pressed; break;
562 case Key.Up: pressed[Up] = event.pressed; break;
563 case Key.Down: pressed[Down] = event.pressed; break;
564 default:
567 delegate (MouseEvent event) {
568 lightX = event.x/scale;
569 lightY = event.y/scale;
570 lightX += mapOfsX/scale;
571 lightY += mapOfsY/scale;
573 delegate (dchar ch) {
574 if (ch == 'q') { closeWindow(); return; }
575 if (ch == 's') scanlines = !scanlines;
576 if (ch == '1') scale = 1;
577 if (ch == '2') scale = 2;
578 if (ch == ' ') movement = !movement;
581 closeWindow();
582 flushGui();