1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module render
is aliced
;
20 //version = dont_use_vsync;
27 import std
.concurrency
;
50 // ////////////////////////////////////////////////////////////////////////// //
55 // ////////////////////////////////////////////////////////////////////////// //
56 public __gshared
bool cheatNoDoors
= false;
57 public __gshared
bool cheatNoWallClip
= false;
60 // ////////////////////////////////////////////////////////////////////////// //
61 public __gshared SimpleWindow sdwindow
;
64 public enum vlWidth
= 800;
65 public enum vlHeight
= 800;
66 __gshared
int scale
= 2;
68 public int getScale () nothrow @trusted @nogc { pragma(inline
, true); return scale
; }
71 // ////////////////////////////////////////////////////////////////////////// //
72 __gshared
bool levelLoaded
= false;
75 // ////////////////////////////////////////////////////////////////////////// //
76 __gshared
bool scanlines
= false;
77 __gshared
bool doLighting
= true;
78 __gshared
bool gamePaused
= false;
79 __gshared
bool rConsoleVisible
= false;
80 __gshared
int rConsoleHeight
= 16*3;
81 shared bool editMode
= false;
82 shared bool vquitRequested
= false;
84 public @property bool inEditMode () nothrow @trusted @nogc { import core
.atomic
; return atomicLoad(editMode
); }
85 @property void inEditMode (bool v
) nothrow @trusted @nogc { import core
.atomic
; atomicStore(editMode
, v
); }
87 public @property bool quitRequested () nothrow @trusted @nogc { import core
.atomic
; return atomicLoad(vquitRequested
); }
88 public @property bool conVisible () nothrow @trusted @nogc { return rConsoleVisible
; }
91 // ////////////////////////////////////////////////////////////////////////// //
92 __gshared
char[] concmdbuf
;
93 __gshared
uint concmdbufpos
;
94 private import core
.sync
.mutex
: Mutex
;
95 __gshared Mutex concmdbufLock
;
96 shared static this () { concmdbuf
.length
= 65536; concmdbufLock
= new Mutex(); }
98 __gshared
char[4096] concli
= 0;
99 __gshared
uint conclilen
= 0;
101 __gshared
char[4096][128] concmdhistory
= void;
102 __gshared
int conhisidx
= -1;
103 shared static this () { foreach (ref hb
; concmdhistory
) hb
[] = 0; }
105 __gshared
int conskiplines
= 0;
108 const(char)[] conhisAt (int idx
) {
109 if (idx
< 0 || idx
>= concmdhistory
.length
) return null;
110 const(char)[] res
= concmdhistory
.ptr
[idx
][];
112 while (pos
< res
.length
&& res
.ptr
[pos
]) ++pos
;
117 int conhisFind (const(char)[] cmd
) {
118 while (cmd
.length
&& cmd
[$-1] <= 32) cmd
= cmd
[0..$-1];
119 if (cmd
.length
> concmdhistory
.ptr
[0].length
) cmd
= cmd
[0..concmdhistory
.ptr
[0].length
];
120 if (cmd
.length
== 0) return -1;
121 foreach (int idx
; 0..cast(int)concmdhistory
.length
) {
122 auto c
= conhisAt(idx
);
123 while (c
.length
> 0 && c
[$-1] <= 32) c
= c
[0..$-1];
124 if (c
== cmd
) return idx
;
130 void conhisAdd (const(char)[] cmd
) {
131 while (cmd
.length
&& cmd
[$-1] <= 32) cmd
= cmd
[0..$-1];
132 if (cmd
.length
> concmdhistory
.ptr
[0].length
) cmd
= cmd
[0..concmdhistory
.ptr
[0].length
];
133 if (cmd
.length
== 0) return;
134 auto idx
= conhisFind(cmd
);
137 foreach (immutable c
; idx
+1..concmdhistory
.length
) concmdhistory
.ptr
[c
-1][] = concmdhistory
.ptr
[c
][];
140 foreach (immutable c
; 1..concmdhistory
.length
; reverse
) concmdhistory
.ptr
[c
][] = concmdhistory
.ptr
[c
-1][];
141 concmdhistory
.ptr
[0][] = 0;
142 concmdhistory
.ptr
[0][0..cmd
.length
] = cmd
[];
146 void concmdAdd (const(char)[] s
) {
148 if (concmdbuf
.length
-concmdbufpos
< s
.length
+1) {
149 concmdbuf
.assumeSafeAppend
.length
+= s
.length
-(concmdbuf
.length
-concmdbufpos
)+512;
151 if (concmdbufpos
> 0 && concmdbuf
[concmdbufpos
-1] != '\n') concmdbuf
.ptr
[concmdbufpos
++] = '\n';
152 concmdbuf
[concmdbufpos
..concmdbufpos
+s
.length
] = s
[];
153 concmdbufpos
+= s
.length
;
158 void concmdDoAll () {
159 if (concmdbufpos
== 0) return;
160 scope(exit
) concmdbufpos
= 0;
161 auto ebuf
= concmdbufpos
;
162 const(char)[] s
= concmdbuf
[0..concmdbufpos
];
165 auto cmd
= conGetCommand(s
);
166 if (cmd
is null) break;
169 } catch (Exception e
) {
170 conwriteln("***ERROR: ", e
.msg
);
173 if (concmdbufpos
<= ebuf
) break;
174 s
= concmdbuf
[ebuf
..concmdbufpos
];
180 void concliChar (char ch
) {
181 __gshared
int prevWasEmptyAndTab
= 0;
183 concmdbufLock
.lock();
184 scope(exit
) concmdbufLock
.unlock();
189 if (conclilen
== 0) {
190 if (++prevWasEmptyAndTab
< 2) return;
192 prevWasEmptyAndTab
= 0;
195 string minPfx
= null;
196 // find longest command
197 foreach (auto name
; conByCommand
) {
198 if (name
.length
>= conclilen
&& name
.length
> minPfx
.length
&& name
[0..conclilen
] == concli
[0..conclilen
]) minPfx
= name
;
200 //conwriteln("longest command: [", minPfx, "]");
201 // find longest prefix
202 foreach (auto name
; conByCommand
) {
203 if (name
.length
< conclilen
) continue;
204 if (name
[0..conclilen
] != concli
[0..conclilen
]) continue;
206 while (pos
< name
.length
&& pos
< minPfx
.length
&& minPfx
.ptr
[pos
] == name
.ptr
[pos
]) ++pos
;
207 if (pos
< minPfx
.length
) minPfx
= minPfx
[0..pos
];
209 if (minPfx
.length
> concli
.length
) minPfx
= minPfx
[0..concli
.length
];
210 //conwriteln("longest prefix : [", minPfx, "]");
211 if (minPfx
.length
>= conclilen
) {
213 bool doRet
= (minPfx
.length
> conclilen
);
215 concli
[0..minPfx
.length
] = minPfx
[];
216 conclilen
= cast(uint)minPfx
.length
;
217 if (conclilen
< concli
.length
&& conHasCommand(minPfx
)) {
218 concli
.ptr
[conclilen
++] = ' ';
224 // nope, print all available commands
225 bool needDelimiter
= true;
226 foreach (auto name
; conByCommand
) {
228 if (name
.length
< conclilen
) continue;
229 if (name
[0..conclilen
] != concli
[0..conclilen
]) continue;
231 if (needDelimiter
) { conwriteln("----------------"); needDelimiter
= false; }
236 // process other keys
237 prevWasEmptyAndTab
= 0;
240 if (conclilen
> 0) { conLastChange
= 0; --conclilen
; }
245 if (conskiplines
) { conskiplines
= 0; conLastChange
= 0; }
249 conhisAdd(concli
[0..conclilen
]);
250 concmdAdd(concli
[0..conclilen
]);
257 if (conclilen
> 0) { conLastChange
= 0; conclilen
= 0; }
263 auto cmd
= conhisAt(conhisidx
);
264 if (cmd
.length
== 0) {
267 concli
[0..cmd
.length
] = cmd
[];
268 conclilen
= cast(uint)cmd
.length
;
276 auto cmd
= conhisAt(conhisidx
);
277 if (cmd
.length
== 0 && conhisidx
< -1) {
280 concli
[0..cmd
.length
] = cmd
[];
281 conclilen
= cast(uint)cmd
.length
;
288 int lnx
= (rConsoleHeight
-4)/conCharHeight
-2;
289 if (lnx
< 1) lnx
= 1;
296 if (conskiplines
> 0) {
297 int lnx
= (rConsoleHeight
-4)/conCharHeight
-2;
298 if (lnx
< 1) lnx
= 1;
299 if ((conskiplines
-= lnx
) < 0) conskiplines
= 0;
305 if (ch
< ' ' || ch
> 127) return;
306 if (conclilen
>= concli
.length
) return;
307 concli
.ptr
[conclilen
++] = ch
;
312 // ////////////////////////////////////////////////////////////////////////// //
313 shared static this () {
314 conRegVar
!doLighting("r_lighting", "dynamic lighting");
315 conRegVar
!gamePaused("g_pause", "pause game");
316 conRegVar
!rConsoleVisible("r_console", "console visibility");
317 conRegVar
!rConsoleHeight(16*3, vlHeight
, "r_conheight");
318 rConsoleHeight
= vlHeight
-vlHeight
/3;
319 rConsoleHeight
= vlHeight
/2;
320 conRegVar
!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
321 conRegVar
!scale(1, 2, "r_scale");
323 cheatNoDoors
= !cheatNoDoors
;
324 if (cheatNoDoors
) conwriteln("player ignores doors"); else conwriteln("player respects doors");
325 })("nodoorclip", "ignore doors");
327 cheatNoWallClip
= !cheatNoWallClip
;
328 if (cheatNoWallClip
) conwriteln("player ignores walls"); else conwriteln("player respects walls");
329 })("nowallclip", "ignore walls");
332 atomicStore(vquitRequested
, true);
333 })("quit", "quit game");
334 conRegFunc
!((const(char)[] msg
, int pauseMsecs
=3000, bool noreplace
=false) {
336 auto s
= buf
.conFormatStr(msg
);
337 if (s
.length
) postAddMessage(s
, pauseMsecs
, noreplace
);
338 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
342 // ////////////////////////////////////////////////////////////////////////// //
344 __gshared
ubyte[] prevFrameActorsData
;
345 __gshared
uint[65536] prevFrameActorOfs
; // uint.max-1: dead; uint.max: last
346 __gshared MonoTime lastthink
= MonoTime
.zero
; // for interpolator
347 __gshared MonoTime nextthink
= MonoTime
.zero
;
348 __gshared
bool frameInterpolation
= true;
351 // ////////////////////////////////////////////////////////////////////////// //
353 struct AttachedLightInfo
{
360 int w
, h
; // for ambient lights
366 __gshared AttachedLightInfo
[65536] attachedLights
;
367 __gshared
uint attachedLightCount
= 0;
370 // ////////////////////////////////////////////////////////////////////////// //
371 enum MaxLightRadius
= 255;
374 // ////////////////////////////////////////////////////////////////////////// //
376 __gshared FBO
[MaxLightRadius
+1] fboDistMap
;
377 __gshared FBO fboOccluders
;
378 __gshared Shader shadToPolar
, shadBlur
, shadBlurOcc
, shadAmbient
;
379 __gshared TrueColorImage editorImg
;
380 __gshared FBO fboEditor
;
381 __gshared FBO fboConsole
;
382 __gshared Texture texSigil
;
384 __gshared FBO fboLevel
, fboLevelLight
, fboOrigBack
, fboLMaskSmall
;
385 __gshared Shader shadScanlines
;
386 __gshared Shader shadLiquidDistort
;
389 // ////////////////////////////////////////////////////////////////////////// //
391 public void initOpenGL () {
394 glEnable(GL_TEXTURE_2D
);
395 glDisable(GL_LIGHTING
);
396 glDisable(GL_DITHER
);
398 glDisable(GL_DEPTH_TEST
);
401 shadScanlines
= new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
403 shadLiquidDistort
= new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
404 shadLiquidDistort
.exec((Shader shad
) {
405 shad
["texLqMap"] = 0;
409 shadToPolar
= new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
410 shadToPolar
.exec((Shader shad
) {
412 shad
["texOccFull"] = 2;
413 shad
["texOccSmall"] = 3;
416 shadBlur
= new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
417 shadBlur
.exec((Shader shad
) {
421 shad
["texOccSmall"] = 3;
424 shadBlurOcc
= new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
425 shadBlurOcc
.exec((Shader shad
) {
429 shad
["texOccSmall"] = 3;
432 shadAmbient
= new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
433 shadAmbient
.exec((Shader shad
) {
436 shad
["texOccSmall"] = 3;
439 fboOccluders
= new FBO(MaxLightRadius
*2, MaxLightRadius
*2, Texture
.Option
.Clamp
, Texture
.Option
.Linear
);
441 foreach (int sz
; 2..MaxLightRadius
+1) {
442 fboDistMap
[sz
] = new FBO(sz
*2, 1, Texture
.Option
.Clamp
, Texture
.Option
.Linear
); // create 1d distance map FBO
445 editorImg
= new TrueColorImage(vlWidth
, vlHeight
);
446 editorImg
.imageData
.colors
[] = Color(0, 0, 0, 0);
447 fboEditor
= new FBO(vlWidth
, vlHeight
, Texture
.Option
.Nearest
);
449 texSigil
= new Texture("console/sigil_of_baphomet.png", Texture
.Option
.Nearest
);
450 fboConsole
= new FBO(vlWidth
, vlHeight
, Texture
.Option
.Nearest
);
454 glMatrixMode(GL_MODELVIEW
);
459 loadAllMonsterGraphics();
463 // ////////////////////////////////////////////////////////////////////////// //
464 __gshared
ulong conLastChange
= 0;
466 void renderConsoleFBO () {
468 if (conLastChange
== cbufLastChange
) return;
469 concmdbufLock
.lock();
470 scope(exit
) concmdbufLock
.unlock();
472 conLastChange
= cbufLastChange
;
473 //foreach (auto s; conbufLinesRev) stdout.writeln(s, "|");
474 int skipLines
= conskiplines
;
475 fboConsole
.exec((FBO me
) {
477 orthoCamera(me
.width
, me
.height
);
479 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
480 glClear(GL_COLOR_BUFFER_BIT
);
483 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
484 glRectf(0, 0, me.width-1, me.height-1);
488 glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
490 glColor4f(1.0f, 1.0f, 1.0f, 0.2f);
491 drawAtXY(texSigil
, (me
.width
-texSigil
.width
)/2, (vlHeight
-rConsoleHeight
)/2+(me
.height
-texSigil
.height
)/2);
493 int y
= me
.height
-conCharHeight
-2;
496 scope(exit
) glPopMatrix();
497 glTranslatef(XOfs
, y
, 0);
498 int w
= conCharWidth('>');
499 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
501 uint spos
= conclilen
;
503 char ch
= concli
.ptr
[spos
-1];
504 if (w
+conCharWidth(ch
) > me
.width
-XOfs
*2-12) break;
505 w
+= conCharWidth(ch
);
508 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
509 foreach (char ch
; concli
[spos
..conclilen
]) conDrawChar(ch
);
512 glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
513 glRectf(0, 0, 12, 16);
517 glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
519 scope(exit
) glPopMatrix();
520 glTranslatef(XOfs
, y
, 0);
521 foreach (auto line
; conbufLinesRev
) {
522 if (line
.length
== 0) {
523 if (skipLines
-- <= 0) {
527 glTranslatef(XOfs
, y
, 0);
530 usize pos
= line
.length
;
535 char ch
= line
[sp
-1];
536 if (w
+conCharWidth(ch
) > me
.width
-XOfs
*2) break;
537 w
+= conCharWidth(ch
);
540 if (skipLines
-- <= 0) {
541 foreach (immutable p
; sp
..pos
) conDrawChar(line
[p
]);
545 glTranslatef(XOfs
, y
, 0);
547 if (sp
== 0 || y
< 0) break;
557 // ////////////////////////////////////////////////////////////////////////// //
558 // should be called when OpenGL is initialized
559 void loadMap (string mapname
) {
560 mapscripts
.runUnloading(); // "map unloading" script
563 if (map
!is null) map
.clear();
564 map
= new LevelMap(mapname
);
565 curmapname
= mapname
;
567 ugInit(map
.width
*TileSize
, map
.height
*TileSize
);
572 if (fboLevel
!is null) fboLevel
.clear();
573 if (fboLevelLight
!is null) fboLevelLight
.clear();
574 if (fboOrigBack
!is null) fboOrigBack
.clear();
575 if (fboLMaskSmall
!is null) fboLMaskSmall
.clear();
577 fboLevel
= new FBO(map
.width
*TileSize
, map
.height
*TileSize
, Texture
.Option
.Nearest
); // final level render will be here
578 fboLevelLight
= new FBO(map
.width
*TileSize
, map
.height
*TileSize
, Texture
.Option
.Nearest
); // level lights will be rendered here
579 fboOrigBack
= new FBO(map
.width
*TileSize
, map
.height
*TileSize
, Texture
.Option
.Nearest
/*, Texture.Option.Depth*/); // background+foreground
580 fboLMaskSmall
= new FBO(map
.width
, map
.height
, Texture
.Option
.Nearest
); // small lightmask
582 shadToPolar
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
-1, map
.height
*TileSize
-1); });
583 shadBlur
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
, map
.height
*TileSize
); });
584 shadBlurOcc
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
, map
.height
*TileSize
); });
585 shadAmbient
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
, map
.height
*TileSize
); });
587 glActiveTexture(GL_TEXTURE0
+0);
588 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
589 orthoCamera(vlWidth
, vlHeight
);
591 Actor
.resetStorage();
594 mapscripts
.runInit();
597 mapscripts
.runLoaded();
599 // save first snapshot
600 if (prevFrameActorsData
.length
== 0) prevFrameActorsData
= new ubyte[](Actor
.actorSize
*65536); // ~15-20 megabytes
601 prevFrameActorOfs
[] = uint.max
; // just for fun
602 Actor
.saveSnapshot(prevFrameActorsData
[], prevFrameActorOfs
.ptr
);
606 { import core
.memory
: GC
; GC
.collect(); }
610 // ////////////////////////////////////////////////////////////////////////// //
612 __gshared
uint mapTilesChanged
= 0;
615 //WARNING! this can be called only from DACS, so we don't have to sync it!
616 public void mapDirty (uint layermask
) { mapTilesChanged |
= layermask
; }
619 void rebuildMapMegaTextures () {
621 //mapTilesChanged = false;
622 //map.clearMegaTextures();
623 map
.oglBuildMega(mapTilesChanged
);
625 dotsAwake(); // let dormant dots fall
629 // ////////////////////////////////////////////////////////////////////////// //
632 enum Phase
{ FadeIn
, Stay
, FadeOut
}
641 //private import core.sync.mutex : Mutex;
643 __gshared Message
[128] messages
;
644 __gshared
uint messagesUsed
= 0;
646 //__gshared Mutex messageLock;
647 //shared static this () { messageLock = new Mutex(); }
650 void addMessage (const(char)[] msgtext
, int pauseMsecs
=3000, bool noreplace
=false) {
651 //messageLock.lock();
652 //scope(exit) messageLock.unlock();
653 if (msgtext
.length
== 0) return;
655 if (pauseMsecs
<= 50) return;
656 if (messagesUsed
== messages
.length
) {
657 // remove top message
658 foreach (immutable cidx
; 1..messagesUsed
) messages
.ptr
[cidx
-1] = messages
.ptr
[cidx
];
659 messages
.ptr
[0].alpha
= 255;
663 if (!noreplace
&& messagesUsed
== 1) {
664 switch (messages
.ptr
[0].phase
) {
665 case Message
.Phase
.FadeIn
:
666 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
668 case Message
.Phase
.Stay
:
669 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
670 messages
.ptr
[0].alpha
= 255;
675 auto msg
= messages
.ptr
+messagesUsed
;
677 msg
.phase
= Message
.Phase
.FadeIn
;
679 msg
.pauseMsecs
= pauseMsecs
;
681 if (msgtext
.length
> msg
.text
.length
) {
682 msg
.text
= msgtext
[0..msg
.text
.length
];
683 msg
.textlen
= msg
.text
.length
;
685 msg
.text
[0..msgtext
.length
] = msgtext
[];
686 msg
.textlen
= msgtext
.length
;
691 void doMessages (MonoTime curtime
) {
692 //messageLock.lock();
693 //scope(exit) messageLock.unlock();
695 if (messagesUsed
== 0) return;
697 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
703 final switch (msg
.phase
) {
704 case Message
.Phase
.FadeIn
:
705 if ((msg
.alpha
+= 10) >= 255) {
706 msg
.phase
= Message
.Phase
.Stay
;
707 msg
.removeTime
= curtime
+dur
!"msecs"(msg
.pauseMsecs
);
708 goto case; // to stay
710 glColor4f(1.0f, 1.0f, 1.0f, msg
.alpha
/255.0f);
712 case Message
.Phase
.Stay
:
713 if (msg
.removeTime
<= curtime
) {
715 msg
.phase
= Message
.Phase
.FadeOut
;
716 goto case; // to fade
718 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
720 case Message
.Phase
.FadeOut
:
721 if ((msg
.alpha
-= 10) <= 0) {
722 if (--messagesUsed
== 0) return;
723 // remove this message
724 foreach (immutable cidx
; 1..messagesUsed
+1) messages
.ptr
[cidx
-1] = messages
.ptr
[cidx
];
727 glColor4f(1.0f, 1.0f, 1.0f, msg
.alpha
/255.0f);
731 smDrawText(10, 10, msg
.text
[0..msg
.textlen
]);
735 // ////////////////////////////////////////////////////////////////////////// //
736 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
738 mixin(Actor
.FieldGetMixin
!("classtype", StrId
)); // fget_classtype
739 mixin(Actor
.FieldGetMixin
!("classname", StrId
)); // fget_classname
740 mixin(Actor
.FieldGetMixin
!("x", int));
741 mixin(Actor
.FieldGetMixin
!("y", int));
742 mixin(Actor
.FieldGetMixin
!("s", int));
743 mixin(Actor
.FieldGetMixin
!("radius", int));
744 mixin(Actor
.FieldGetMixin
!("height", int));
745 mixin(Actor
.FieldGetMixin
!("flags", uint));
746 mixin(Actor
.FieldGetMixin
!("zAnimstate", StrId
));
747 mixin(Actor
.FieldGetMixin
!("zAnimidx", int));
748 mixin(Actor
.FieldGetMixin
!("dir", uint));
749 mixin(Actor
.FieldGetMixin
!("attLightXOfs", int));
750 mixin(Actor
.FieldGetMixin
!("attLightYOfs", int));
751 mixin(Actor
.FieldGetMixin
!("attLightRGBX", uint));
753 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
754 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
755 mixin(Actor
.FieldGetPtrMixin
!("x", int));
756 mixin(Actor
.FieldGetPtrMixin
!("y", int));
757 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
758 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
759 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
760 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
761 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
762 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
763 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
766 // ////////////////////////////////////////////////////////////////////////// //
767 __gshared
int vportX0
, vportY0
, vportX1
, vportY1
;
770 // ////////////////////////////////////////////////////////////////////////// //
771 void renderLightAmbient() (int lightX
, int lightY
, int lightW
, int lightH
, in auto ref SVec4F lcol
) {
772 //conwriteln("!!!000: (", lightX, ",", lightY, ")-(", lightW, ",", lightH, "): r=", cast(uint)(255.0f*lcol.r), "; g=", cast(uint)(255.0f*lcol.g), "; b=", cast(uint)(255.0f*lcol.b), "; a=", cast(uint)(255.0f*lcol.a));
773 if (lightW
< 1 || lightH
< 1) return;
774 int lightX1
= lightX
+lightW
-1;
775 int lightY1
= lightY
+lightH
-1;
776 // clip light to viewport
777 if (lightX
< vportX0
) lightX
= vportX0
;
778 if (lightY
< vportY0
) lightY
= vportY0
;
779 if (lightX1
> vportX1
) lightX1
= vportX1
;
780 if (lightY1
> vportY1
) lightY1
= vportY1
;
781 // is this light visible?
782 //conwriteln("!!!001: (", lightX, ",", lightY, ")-(", lightX1, ",", lightY1, "): r=", cast(uint)(255.0f*lcol.r), "; g=", cast(uint)(255.0f*lcol.g), "; b=", cast(uint)(255.0f*lcol.b), "; a=", cast(uint)(255.0f*lcol.a));
783 if (lightX1
< lightX || lightY1
< lightY || lightX
> vportX1 || lightY
> vportY1 || lightX1
< vportX0 || lightY1
< vportY0
) return;
784 //conwriteln("!!!002: (", lightX, ",", lightY, ")-(", lightX1, ",", lightY1, "): r=", cast(uint)(255.0f*lcol.r), "; g=", cast(uint)(255.0f*lcol.g), "; b=", cast(uint)(255.0f*lcol.b), "; a=", cast(uint)(255.0f*lcol.a));
789 glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
790 //glDisable(GL_BLEND);
791 orthoCamera(map
.width
*TileSize
, map
.height
*TileSize
);
792 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
793 shadAmbient
.exec((Shader shad
) {
794 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
795 //shad["lightPos"] = SVec2F(lightX, lightY);
796 glRectf(lightX
, lightY
, lightX1
, lightY1
);
802 // ////////////////////////////////////////////////////////////////////////// //
803 void renderLight() (int lightX
, int lightY
, in auto ref SVec4F lcol
, int lightRadius
) {
804 if (lightRadius
< 2) return;
805 if (lightRadius
> MaxLightRadius
) lightRadius
= MaxLightRadius
;
806 int lightSize
= lightRadius
*2;
807 // is this light visible?
808 if (lightX
<= -lightRadius || lightY
<= -lightRadius || lightX
-lightRadius
>= map
.width
*TileSize || lightY
-lightRadius
>= map
.height
*TileSize
) return;
810 // out of viewport -- do nothing
811 if (lightX
+lightRadius
< vportX0 || lightY
+lightRadius
< vportY0
) return;
812 if (lightX
-lightRadius
> vportX1 || lightY
-lightRadius
> vportY1
) return;
814 if (lightX
>= 0 && lightY
>= 0 && lightX
< map
.width
*TileSize
&& lightY
< map
.height
*TileSize
&&
815 map
.teximgs
[map
.LightMask
].imageData
.colors
.ptr
[lightY
*(map
.width
*TileSize
)+lightX
].a
> 190) return;
817 // common color for all the following
819 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
821 // build 1d distance map to fboShadowMapId
822 fboDistMap
.ptr
[lightRadius
].exec({
823 // no need to clear it, shader will take care of that
824 shadToPolar
.exec((Shader shad
) {
825 shad
["lightTexSize"] = SVec2F(lightSize
, lightSize
);
826 shad
["lightPos"] = SVec2F(lightX
, lightY
);
827 orthoCamera(lightSize
, 1);
828 // it doesn't matter what we will draw here, so just draw filled rect
829 glRectf(0, 0, lightSize
, 1);
833 // build light texture for blending
835 // no need to clear it, shader will take care of that
837 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
838 //glClear(GL_COLOR_BUFFER_BIT);
839 shadBlur
.exec((Shader shad
) {
840 shad
["lightTexSize"] = SVec2F(lightSize
, MaxLightRadius
*2); // x: size of distmap; y: size of this texture
841 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
842 shad
["lightPos"] = SVec2F(lightX
-lightRadius
, lightY
-lightRadius
);
843 orthoCamera(fboOccluders
.tex
.width
, fboOccluders
.tex
.height
);
844 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
845 bindTexture(fboDistMap
.ptr
[lightRadius
].tex
.tid
);
847 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
848 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize
, 0); // top-right
849 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize
, lightSize
); // bottom-right
850 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize
); // bottom-left
855 // blend light texture
858 //glDisable(GL_BLEND);
859 glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
860 orthoCamera(fboLevelLight
.tex
.width
, fboLevelLight
.tex
.height
);
861 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
862 float occe
= 1.0f*lightSize
/(MaxLightRadius
*2);
863 float occs
= 1.0f-occe
;
864 int x0
= lightX
-lightRadius
+0;
865 int y1
= lightY
-lightRadius
+0;
866 int x1
= lightX
+lightRadius
-1+1;
867 int y0
= lightY
+lightRadius
-1+1;
868 bindTexture(fboOccluders
.tex
.tid
);
871 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
872 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
873 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
874 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
876 glTexCoord2f(0.0f, occs
); glVertex2i(x0
, y0
); // top-left
877 glTexCoord2f(occe
, occs
); glVertex2i(x1
, y0
); // top-right
878 glTexCoord2f(occe
, 1.0f); glVertex2i(x1
, y1
); // bottom-right
879 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0
, y1
); // bottom-left
883 glRectf(x0, y0, x1, y1);
885 // and blend it again, with the shader that will touch only occluders
886 shadBlurOcc
.exec((Shader shad
) {
887 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
888 shad
["lightTexSize"] = SVec2F(lightSize
, fboOccluders
.tex
.height
);
889 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
890 shad
["lightPos"] = SVec2F(lightX
, lightY
);
891 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
892 bindTexture(fboOccluders
.tex
.tid
);
894 glTexCoord2f(0.0f, occs
); glVertex2i(x0
, y0
); // top-left
895 glTexCoord2f(occe
, occs
); glVertex2i(x1
, y0
); // top-right
896 glTexCoord2f(occe
, 1.0f); glVertex2i(x1
, y1
); // bottom-right
897 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0
, y1
); // bottom-left
899 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
905 // ////////////////////////////////////////////////////////////////////////// //
906 __gshared
int testLightX
= vlWidth
/2, testLightY
= vlHeight
/2;
907 __gshared
bool testLightMoved
= false;
908 //__gshared int mapOfsX, mapOfsY;
909 //__gshared bool movement = false;
910 __gshared
float iLiquidTime
= 0.0;
911 //__gshared bool altMove = false;
914 void renderScene (MonoTime curtime
) {
915 //enum BackIntens = 0.05f;
916 enum BackIntens
= 0.0f;
919 float atob
= (curtime
> lastthink ?
cast(float)((curtime
-lastthink
).total
!"msecs")/cast(float)((nextthink
-lastthink
).total
!"msecs") : 1.0f);
920 if (gamePaused || inEditMode
) atob
= 1.0f;
921 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
924 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
925 int curfp = cast(int)((curtime-lastthink).total!"msecs");
926 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
930 int mofsx
, mofsy
; // camera offset, will be set in background layer builder
932 if (mapTilesChanged
!= 0) rebuildMapMegaTextures();
934 // build background layer
936 //glDisable(GL_BLEND);
937 //glClearDepth(1.0f);
938 //glDepthFunc(GL_LESS);
939 //glDepthFunc(GL_NEVER);
940 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
941 glClear(GL_COLOR_BUFFER_BIT
/*|GL_DEPTH_BUFFER_BIT*/);
942 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
943 orthoCamera(map
.width
*TileSize
, map
.height
*TileSize
);
946 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
949 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
950 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
951 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
952 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
954 drawAtXY(map
.skytexgl
.tid
, 0, 0, map
.MapSize
*TileSize
, map
.MapSize
*TileSize
);
956 drawAtXY(map
.texgl
.ptr
[map
.Back
], 0, 0);
957 // draw distorted liquid areas
958 shadLiquidDistort
.exec((Shader shad
) {
959 shad
["iDistortTime"] = iLiquidTime
;
960 drawAtXY(map
.texgl
.ptr
[map
.AllLiquids
], 0, 0);
962 // monsters, items; we'll do linear interpolation here
963 glColor3f(1.0f, 1.0f, 1.0f);
964 //glEnable(GL_DEPTH_TEST);
965 attachedLightCount
= 0;
967 // who cares about memory?!
968 // draw order: players, items, monsters, other
969 static struct DrawInfo
{
973 @disable this (this); // no copies
975 enum { Pixels
, Players
, Items
, Monsters
, Other
}
976 __gshared DrawInfo
[65536][4] drawlists
;
977 __gshared
uint[4] dlpos
;
982 Actor
.forEach((ActorId me
) {
983 //me.fprop_0drawlistpos = 0;
984 if (auto adef
= findActorDef(me
)) {
986 switch (adef
.classtype
.get
) {
987 case "monster": dlnum
= (adef
.classname
.get
!= "Player" ? Monsters
: Players
); break;
988 case "item": dlnum
= Items
; break;
989 default: dlnum
= Other
; break;
991 if (me
.fget_flags
&AF_PIXEL
) dlnum
= Pixels
;
992 int actorX
, actorY
; // current actor position
994 auto ofs
= prevFrameActorOfs
.ptr
[me
.id
&0xffff];
995 if (frameInterpolation
&& ofs
< uint.max
-1 && (me
.fget_flags
&AF_TELEPORT
) == 0 && Actor
.isSameSavedActor(me
.id
, prevFrameActorsData
.ptr
, ofs
)) {
996 import core
.stdc
.math
: roundf
;
997 auto xptr
= prevFrameActorsData
.ptr
+ofs
;
998 int ox
= xptr
.fgetp_x
;
1000 int oy
= xptr
.fgetp_y
;
1002 actorX
= cast(int)(ox
+roundf((nx
-ox
)*atob
));
1003 actorY
= cast(int)(oy
+roundf((ny
-oy
)*atob
));
1004 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
1010 if (me
.id
== cameraChick
.id
) {
1011 camchickdi
.adef
= adef
;
1012 camchickdi
.aid
= me
;
1013 camchickdi
.actorX
= actorX
;
1014 camchickdi
.actorY
= actorY
;
1017 if ((me
.fget_flags
&AF_NODRAW
) == 0) {
1018 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
1019 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
1020 auto dl = drawlists
.ptr
[dlnum
].ptr
+dlpos
.ptr
[dlnum
];
1027 // process attached lights
1028 if ((me
.fget_flags
&AF_NOLIGHT
) == 0) {
1029 uint alr
= me
.fget_attLightRGBX
;
1030 bool isambient
= (me
.fget_classtype
.get
== "light" && me
.fget_classname
.get
== "Ambient");
1031 if ((alr
&0xff) >= 4 ||
(isambient
&& me
.fget_radius
>= 1 && me
.fget_height
>= 1)) {
1032 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
1034 auto li
= attachedLights
.ptr
+attachedLightCount
;
1035 ++attachedLightCount
;
1036 li
.type
= (!isambient ? AttachedLightInfo
.Type
.Point
: AttachedLightInfo
.Type
.Ambient
);
1037 li
.x
= actorX
+me
.fget_attLightXOfs
;
1038 li
.y
= actorY
+me
.fget_attLightYOfs
;
1039 li
.r
= ((alr
>>24)&0xff)/255.0f; // red or intensity
1040 if ((alr
&0x00_ff_ff
_00U) == 0x00_00_01_00U) {
1041 li
.uncolored
= true;
1043 li
.g
= ((alr
>>16)&0xff)/255.0f;
1044 li
.b
= ((alr
>>8)&0xff)/255.0f;
1045 li
.uncolored
= false;
1047 li
.radius
= (alr
&0xff);
1048 if (li
.radius
> MaxLightRadius
) li
.radius
= MaxLightRadius
;
1050 li
.w
= me
.fget_radius
;
1051 li
.h
= me
.fget_height
;
1056 conwriteln("not found actor ", me
.id
, " (", me
.classtype
!string
, ":", me
.classname
!string
, ")");
1061 foreach_reverse (uint dlnum
; 0..drawlists
.length
) {
1062 if (dlnum
== Pixels
) continue;
1063 auto dl = drawlists
.ptr
[dlnum
].ptr
;
1064 if (dlnum
== Players
) dl += dlpos
.ptr
[dlnum
]-1;
1065 foreach (uint idx
; 0..dlpos
.ptr
[dlnum
]) {
1067 if (auto isp
= dl.adef
.animSpr(me
.fget_zAnimstate
, me
.fget_dir
, me
.fget_zAnimidx
)) {
1068 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
1069 isp
.drawAtXY(dl.actorX
, dl.actorY
);
1071 if (dlnum
!= Players
) ++dl; else --dl;
1075 if (dlpos
[Pixels
]) {
1077 bool pointsStarted
= false;
1078 Color lastColor
= Color(0, 0, 0, 0);
1079 auto dl = drawlists
.ptr
[Pixels
].ptr
;
1080 foreach (uint idx
; 0..dlpos
.ptr
[Pixels
]) {
1083 if (s
< 0 || s
> 255) continue; //FIXME
1084 Color clr
= d2dpal
.ptr
[s
&0xff];
1085 if (clr
.a
== 0) continue;
1086 if (clr
!= lastColor
) {
1087 if (pointsStarted
) glEnd();
1088 glColor4f(clr
.r
/255.0f, clr
.g
/255.0f, clr
.b
/255.0f, clr
.a
/255.0f);
1090 pointsStarted
= false;
1092 if (!pointsStarted
) {
1094 pointsStarted
= true;
1096 glVertex2i(dl.actorX
, dl.actorY
);
1099 if (pointsStarted
) {
1101 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1106 if (/*altMove || movement ||*/ scale
== 1 ||
!cameraChick
.valid
) {
1111 vportX1
= map
.width
*TileSize
;
1112 vportY1
= map
.height
*TileSize
;
1114 int tiltHeight
= /*getMapViewHeight()*/(vlHeight
/scale
)/4;
1115 int vy
= cameraChick
.looky
!int;
1116 if (vy
< -tiltHeight
) vy
= -tiltHeight
; else if (vy
> tiltHeight
) vy
= tiltHeight
;
1117 int swdt
= vlWidth
/scale
;
1118 int shgt
= vlHeight
/scale
;
1119 int x
= camchickdi
.actorX
-swdt
/2;
1120 int y
= (camchickdi
.actorY
+vy
)-shgt
/2;
1121 if (x
< 0) x
= 0; else if (x
>= map
.width
*TileSize
-swdt
) x
= map
.width
*TileSize
-swdt
-1;
1122 if (y
< 0) y
= 0; else if (y
>= map
.height
*TileSize
-shgt
) y
= map
.height
*TileSize
-shgt
-1;
1125 vportX0
= mofsx
/scale
;
1126 vportY0
= mofsy
/scale
;
1127 vportX1
= vportX0
+vlWidth
/scale
;
1128 vportY1
= vportY0
+vlHeight
/scale
;
1131 //glDisable(GL_DEPTH_TEST);
1134 // do liquid coloring
1135 drawAtXY(map
.texgl
.ptr
[map
.LiquidMask
], 0, 0);
1136 // foreground -- hide secrets, draw lifts and such
1137 drawAtXY(map
.texgl
.ptr
[map
.Front
], 0, 0);
1145 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
1146 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
1147 if (cameraChick.valid) {
1149 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
1151 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
1153 //glRectf(0, 0, 300, 300);
1154 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1161 glDisable(GL_BLEND
);
1162 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1164 // make smaller occluder texture, so we can trace faster
1165 //assert(fboLMaskSmall.tex.width == map.width);
1166 //assert(fboLMaskSmall.tex.height == map.height);
1167 fboLMaskSmall
.exec({
1168 orthoCamera(map
.width
, map
.height
);
1169 drawAtXY(map
.texgl
.ptr
[map
.LightMask
].tid
, 0, 0, map
.width
, map
.height
, mirrorY
:true);
1172 // clear light layer
1173 fboLevelLight
.exec({
1174 glClearColor(BackIntens
, BackIntens
, BackIntens
, 1.0f);
1175 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
1176 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
1177 glClear(GL_COLOR_BUFFER_BIT
);
1180 // texture 1 is background
1181 glActiveTexture(GL_TEXTURE0
+1);
1182 glBindTexture(GL_TEXTURE_2D
, fboOrigBack
.tex
.tid
);
1183 // texture 2 is occluders
1184 glActiveTexture(GL_TEXTURE0
+2);
1185 glBindTexture(GL_TEXTURE_2D
, map
.texgl
.ptr
[map
.LightMask
].tid
);
1186 // texture 3 is small occluder map
1187 glActiveTexture(GL_TEXTURE0
+3);
1188 glBindTexture(GL_TEXTURE_2D
, fboLMaskSmall
.tex
.tid
);
1189 // done texture assign
1190 glActiveTexture(GL_TEXTURE0
+0);
1195 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1196 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1197 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1198 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1199 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1200 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1201 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1202 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1203 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1204 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1206 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
1208 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1213 foreach (ref li
; attachedLights
[0..attachedLightCount
]) {
1214 if (li
.type
== AttachedLightInfo
.Type
.Ambient
) {
1215 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
1218 renderLightAmbient(li
.x
, li
.y
, li
.w
, li
.h
, SVec4F(0.0f, 0.0f, 0.0f, li
.r
));
1220 renderLightAmbient(li
.x
, li
.y
, li
.w
, li
.h
, SVec4F(li
.r
, li
.g
, li
.b
, 1.0f));
1222 } else if (li
.type
== AttachedLightInfo
.Type
.Point
) {
1225 renderLight(li
.x
, li
.y
, SVec4F(0.0f, 0.0f, 0.0f, li
.r
), li
.radius
);
1227 renderLight(li
.x
, li
.y
, SVec4F(li
.r
, li
.g
, li
.b
, 1.0f), li
.radius
);
1234 if (testLightMoved
) {
1235 testLightX
= testLightX
/scale
+mofsx
/scale
;
1236 testLightY
= testLightY
/scale
+mofsy
/scale
;
1237 testLightMoved
= false;
1239 foreach (immutable _
; 0..1) {
1240 renderLight(testLightX
, testLightY
, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
1244 glActiveTexture(GL_TEXTURE0
+1);
1245 glBindTexture(GL_TEXTURE_2D
, 0);
1246 glActiveTexture(GL_TEXTURE0
+2);
1247 glBindTexture(GL_TEXTURE_2D
, 0);
1248 glActiveTexture(GL_TEXTURE0
+3);
1249 glBindTexture(GL_TEXTURE_2D
, 0);
1250 glActiveTexture(GL_TEXTURE0
+0);
1253 // draw scaled level
1255 shadScanlines.exec((Shader shad) {
1256 shad["scanlines"] = scanlines;
1257 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1258 glClear(GL_COLOR_BUFFER_BIT);
1259 orthoCamera(vlWidth, vlHeight);
1260 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
1261 //glMatrixMode(GL_MODELVIEW);
1263 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
1264 // somehow, FBO objects are mirrored; wtf?!
1265 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1271 fboLevelLight.exec({
1272 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
1277 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
1279 glDisable(GL_BLEND
);
1283 //auto img = smfont.ptr[0x39];
1286 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
1287 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
1294 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
1295 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1296 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1297 glDrawBuffers(1, buffers.ptr);
1302 orthoCamera(vlWidth
, vlHeight
);
1303 auto tex
= (doLighting ? fboLevelLight
.tex
.tid
: fboOrigBack
.tex
.tid
);
1304 drawAtXY(tex
, -mofsx
, -mofsy
, map
.width
*TileSize
*scale
, map
.height
*TileSize
*scale
, mirrorY
:true);
1307 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1308 //orthoCamera(map.width*TileSize, map.height*TileSize);
1310 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1311 hudScripts
.runDraw();
1314 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1315 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1319 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1320 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1321 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1322 editorUpdateImage();
1323 fboEditor
.tex
.setFromImage(editorImg
);
1324 drawAtXY(fboEditor
.tex
, 0, 0);
1325 glDisable(GL_BLEND
);
1328 doMessages(curtime
);
1330 if (rConsoleVisible
) {
1332 orthoCamera(vlWidth
, vlHeight
);
1334 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1335 glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
1336 drawAtXY(fboConsole
.tex
, 0, rConsoleHeight
-vlHeight
, mirrorY
:true);
1341 // ////////////////////////////////////////////////////////////////////////// //
1342 // returns time slept
1343 int sleepAtMaxMsecs (int msecs
) {
1345 import core
.sys
.posix
.signal
: timespec
;
1346 import core
.sys
.posix
.time
: nanosleep
;
1347 timespec ts
= void, tpassed
= void;
1349 ts
.tv_nsec
= msecs
*1000*1000+(500*1000); // milli to nano
1350 nanosleep(&ts
, &tpassed
);
1351 return (ts
.tv_nsec
-tpassed
.tv_nsec
)/(1000*1000);
1358 // ////////////////////////////////////////////////////////////////////////// //
1359 mixin(import("editor.d"));
1362 // ////////////////////////////////////////////////////////////////////////// //
1364 shared int diedie
= 0;
1366 enum D2DFrameTime
= 55; // milliseconds
1367 enum MinFrameTime
= 1000/60; // ~60 FPS
1369 public void renderThread (Tid starterTid
) {
1370 enum BoolOptVarMsgMixin(string varname
) =
1371 "if (msg.toggle) msg.value = !"~varname
~";\n"~
1372 "if ("~varname
~" != msg.value) "~varname
~" = msg.value; else msg.showMessage = false;\n";
1374 send(starterTid
, 42);
1376 MonoTime curtime
= MonoTime
.currTime
;
1378 lastthink
= curtime
; // for interpolator
1379 nextthink
= curtime
+dur
!"msecs"(D2DFrameTime
);
1380 MonoTime nextvframe
= curtime
;
1382 enum MaxFPSFrames
= 16;
1383 float frtimes
= 0.0f;
1386 int hushFrames
= 6; // ignore first `hushFrames` frames overtime
1387 MonoTime prevFrameStartTime
= curtime
;
1389 bool vframeWasLost
= false;
1391 void resetFrameTimers () {
1392 MonoTime curtime
= MonoTime
.currTime
;
1393 lastthink
= curtime
; // for interpolator
1394 nextthink
= curtime
+dur
!"msecs"(D2DFrameTime
);
1395 nextvframe
= curtime
;
1398 void loadNewLevel (string name
) {
1401 conwriteln("ERROR: can't load new levels yet");
1405 if (name
.length
== 0) {
1406 conwriteln("ERROR: can't load empty level!");
1408 conwriteln("loading map '", name
, "'");
1414 string mn
= genNextMapName(0);
1416 nextmapname
= null; // clear "exit" flag
1419 conwriteln("can't skip level");
1421 })("skiplevel", "skip current level");
1424 inEditMode
= !inEditMode
;
1425 if (inEditMode
) sdwindow
.hideCursor(); else sdwindow
.showCursor();
1426 })("ed_toggle", "toggle editor");
1431 sdwindow
.showCursor();
1433 })("ed_exit", "exit from editor");
1435 conRegFunc
!((string mapname
) {
1436 nextmapname
= null; // clear "exit" flag
1437 loadNewLevel(mapname
);
1438 })("map", "load map");
1440 void receiveMessages () {
1442 import core
.time
: Duration
;
1443 //conwriteln("rendering thread: waiting for messages...");
1444 auto got
= receiveTimeout(
1445 Duration
.zero
, // don't wait
1447 addMessage(msg
.text
[0..msg
.textlen
], msg
.pauseMsecs
, msg
.noreplace
);
1449 (TMsgTestLightMove msg
) {
1452 testLightMoved
= true;
1454 (TMsgMouseEvent msg
) { editorMouseEvent(msg
); },
1455 (TMsgKeyEvent msg
) { editorKeyEvent(msg
); },
1456 (TMsgChar msg
) { concliChar(msg
.ch
); },
1458 conwriteln("WARNING: unknown thread message received and ignored");
1463 //conwriteln("rendering thread: no more messages");
1467 if (nextmapname
.length
) {
1468 string mn
= nextmapname
;
1469 nextmapname
= null; // clear "exit" flag
1474 void processConsoleCommands () {
1475 concmdbufLock
.lock();
1476 scope(exit
) concmdbufLock
.unlock();
1480 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1481 bool doThinkFrame () {
1482 if (curtime
>= nextthink
) {
1483 lastthink
= curtime
;
1484 while (nextthink
<= curtime
) nextthink
+= dur
!"msecs"(D2DFrameTime
);
1486 // save snapshot and other data for interpolator
1487 Actor
.saveSnapshot(prevFrameActorsData
[], prevFrameActorOfs
.ptr
);
1488 if (!gamePaused
&& !inEditMode
) {
1495 auto tm
= MonoTime
.currTime
;
1496 int thinkTime
= cast(int)((tm
-curtime
).total
!"msecs");
1497 if (thinkTime
> 9) { import core
.stdc
.stdio
; printf("spent on thinking: %d msecs\n", thinkTime
); }
1505 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1507 version(dont_use_vsync
) {
1509 enum doCheckTime
= true;
1512 __gshared
bool prevLost
= false;
1513 bool doCheckTime
= vframeWasLost
;
1514 if (vframeWasLost
) {
1516 { import core
.stdc
.stdio
; printf("frame was lost!\n"); }
1524 if (curtime
< nextvframe
) return false;
1525 version(dont_use_vsync
) {
1526 if (curtime
> nextvframe
) {
1527 auto overtime
= cast(int)((curtime
-nextvframe
).total
!"msecs");
1528 if (overtime
> 2500) {
1532 { import core
.stdc
.stdio
; printf(" spent whole %d msecs\n", overtime
); }
1538 while (nextvframe
<= curtime
) nextvframe
+= dur
!"msecs"(MinFrameTime
);
1542 scope(exit
) sdwindow
.mtUnlock();
1543 ctset
= sdwindow
.setAsCurrentOpenGlContextNT
;
1545 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1548 iLiquidTime
= cast(float)((curtime
-MonoTime
.zero
).total
!"msecs"%10000000)/18.0f*0.04f;
1549 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1550 processConsoleCommands();
1552 renderScene(curtime
);
1554 //renderLoading(curtime);
1557 scope(exit
) sdwindow
.mtUnlock();
1558 sdwindow
.swapOpenGlBuffers();
1560 sdwindow
.releaseCurrentOpenGlContext();
1561 vframeWasLost
= false;
1563 vframeWasLost
= true;
1564 { import core
.stdc
.stdio
; printf("xframe was lost!\n"); }
1566 curtime
= MonoTime
.currTime
;
1571 if (sdwindow
.closed
) break;
1572 if (atomicLoad(diedie
) > 0) break;
1574 curtime
= MonoTime
.currTime
;
1575 auto fstime
= curtime
;
1577 doThinkFrame(); // this will fix curtime if necessary
1579 if (!vframeWasLost
) {
1581 auto frameTime
= cast(float)(curtime
-prevFrameStartTime
).total
!"msecs"/1000.0f;
1582 prevFrameStartTime
= curtime
;
1583 frtimes
+= frameTime
;
1584 if (++framenum
>= MaxFPSFrames || frtimes
>= 3.0f) {
1585 import std
.string
: format
;
1586 int newFPS
= cast(int)(cast(float)MaxFPSFrames
/frtimes
+0.5);
1587 if (newFPS
!= prevFPS
) {
1588 sdwindow
.title
= "%s / FPS:%s".format("D2D", newFPS
);
1597 curtime
= MonoTime
.currTime
;
1599 // now sleep until next "video" or "think" frame
1600 if (nextthink
> curtime
&& nextvframe
> curtime
) {
1602 immutable nextVideoFrameSleep
= cast(int)((nextvframe
-curtime
).total
!"msecs");
1603 immutable nextThinkFrameSleep
= cast(int)((nextthink
-curtime
).total
!"msecs");
1604 immutable sleepTime
= (nextVideoFrameSleep
< nextThinkFrameSleep ? nextVideoFrameSleep
: nextThinkFrameSleep
);
1605 sleepAtMaxMsecs(sleepTime
);
1606 //curtime = MonoTime.currTime;
1609 } catch (Throwable e
) {
1610 // here, we are dead and fucked (the exact order doesn't matter)
1611 import core
.stdc
.stdlib
: abort
;
1612 import core
.stdc
.stdio
: fprintf
, stderr
;
1613 import core
.memory
: GC
;
1614 GC
.disable(); // yeah
1615 thread_suspendAll(); // stop right here, you criminal scum!
1616 auto s
= e
.toString();
1617 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
1618 abort(); // die, you bitch!
1620 import core.stdc.stdio;
1621 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1622 import std.stdio : stderr;
1625 if (sdwindow.closed) break;
1626 if (atomicLoad(diedie) > 0) break;
1627 sleepAtMaxMsecs(100);
1631 atomicStore(diedie
, 2);
1635 // ////////////////////////////////////////////////////////////////////////// //
1636 __gshared Tid renderTid
;
1637 shared bool renderThreadStarted
= false;
1640 public void startRenderThread () {
1641 if (!cas(&renderThreadStarted
, false, true)) {
1642 assert(0, "render thread already started!");
1644 renderTid
= spawn(&renderThread
, thisTid
);
1645 setMaxMailboxSize(renderTid
, 1024, OnCrowding
.throwException
); //FIXME
1646 // wait for "i'm ready" signal
1649 if (ok
!= 42) assert(0, "wtf?!");
1652 conwriteln("rendering thread started");
1656 // ////////////////////////////////////////////////////////////////////////// //
1657 public void closeWindow () {
1658 if (atomicLoad(diedie
) != 2) {
1659 atomicStore(diedie
, 1);
1660 while (atomicLoad(diedie
) != 2) {}
1662 if (!sdwindow
.closed
) {
1669 // ////////////////////////////////////////////////////////////////////////// //
1673 // ////////////////////////////////////////////////////////////////////////// //
1674 struct TMsgMouseEvent
{
1675 MouseEventType type
;
1678 MouseButton button
; /// See $(LREF MouseButton)
1679 int modifierState
; /// See $(LREF ModifierState)
1682 public void postMouseEvent() (in auto ref MouseEvent evt
) {
1683 if (!atomicLoad(renderThreadStarted
)) return;
1685 msg
.type
= evt
.type
;
1690 msg
.button
= evt
.button
;
1691 msg
.modifierState
= evt
.modifierState
;
1692 send(renderTid
, msg
);
1696 // ////////////////////////////////////////////////////////////////////////// //
1697 struct TMsgKeyEvent
{
1705 public void postKeyEvent() (in auto ref KeyEvent evt
) {
1706 if (!atomicLoad(renderThreadStarted
)) return;
1709 msg
.pressed
= evt
.pressed
;
1710 msg
.character
= evt
.character
;
1711 msg
.modifierState
= evt
.modifierState
;
1712 send(renderTid
, msg
);
1716 // ////////////////////////////////////////////////////////////////////////// //
1717 struct TMsgTestLightMove
{
1721 public void postTestLightMove (int x
, int y
) {
1722 if (!atomicLoad(renderThreadStarted
)) return;
1723 auto msg
= TMsgTestLightMove(x
, y
);
1724 send(renderTid
, msg
);
1728 // ////////////////////////////////////////////////////////////////////////// //
1729 struct TMsgMessage
{
1736 public void postAddMessage (const(char)[] msgtext
, int pauseMsecs
=3000, bool noreplace
=false) {
1737 if (!atomicLoad(renderThreadStarted
)) return;
1738 if (msgtext
.length
> TMsgMessage
.text
.length
) msgtext
= msgtext
[0..TMsgMessage
.text
.length
];
1740 msg
.textlen
= cast(uint)msgtext
.length
;
1741 if (msg
.textlen
) msg
.text
[0..msg
.textlen
] = msgtext
[0..msg
.textlen
];
1742 msg
.pauseMsecs
= pauseMsecs
;
1743 msg
.noreplace
= noreplace
;
1744 send(renderTid
, msg
);
1748 // ////////////////////////////////////////////////////////////////////////// //
1753 public void postChar (char ch
) {
1754 if (!atomicLoad(renderThreadStarted
)) return;
1757 send(renderTid
, msg
);
1761 // ////////////////////////////////////////////////////////////////////////// //
1762 public void concmd (const(char)[] cmd
) {
1763 //if (!atomicLoad(renderThreadStarted)) return;
1764 concmdbufLock
.lock();
1765 scope(exit
) concmdbufLock
.unlock();