1 /**
2  * Retrograde Engine
3  *
4  * Authors:
5  *  Mike Bierlee, m.bierlee@lostmoment.com
6  * Copyright: 2014-2021 Mike Bierlee
7  * License:
8  *  This software is licensed under the terms of the MIT license.
9  *  The full terms of the license can be found in the LICENSE.txt file.
10  */
11 
12 module retrograde.graphics.twodee.sdl2.rendering;
13 
14 version(Have_derelict_sdl2) {
15 
16 import retrograde.entity;
17 import retrograde.graphics.core;
18 import retrograde.math;
19 import retrograde.file;
20 import retrograde.sdl2.window;
21 import retrograde.derelict;
22 import retrograde.messaging;
23 import retrograde.stringid;
24 
25 import poodinis;
26 
27 import std.string;
28 import std.range;
29 import std.math;
30 import std.algorithm.sorting;
31 import std.algorithm.iteration;
32 
33 import derelict.sdl2.sdl;
34 import derelict.sdl2.image;
35 
36 class Sdl2RenderSystemInitException : Exception {
37     this(string reason) {
38         super("Could not initialize SDL2 render system: " ~ reason);
39     }
40 }
41 
42 public class Sdl2RenderSystem : EntityProcessor {
43     private SDL_Renderer* renderer;
44     private SDL_Window* window;
45 
46     public ColorRgba clearColor = ColorRgba(0, 0, 0, 255);
47 
48     @Autowire
49     @OptionalDependency
50     private SubRenderer[] subRenderers;
51 
52     @Autowire
53     private Sdl2WindowCreator windowCreator;
54 
55     public override void initialize() {
56         if (!DerelictLibrary.loadedAndInitialized()) {
57             throw new Sdl2RenderSystemInitException("SDL2 is not initialized. See retrograde.derelict module and load and initialize SDL2 before the renderer.");
58         }
59 
60         window = windowCreator.createWindow();
61         if (!window) {
62             throw new Sdl2RenderSystemInitException("Could not create window");
63         }
64 
65         renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
66         if (!renderer) {
67             throw new Sdl2RenderSystemInitException("Could not create renderer");
68         }
69 
70         initializeSubRenderers();
71     }
72 
73     public void initializeSubRenderers() {
74         if (subRenderers.length == 0) {
75             throw new Sdl2RenderSystemInitException("No SDL2 subrenderers are registered in the dependency container.");
76         }
77 
78         foreach (subRenderer; subRenderers) {
79             subRenderer.renderer = renderer;
80         }
81     }
82 
83     public override void cleanup() {
84         SDL_DestroyWindow(window);
85         SDL_DestroyRenderer(renderer);
86     }
87 
88     public override bool acceptsEntity(Entity entity) {
89         foreach(subRenderer ; subRenderers) {
90             if (subRenderer.acceptsEntity(entity)) {
91                 return true;
92             }
93         }
94         return false;
95     }
96 
97     protected override void processAcceptedEntity(Entity entity) {
98         foreach(subRenderer ; subRenderers) {
99             if (subRenderer.acceptsEntity(entity)) {
100                 subRenderer.addEntity(entity);
101             }
102         }
103     }
104 
105     protected override void processRemovedEntity(Entity entity) {
106         foreach(subRenderer ; subRenderers) {
107             if (subRenderer.hasEntity(entity)) {
108                 subRenderer.removeEntity(entity.id);
109             }
110         }
111     }
112 
113     public override void update() {
114         foreach(subRenderer ; subRenderers) {
115             subRenderer.update();
116         }
117     }
118 
119     public override void draw() {
120         if (renderer) {
121             SDL_SetRenderDrawColor(renderer, clearColor.r, clearColor.g, clearColor.b, clearColor.a);
122             SDL_RenderClear(renderer);
123             foreach(subRenderer ; subRenderers) {
124                 subRenderer.draw();
125             }
126             SDL_RenderPresent(renderer);
127         }
128     }
129 
130     public SDL_Texture* createTextureFromSurface(SDL_Surface* surface) {
131         return SDL_CreateTextureFromSurface(renderer, surface);
132     }
133 }
134 
135 public class SubRenderer : EntityProcessor {
136     public SDL_Renderer* renderer;
137 }
138 
139 public class SpriteSubRenderer : SubRenderer {
140     private bool reorderEntities = false;
141     private Entity[][int] orderedEntities;
142     private SortedRange!(int[]) renderOrderKeys;
143 
144     public override bool acceptsEntity(Entity entity) {
145         return (entity.hasComponent!RenderableSpriteComponent
146             || entity.hasComponent!RenderableTextComponent)
147             && entity.hasComponent!TextureComponent;
148     }
149 
150     public override void update() {
151         if (reorderEntities) {
152             orderedEntities.destroy();
153             _entities.getAll().each!(e => addToOrderedMap(e));
154             createRenderOrderKeys();
155             reorderEntities = false;
156         }
157     }
158 
159     private void createRenderOrderKeys() {
160         renderOrderKeys = orderedEntities.keys.sort();
161     }
162 
163     private void addToOrderedMap(Entity entity) {
164         int renderOrder = getEntityDrawOrder(entity);
165         orderedEntities[renderOrder] ~= entity;
166     }
167 
168     private int getEntityDrawOrder(Entity entity) {
169         return entity.getFromComponent!RenderOrderComponent(component => component.order, 0);
170     }
171 
172     private void removeFromOrderedMap(uint entityId) {
173         auto entity = _entities[entityId];
174         auto renderOrder = getEntityDrawOrder(entity);
175         orderedEntities[renderOrder] = orderedEntities[renderOrder].filter!(ent => ent.id != entityId)().array();
176     }
177 
178     public override void addEntity(Entity entity) {
179         if (acceptsEntity(entity)) {
180             addToOrderedMap(entity);
181             createRenderOrderKeys();
182             super.addEntity(entity);
183         }
184     }
185 
186     public override void removeEntity(uint entityId) {
187         removeFromOrderedMap(entityId);
188         super.removeEntity(entityId);
189     }
190 
191     public override void draw() {
192         foreach(orderIndex; renderOrderKeys) {
193             foreach(entity; orderedEntities[orderIndex]) {
194                 auto isHidden = entity.getFromComponent!HideableComponent(component => component.isHidden, false);
195                 if (isHidden) {
196                     continue;
197                 }
198 
199                 drawEntitySprite(entity);
200             }
201         }
202     }
203 
204     private void drawEntitySprite(Entity entity) {
205         auto texture = cast(Sdl2Texture) entity.getFromComponent!TextureComponent(c => c.texture);
206         if (!texture) return;
207         auto sdlTexture = texture.getSdlTexture();
208         if (!sdlTexture) return;
209 
210         RectangleU currentSpriteSize = entity.getFromComponent!SpriteSheetComponent((component) {
211             auto currentFrame = entity.getFromComponent!SpriteAnimationComponent(component => component.currentFrame, 1L);
212             return cast(RectangleU) component.getNthSprite(currentFrame);
213         }, texture.getTextureSize());
214 
215         Vector2D position = entity.getFromComponent!Position2DComponent(component => component.position, Vector2D(0));
216 
217         entity.maybeWithComponent!DrawingOffset2DComponent((component) {
218             position = position + component.offset;
219         });
220 
221         int renderFlip = SDL_FLIP_NONE;
222         if (entity.hasComponent!VerticalSpriteFlipComponent) {
223             renderFlip = renderFlip | SDL_FLIP_VERTICAL;
224         }
225 
226         if (entity.hasComponent!HorizontalSpriteFlipComponent) {
227             renderFlip = renderFlip | SDL_FLIP_HORIZONTAL;
228         }
229 
230         double rotation = radiansToDegrees(entity.getFromComponent!OrientationR2Component(component => component.angle, 0));
231 
232         Vector2D offset = entity.getFromComponent!DrawingOffset2DComponent(component => component.offset, Vector2D(0)) * -1;
233         SDL_Point center = SDL_Point(cast(int) offset.x, cast(int) offset.y);
234 
235         auto textureColor = entity.getFromComponent!TextureColorComponent(c => c.color, ColorRgb(255, 255, 255));
236         SDL_SetTextureColorMod(sdlTexture, textureColor.r, textureColor.g, textureColor.b);
237 
238         SDL_Rect source;
239         source.x = currentSpriteSize.x;
240         source.y = currentSpriteSize.y;
241         source.w = currentSpriteSize.width;
242         source.h = currentSpriteSize.height;
243         SDL_Rect destination;
244         destination.x = cast(int) round(position.x);
245         destination.y = cast(int) round(position.y);
246         destination.w = currentSpriteSize.width;
247         destination.h = currentSpriteSize.height;
248 
249         SDL_RenderCopyEx(renderer, sdlTexture, &source, &destination, rotation, &center, renderFlip);
250     }
251 }
252 
253 class Sdl2Texture : Texture {
254     private SDL_Texture* texture;
255     private SDL_Rect textureSize;
256     private string name;
257 
258     public this() {}
259 
260     public this(SDL_Texture* texture, string name) {
261         this.texture = texture;
262         this.name = name;
263         setTextureSize(texture);
264     }
265 
266     public override RectangleU getTextureSize() {
267         return RectangleU(textureSize.x, textureSize.y, textureSize.w, textureSize.h);
268     }
269 
270     public override string getName() {
271         return name;
272     }
273 
274     public SDL_Rect getSdlTextureSize() {
275         return textureSize;
276     }
277 
278     public SDL_Texture* getSdlTexture() {
279         return texture;
280     }
281 
282     public void setSdlTexture(SDL_Texture* texture) {
283         this.texture = texture;
284         setTextureSize(texture);
285     }
286 
287     private void setTextureSize(SDL_Texture* texture) {
288         textureSize.x = 0;
289         textureSize.y = 0;
290         SDL_QueryTexture(texture, null, null, &textureSize.w, &textureSize.h);
291     }
292 }
293 
294 class Sdl2TextureComponentFactory : TextureComponentFactory {
295 
296     @Autowire
297     private Sdl2RenderSystem renderer;
298 
299     private TextureCache textures = new TextureCache();
300 
301     public TextureComponent loadTexture(File textureFile) {
302         auto texture = textures.getOrAdd(textureFile.fileName, {
303             SDL_Surface* textureSurface = IMG_Load(textureFile.fileName.toStringz());
304             SDL_Texture* texture = renderer.createTextureFromSurface(textureSurface);
305             SDL_FreeSurface(textureSurface);
306             return new Sdl2Texture(texture, textureFile.fileName);
307         });
308 
309         return new TextureComponent(texture);
310     }
311 
312     public TextureComponent createNullComponent() {
313         return new TextureComponent(null);
314     }
315 }
316 
317 }