1 /*
2 Copyright (c) 2017-2018 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.graphics.material;
29 
30 import std.math;
31 import std.algorithm;
32 
33 import dlib.core.memory;
34 import dlib.math.vector;
35 import dlib.image.color;
36 import dlib.image.image;
37 import dlib.image.unmanaged;
38 import dlib.container.dict;
39 
40 import dagon.core.libs;
41 import dagon.core.ownership;
42 import dagon.graphics.texture;
43 import dagon.graphics.rc;
44 import dagon.graphics.shader;
45 
46 enum
47 {
48     CBlack = Color4f(0.0f, 0.0f, 0.0f, 1.0f),
49     CWhite = Color4f(1.0f, 1.0f, 1.0f, 1.0f),
50     CRed = Color4f(1.0f, 0.0f, 0.0f, 1.0f),
51     COrange = Color4f(1.0f, 0.5f, 0.0f, 1.0f),
52     CYellow = Color4f(1.0f, 1.0f, 0.0f, 1.0f),
53     CGreen = Color4f(0.0f, 1.0f, 0.0f, 1.0f),
54     CCyan = Color4f(0.0f, 1.0f, 1.0f, 1.0f),
55     CBlue = Color4f(0.0f, 0.0f, 1.0f, 1.0f),
56     CPurple = Color4f(0.5f, 0.0f, 1.0f, 1.0f),
57     CMagenta = Color4f(1.0f, 0.0f, 1.0f, 1.0f)
58 }
59 
60 enum int None = 0;
61 
62 enum int ShadowFilterNone = 0;
63 enum int ShadowFilterPCF = 1;
64 
65 enum int ParallaxNone = 0;
66 enum int ParallaxSimple = 1;
67 enum int ParallaxOcclusionMapping = 2;
68 
69 enum int Opaque = 0;
70 enum int Transparent = 1;
71 enum int Additive = 2;
72 
73 enum MaterialInputType
74 {
75     Undefined,
76     Bool,
77     Integer,
78     Float,
79     Vec2,
80     Vec3,
81     Vec4
82 }
83 
84 struct MaterialInput
85 {
86     MaterialInputType type;
87     union
88     {
89         bool asBool;
90         int asInteger;
91         float asFloat;
92         Vector2f asVector2f;
93         Vector3f asVector3f;
94         Vector4f asVector4f;
95     }
96     Texture texture;
97 
98     float getNumericValue()
99     {
100         float res;
101         if (type == MaterialInputType.Bool ||
102             type == MaterialInputType.Integer)
103         {
104             res = asInteger;
105         }
106         else if (type == MaterialInputType.Float)
107         {
108             res = asFloat;
109         }
110         return res;
111     }
112 
113     Color4f sample(float u, float v)
114     {
115         if (texture !is null)
116             return texture.sample(u, v);
117         else if (type == MaterialInputType.Vec4)
118             return Color4f(asVector4f);
119         else if (type == MaterialInputType.Vec3)
120             return Color4f(asVector3f.x, asVector3f.y, asVector3f.z, 1.0f);
121         else if (type == MaterialInputType.Vec2)
122             return Color4f(asVector2f.x, asVector2f.y, 1.0f, 1.0f);
123         else if (type == MaterialInputType.Float)
124             return Color4f(asFloat, 1.0f, 1.0f, 1.0f);
125         else if (type == MaterialInputType.Bool ||
126                  type == MaterialInputType.Integer)
127             return Color4f(cast(float)asInteger, 1.0f, 1.0f, 1.0f);
128         else
129             return Color4f(0.0f, 0.0f, 0.0f, 0.0f);
130     }
131 }
132 
133 MaterialInput materialInput(float v)
134 {
135     MaterialInput mi;
136     mi.asFloat = v;
137     mi.type = MaterialInputType.Float;
138     return mi;
139 }
140 
141 class Material: Owner
142 {
143     Dict!(MaterialInput, string) inputs;
144     Shader shader;
145     bool customShader = false;
146 
147     this(Shader shader, Owner o)
148     {
149         super(o);
150 
151         inputs = New!(Dict!(MaterialInput, string));
152         setStandardInputs();
153         this.shader = shader;
154     }
155 
156     ~this()
157     {
158         Delete(inputs);
159     }
160 
161     void setStandardInputs()
162     {
163         setInput("diffuse", Color4f(0.8f, 0.8f, 0.8f, 1.0f));
164         setInput("specular", Color4f(1.0f, 1.0f, 1.0f, 1.0f));
165         setInput("shadeless", false);
166         setInput("emission", Color4f(0.0f, 0.0f, 0.0f, 1.0f));
167         setInput("energy", 1.0f);
168         setInput("transparency", 1.0f);
169         setInput("roughness", 0.5f);
170         setInput("metallic", 0.0f);
171         setInput("normal", Vector3f(0.0f, 0.0f, 1.0f));
172         setInput("height", 0.0f);
173         setInput("parallax", ParallaxNone);
174         setInput("parallaxScale", 0.03f);
175         setInput("parallaxBias", -0.01f);
176         setInput("shadowsEnabled", true);
177         setInput("shadowFilter", ShadowFilterPCF);
178         setInput("fogEnabled", true);
179         setInput("blending", Opaque);
180         setInput("culling", true);
181         setInput("colorWrite", true);
182         setInput("depthWrite", true);
183         setInput("particleColor", Color4f(1.0f, 1.0f, 1.0f, 1.0f));
184         setInput("particleSphericalNormal", false);
185     }
186 
187     final auto opDispatch(string name)() @property
188     {
189         return (name in inputs);
190     }
191 
192     final void opDispatch(string name, T)(T value) @property
193     {
194         setInput(name, value);
195     }
196 
197     final MaterialInput* setInput(T)(string name, T value)
198     {
199         MaterialInput input;
200         static if (is(T == bool))
201         {
202             input.type = MaterialInputType.Bool;
203             input.asBool = value;
204         }
205         else static if (is(T == int))
206         {
207             input.type = MaterialInputType.Integer;
208             input.asInteger = value;
209         }
210         else static if (is(T == float) || is(T == double))
211         {
212             input.type = MaterialInputType.Float;
213             input.asFloat = value;
214         }
215         else static if (is(T == Vector2f))
216         {
217             input.type = MaterialInputType.Vec2;
218             input.asVector2f = value;
219         }
220         else static if (is(T == Vector3f))
221         {
222             input.type = MaterialInputType.Vec3;
223             input.asVector3f = value;
224         }
225         else static if (is(T == Vector4f))
226         {
227             input.type = MaterialInputType.Vec4;
228             input.asVector4f = value;
229         }
230         else static if (is(T == Color4f))
231         {
232             input.type = MaterialInputType.Vec4;
233             input.asVector4f = value;
234         }
235         else static if (is(T == Texture))
236         {
237             input.texture = value;
238             if (value.format == GL_RED)
239                 input.type = MaterialInputType.Float;
240             else if (value.format == GL_RG)
241                 input.type = MaterialInputType.Vec2;
242             else if (value.format == GL_RGB)
243                 input.type = MaterialInputType.Vec3;
244             else if (value.format == GL_RGBA)
245                 input.type = MaterialInputType.Vec4;
246         }
247         else
248         {
249             input.type = MaterialInputType.Undefined;
250         }
251 
252         inputs[name] = input;
253         return (name in inputs);
254     }
255 
256     final bool boolProp(string prop)
257     {
258         auto p = prop in inputs;
259         bool res = false;
260         if (p.type == MaterialInputType.Bool ||
261             p.type == MaterialInputType.Integer)
262         {
263             res = p.asBool;
264         }
265         return res;
266     }
267 
268     final int intProp(string prop)
269     {
270         auto p = prop in inputs;
271         int res = 0;
272         if (p.type == MaterialInputType.Bool ||
273             p.type == MaterialInputType.Integer)
274         {
275             res = p.asInteger;
276         }
277         else if (p.type == MaterialInputType.Float)
278         {
279             res = cast(int)p.asFloat;
280         }
281         return res;
282     }
283 
284     final Texture makeTexture(Color4f rgb, Texture alpha)
285     {
286         SuperImage rgbaImg = New!UnmanagedImageRGBA8(alpha.width, alpha.height);
287 
288         foreach(y; 0..alpha.height)
289         foreach(x; 0..alpha.width)
290         {
291             Color4f col = rgb;
292             col.a = alpha.image[x, y].r;
293             rgbaImg[x, y] = col;
294         }
295 
296         auto tex = New!Texture(rgbaImg, this);
297         return tex;
298     }
299 
300     final Texture makeTexture(Texture rgb, float alpha)
301     {
302         SuperImage rgbaImg = New!UnmanagedImageRGBA8(rgb.width, rgb.height);
303 
304         foreach(y; 0..rgb.height)
305         foreach(x; 0..rgb.width)
306         {
307             Color4f col = rgb.image[x, y];
308             col.a = alpha;
309             rgbaImg[x, y] = col;
310         }
311 
312         auto tex = New!Texture(rgbaImg, this);
313         return tex;
314     }
315 
316     final Texture makeTexture(Texture rgb, Texture alpha)
317     {
318         uint width = max(rgb.width, alpha.width);
319         uint height = max(rgb.height, alpha.height);
320 
321         SuperImage rgbaImg = New!UnmanagedImageRGBA8(width, height);
322 
323         foreach(y; 0..rgbaImg.height)
324         foreach(x; 0..rgbaImg.width)
325         {
326             float u = cast(float)x / cast(float)width;
327             float v = cast(float)y / cast(float)height;
328 
329             Color4f col = rgb.sample(u, v);
330             col.a = alpha.sample(u, v).r;
331 
332             rgbaImg[x, y] = col;
333         }
334 
335         auto tex = New!Texture(rgbaImg, this);
336         return tex;
337     }
338 
339     final Texture makeTexture(MaterialInput r, MaterialInput g, MaterialInput b, MaterialInput a)
340     {
341         uint width = 8;
342         uint height = 8;
343 
344         if (r.texture !is null)
345         {
346             width = max(width, r.texture.width);
347             height = max(height, r.texture.height);
348         }
349 
350         if (g.texture !is null)
351         {
352             width = max(width, g.texture.width);
353             height = max(height, g.texture.height);
354         }
355 
356         if (b.texture !is null)
357         {
358             width = max(width, b.texture.width);
359             height = max(height, b.texture.height);
360         }
361 
362         if (a.texture !is null)
363         {
364             width = max(width, a.texture.width);
365             height = max(height, a.texture.height);
366         }
367 
368         SuperImage img = New!UnmanagedImageRGBA8(width, height);
369 
370         foreach(y; 0..img.height)
371         foreach(x; 0..img.width)
372         {
373             Color4f col = Color4f(0, 0, 0, 0);
374 
375             float u = cast(float)x / cast(float)img.width;
376             float v = cast(float)y / cast(float)img.height;
377 
378             col.r = r.sample(u, v).r;
379             col.g = g.sample(u, v).r;
380             col.b = b.sample(u, v).r;
381             col.a = a.sample(u, v).r;
382 
383             img[x, y] = col;
384         }
385 
386         auto tex = New!Texture(img, this);
387         return tex;
388     }
389 
390     bool isTransparent()
391     {
392         auto iblending = "blending" in inputs;
393         int b = iblending.asInteger;
394         return (b == Transparent || b == Additive);
395     }
396 
397     bool usesCustomShader()
398     {
399         return customShader;
400     }
401 
402     void bind(RenderingContext* rc)
403     {
404         auto iblending = "blending" in inputs;
405         auto iculling = "culling" in inputs;
406         auto icolorWrite = "colorWrite" in inputs;
407         auto idepthWrite = "depthWrite" in inputs;
408 
409         if (iblending.asInteger == Transparent)
410         {
411             glEnablei(GL_BLEND, 0);
412             glEnablei(GL_BLEND, 1);
413             glEnablei(GL_BLEND, 2);
414             glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
415             glBlendFunci(1, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
416             glBlendFunci(2, GL_SRC_ALPHA, GL_ONE);
417         }
418         else if (iblending.asInteger == Additive)
419         {
420             glEnablei(GL_BLEND, 0);
421             glEnablei(GL_BLEND, 1);
422             glEnablei(GL_BLEND, 2);
423             glBlendFunci(0, GL_SRC_ALPHA, GL_ONE);
424             glBlendFunci(1, GL_SRC_ALPHA, GL_ONE);
425             glBlendFunci(2, GL_SRC_ALPHA, GL_ONE);
426         }
427 
428         if (iculling.asBool)
429         {
430             glEnable(GL_CULL_FACE);
431         }
432 
433         if (!icolorWrite.asBool)
434         {
435             glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
436         }
437 
438         if (!idepthWrite.asBool && !rc.shadowPass)
439         {
440             glDepthMask(GL_FALSE);
441         }
442 
443         RenderingContext rcLocal = *rc;
444         rcLocal.material = this;
445 
446         if (rc.overrideShader)
447             rc.overrideShader.bind(&rcLocal);
448         else if (shader)
449             shader.bind(&rcLocal);
450     }
451 
452     void unbind(RenderingContext* rc)
453     {
454         auto icolorWrite = "colorWrite" in inputs;
455         auto idepthWrite = "depthWrite" in inputs;
456 
457         RenderingContext rcLocal = *rc;
458         rcLocal.material = this;
459 
460         if (rc.overrideShader)
461             rc.overrideShader.unbind(&rcLocal);
462         else if (shader)
463             shader.unbind(&rcLocal);
464 
465         if (!idepthWrite.asBool && rc.depthPass)
466         {
467             glDepthMask(GL_TRUE);
468         }
469 
470         if (!icolorWrite.asBool && rc.colorPass)
471         {
472             glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
473         }
474 
475         glDisable(GL_CULL_FACE);
476 
477         glDisablei(GL_BLEND, 0);
478         glDisablei(GL_BLEND, 1);
479         glDisablei(GL_BLEND, 2);
480     }
481 }
482 
483 deprecated("use `Material` instead") alias GenericMaterial = Material;
484 deprecated("use `Material` instead") alias ShaderMaterial = Material;