1 module sbylib.graphics.util.functions;
2 
3 import sbylib.math;
4 
5 // public import sbylib.math;
6 // public import sbylib.wrapper.freeimage : Image;
7 // 
8 // // import sbylib.graphics.canvas : Canvas;
9 // import sbylib.wrapper.gl : Texture;
10 // 
11 // import std.traits : isAggregateType, FieldTypeTuple, FieldNameTuple;
12 // 
13 mixin template DefineCachedValue(Type, string attribute, string name, string expression, string[] keys) 
14     if (keys.length > 0)
15 {
16 
17     import std : format, replace;
18 
19     static foreach (key; keys) {
20         mixin(format!"%s _%sSavedValue;"(VariableType!(typeof(mixin(key))).stringof, key.replace(".", "_")));
21     }
22     mixin(format!"%s _%sSavedValue;"(Type.stringof, name));
23 
24     mixin(format!q{
25         %s %s %s() {
26             bool _shouldUpdate = false;
27             static foreach (key; keys) {
28                 if (mixin(format!"_%sSavedValue != %s"(key.replace(".", "_"), key))) {
29                     _shouldUpdate = true;
30                     mixin(format!"_%sSavedValue = %s;"(key.replace(".", "_"), key));
31                 }
32             }
33             if (_shouldUpdate)
34                 _%sSavedValue = %s;
35             return _%sSavedValue;
36         }
37     }(attribute, Type.stringof, name, "%s", "%s", "%s", "%s", name, expression, name));
38 
39     template VariableType(Type) {
40         import std : isSomeFunction, ReturnType;
41     
42         static if (isSomeFunction!(Type)) {
43             alias VariableType = ReturnType!(Type);
44         } else {
45             alias VariableType = Type;
46         }
47     }
48 } 
49 
50 mixin template ImplPos() {
51     import sbylib.math : vec3;
52     // import sbylib.graphics.util : Pixel, pixel, ImplPixelX, ImplPixelY;
53     import sbylib.wrapper.glfw : Window;
54 
55     vec3 pos = vec3(0);
56 
57     /*
58 
59     Pixel[2] pixelPos() {
60         import sbylib.wrapper.gl : GlUtils;
61 
62         const viewport = GlUtils().getViewport();
63         return [
64             pixel(cast(int)(pos.x * viewport[2]) / 2),
65             pixel(cast(int)(pos.y * viewport[3]) / 2),
66         ];
67     }
68 
69     Pixel[2] pixelPos(Pixel[2] pixel) {
70         import sbylib.wrapper.gl : GlUtils;
71 
72         const viewport = GlUtils().getViewport();
73 
74         this.pos.xy = 2 * vec2(pixel) / vec2(viewport[2..$]);
75 
76         return pixel;
77     }
78 
79     mixin ImplPixelX;
80     mixin ImplPixelY;
81     */
82 }
83 
84 /*
85 mixin template ImplPixelX() {
86     Pixel pixelX() {
87         return this.pixelPos[0];
88     }
89 
90     Pixel pixelX(Pixel pixel) {
91         this.pixelPos = [pixel, this.pixelPos[1]];
92         return pixel;
93     }
94 }
95 
96 mixin template ImplPixelY() {
97     Pixel pixelY() {
98         return this.pixelPos[1];
99     }
100 
101     Pixel pixelY(Pixel pixel) {
102         this.pixelPos = [this.pixelPos[0], pixel];
103         return pixel;
104     }
105 }
106 */
107 
108 mixin template ImplRot() {
109     import sbylib.math : mat3, Angle;
110 
111     mat3 rot = mat3.identity;
112 
113     auto rotate(Angle angle) {
114         this.rot *= mat3.axisAngle(vec3(0,0,1), angle);
115         return this;
116     }
117 }
118  
119  mixin template ImplScale() {
120      import sbylib.math : vec3;
121      // import sbylib.graphics.util : Pixel, ImplPixelX, ImplPixelY;
122  
123      vec3 scale = vec3(1);
124  
125      /*
126      Pixel[2] pixelSize(Pixel[2] pixel) {
127          import sbylib.wrapper.gl : GlUtils;
128  
129          this.scale.xy = 2 * vec2(pixel) / vec2(GlUtils().getViewport()[2..$]);
130  
131          return pixel;
132      }
133  
134      Pixel[2] pixelSize() {
135          import sbylib.wrapper.gl : GlUtils;
136  
137          const viewport = GlUtils().getViewport();
138          return [
139              pixel(cast(int)(this.scale.x * viewport[2]) / 2),
140              pixel(cast(int)(this.scale.y * viewport[3]) / 2),
141          ];
142      }
143  
144      mixin ImplPixelWidth;
145      mixin ImplPixelHeight;
146      */
147  }
148  
149 // mixin template ImplPixelWidth() {
150 //     Pixel pixelWidth() {
151 //         return this.pixelSize[0];
152 //     }
153 // 
154 //     Pixel pixelWidth(Pixel pixel) {
155 //         this.pixelSize = [pixel, this.pixelSize[1]];
156 //         return pixel;
157 //     }
158 // }
159 // 
160 // mixin template ImplPixelHeight() {
161 //     Pixel pixelHeight() {
162 //         return this.pixelSize[1];
163 //     }
164 // 
165 //     Pixel pixelHeight(Pixel pixel) {
166 //         this.pixelSize = [this.pixelSize[0], pixel];
167 //         return pixel;
168 //     }
169 // }
170 // 
171 mixin template ImplWorldMatrix(string name = "worldMatrix") {
172     import sbylib.graphics.util : DefineCachedValue;
173     import std : hasMember, format;
174 
175     private enum hasPos = hasMember!(typeof(this), "pos");
176     private enum hasRot = hasMember!(typeof(this), "rot");
177     private enum hasScale = hasMember!(typeof(this), "scale");
178     private enum expression = format!q{mat4.makeTRS(%s, %s, %s)}
179         (hasPos   ? "pos"   : "vec3(0)",
180          hasRot   ? "rot"   : "mat3.identity",
181          hasScale ? "scale" : "vec3(1)");
182     enum key = 
183           (hasPos   ? ["pos"]   : [])
184         ~ (hasRot   ? ["rot"]   : [])
185         ~ (hasScale ? ["scale"] : []);
186 
187     static if (key.length > 0) {
188         mixin DefineCachedValue!(mat4, "", name, expression, key);
189     } else {
190         mixin(format!q{mat4 %s() { return mat4.identity; }}(name));
191     }
192 }
193 // 
194 // mixin template ImplParentalWorldMatrix(alias parent) {
195 //     mixin ImplWorldMatrix!("_worldMatrix") W;
196 // 
197 //     private enum expression = "parent.worldMatrix * W._worldMatrix";
198 //     private enum key = [parent.stringof] ~ "parent.worldMatrix" ~ W.key;
199 // 
200 //     mixin DefineCachedValue!(mat4, "@uniform", "worldMatrix", expression, key);
201 // }
202 // 
203 mixin template ImplViewMatrix() {
204     import sbylib.graphics.util : DefineCachedValue;
205     import std : hasMember, format;
206 
207     private enum hasPos = hasMember!(typeof(this), "pos");
208     private enum hasRot = hasMember!(typeof(this), "rot");
209     private enum hasScale = hasMember!(typeof(this), "scale");
210     private enum expression = format!q{mat4.makeInvertTRS(%s, %s, %s)}
211         (hasPos   ? "pos"   : "vec3(0)",
212          hasRot   ? "rot"   : "mat3.identity",
213          hasScale ? "scale" : "vec3(1)");
214     private enum key = 
215           (hasPos   ? ["pos"]   : [])
216         ~ (hasRot   ? ["rot"]   : [])
217         ~ (hasScale ? ["scale"] : []);
218 
219     static if (key.length > 0) {
220         mixin DefineCachedValue!(mat4, "", "viewMatrix", expression, key);
221     } else {
222         mat4 getViewMatrix() { return mat4.identity; }
223     }
224 }
225 // 
226 // /*
227 // void render(Canvas dstCanvas, Texture tex, ivec2 p, ivec2 s) {
228 //     import sbylib.graphics.canvas : CanvasBuilder;
229 //     import sbylib.wrapper.gl : TextureFilter, BufferBit;
230 // 
231 //     static Canvas canvas;
232 //     if (canvas is null) {
233 //         with (CanvasBuilder()) {
234 //             color.enable = true;
235 //             canvas = build();
236 //         }
237 //     }
238 //     canvas.color.attach(tex);
239 // 
240 //     dstCanvas.render(canvas,
241 //         0, 0, tex.width, tex.height,
242 //         p.x, p.y, p.x + s.x, p.y + s.y,
243 //         TextureFilter.Linear, BufferBit.Color);
244 // }
245 // */
246 // 
247 void lookAt(T)(T entity, vec3 target, vec3 up = vec3(0,1,0)) {
248     entity.rot = mat3.lookAt(normalize(entity.pos - target), up);
249 }
250 // 
251 // Texture convert(Image image) {
252 //     import sbylib.wrapper.gl : TextureBuilder;
253 //     with (TextureBuilder()) {
254 //         width = image.width;
255 //         height = image.height;
256 //         return build(image.dataArray());
257 //     }
258 // }
259 
260 mixin template ImplResourceStack() {
261     private void delegate()[] deconstructionStack;
262 
263     private void destroyStack() {
264         while (deconstructionStack.empty is false) {
265             deconstructionStack.back()();
266             deconstructionStack.popBack();
267         }
268     }
269 
270     public T pushResource(T)(T t) {
271         pushReleaseCallback({ t.destroy(); });
272         return t;
273     }
274 
275     public void pushReleaseCallback(void delegate() cb) {
276         deconstructionStack ~= cb;
277     }
278 }
279 
280 mixin template Sealable(Type, string name) {
281     import std : replace;
282 
283     mixin(q{
284         private ${type} _${name};
285         private bool _${name}SealFlag;
286 
287         ref ${type} ${name}() {
288             import std : enforce;
289             enforce(_${name}SealFlag is false, "${name} is already sealed.");
290             return _${name};
291         }
292 
293         void seal(alias target : ${name})() {
294             _${name}SealFlag = true;
295         }
296     }
297     .replace("${type}", Type.stringof)
298     .replace("${name}", name));
299 }
300 
301 unittest {
302     import std : assertThrown;
303 
304     class A {
305         mixin Sealable!(int, "x");
306     }
307 
308     auto a = new A;
309     a.x = 3;
310     assert(a.x == 3);
311     a.seal!(a.x);
312     assertThrown(a.x = 4);
313 }