1 
2 module ws.gl.material;
3 
4 import
5 	derelict.opengl3.gl3,
6 	std.file,
7 	std.utf,
8 	std.conv,
9 
10 	ws.io,
11 	ws.string,
12 	ws.exception,
13 	ws.decode,
14 	ws.gl.gl,
15 	ws.gl.shader,
16 	ws.gl.texture,
17 	ws.thread.loader,
18 	ws.math.vector,
19 	ws.file.obj;
20 
21 
22 __gshared:
23 
24 
25 const string VERTEX_BASE = "
26 #version 130
27 
28 uniform mat4 mvp;
29 uniform mat4 world;
30 
31 in vec4 vertex;
32 
33 //uniform float far;
34 //out float logz;
35 
36 %s
37 
38 void main(){
39 	%s
40 	gl_Position = mvp*vertex;
41 	//gl_Position.z = log2(max(1e-6, 1.0 + gl_Position.w)) * (2.0 / log2(far + 1.0)) - 1.0;
42 	//gl_Position.z *= gl_Position.w;
43 	//logz = log2(1.0 + gl_Position.w) / log2(far + 1);
44 }";
45 
46 
47 const string FRAGMENT_BASE = "
48 #version 130
49 
50 out vec4 outDiffuse;
51 out vec4 outNormal;
52 out vec4 outLightData;
53 
54 //in float logz;
55 
56 vec4 calcNormal();
57 vec4 calcDiffuse();
58 vec4 calcLightData();
59 
60 void main(){
61 	outDiffuse = calcDiffuse();
62 	if(outDiffuse.a < 0)
63 		discard;
64 	outNormal = calcNormal();
65 	outLightData = calcLightData();
66 	//gl_FragDepth = logz;
67 }";
68 
69 
70 class DeferredMaterial: Loadable {
71 
72 	enum {
73 		diffuse,
74 		normal,
75 		lightInfo
76 	}
77 
78 	enum targetTextures = [
79 		diffuse,
80 		normal,
81 		lightInfo
82 	];
83 
84 	this(string name, string[string] vp=null, string[] fp=null, int[string] attr=null){
85 		this.name = name;
86 		shader = new MaterialShader;
87 		if(!vp)
88 			vp = vp.init;
89 		if(!fp)
90 			fp = fp.init;
91 		foreach(n,f; vp)
92 			shader.partsVertex[n] = f;
93 		shader.partsFragment = fp.dup;
94 		if(!attr)
95 			attr = [
96 					"vertex": gl.attributeVertex, 
97 					"normal": gl.attributeNormal,
98 					"texCoord": gl.attributeTexture
99 					];
100 		this.output = output.dup;
101 		foreach(n,i; attr)
102 			shader.attributes[n] = i;
103 	}
104 
105 
106 	void use(Args...)(Args args){
107 		if(!loaded)
108 			exception("Trying to use unfinished material \"" ~ name ~ "\"");
109 		if(shader.failed)
110 			exception("Material \"" ~ name ~ "\": trying to use failed shader " ~ shader.name);
111 		shader.use(args);
112 		int curtex = 0;
113 		foreach(u; globals){
114 			final switch(u.type){
115 				case MaterialUniform.Type.vec:
116 					u.set(u.data.vec);
117 				break;
118 				case MaterialUniform.Type.number:
119 					u.set(u.data.number);
120 				break;
121 				case MaterialUniform.Type.vec4:
122 					u.set(u.data.vec4);
123 				break;
124 				case MaterialUniform.Type.tex:
125 					glActiveTexture(GL_TEXTURE0 + curtex);
126 					glBindTexture(GL_TEXTURE_2D, u.data.tex.id);
127 					u.set(curtex++);
128 				break;
129 			}
130 		}
131 	}
132 
133 
134 	void addTexture(string id, string path){
135 		if(loadState != Loaded)
136 			onFinish ~= {
137 				globals[id] = new MaterialUniform(shader, id, Texture.load(path));
138 				//gl.check(id);
139 			};
140 		else {
141 			globals[id] = new MaterialUniform(shader, id, Texture.load(path));
142 			//gl.check(id);
143 		}
144 	}
145 
146 
147 	void addUniform(T)(string id, T u){
148 		if(!shader)
149 			onFinish ~= {
150 				globals[id] = new MaterialUniform(shader, id, u);
151 				//gl.check(id);
152 			};
153 		else {
154 			globals[id] = new MaterialUniform(shader, id, u);
155 			//gl.check(id);
156 		}
157 	}
158 
159 
160 	void linkVertex(string path, string fn){
161 		shader.partsVertex[path] = fn;
162 	}
163 
164 
165 	void linkFragment(string part){
166 		shader.partsFragment ~= part;
167 	}
168 
169 	override void finish(){
170 		if(loadState != Idle)
171 			exception("Already finished");
172 		try {
173 			loadState = Loadable.Loading;
174 			string name = 
175 				to!string(shader.partsVertex)
176 				~ to!string(shader.partsFragment) 
177 				~ to!string(shader.attributes);
178 			if(name in shaders)
179 				shader = shaders[name];
180 			else {
181 				shader.name = name;
182 				shader.finish();
183 				shader.bindFrag([diffuse: "outDiffuse", normal: "outNormal", lightInfo: "outLightData"]);
184 				shaders[name] = shader;
185 			}
186 			foreach(f; onFinish)
187 				f();
188 			loadState = Loadable.Loaded;
189 		}catch(Exception e){
190 			loadState = Loadable.Error;
191 			exception("Failed to finish material \"" ~ name ~ "\"", e);
192 		}
193 	}
194 
195 	override string toString(){
196 		return name ~ ':'
197 				~ to!string(shader.partsVertex)
198 				~ to!string(shader.partsFragment) 
199 				~ to!string(shader.attributes);
200 	}
201 
202 	void activateTextures(){
203 		int curtex = 0;
204 		foreach(u; globals){
205 			if(u.type == MaterialUniform.Type.tex){
206 				glActiveTexture(GL_TEXTURE0 + curtex);
207 				glBindTexture(GL_TEXTURE_2D, u.data.tex.id);
208 			}
209 		}
210 	}
211 
212 	protected:
213 
214 		string name;
215 
216 		void delegate()[] onFinish;
217 
218 		string[int] output;
219 		MaterialShader shader;
220 		MaterialUniform[string] globals;
221 
222 		static MaterialShader[string] shaders;
223 
224 
225 		static class MaterialShader: Shader {
226 
227 			void attach(uint type, string path){
228 				try
229 					program.attach(new gl.Shader(type, cast(string)read("shaders/parts/"~path)));
230 				catch(Exception e)
231 					writeln("Failed to compile shader part \"" ~ path ~ "\":\n", e);
232 			}
233 
234 			override void finish(){
235 				try {
236 					program = new gl.Program;
237 					foreach(part, f; partsVertex)
238 						attach(gl.shaderVertex, part ~ ".vp");
239 					program.attach(new gl.Shader(gl.shaderVertex, buildVertex()));
240 					foreach(part; partsFragment)
241 						attach(gl.shaderFragment, part ~ ".fp");
242 					program.attach(new gl.Shader(gl.shaderFragment, FRAGMENT_BASE));
243 					foreach(s,i; attributes)
244 						bindAttr([i: s]);
245 					super.finish();
246 				}catch(Exception e)
247 					exception("Failed to finish material \"" ~ name ~ "\"", e);
248 			}
249 
250 			string buildVertex(){
251 				string dec;
252 				foreach(_, f; partsVertex)
253 					dec ~= "void " ~ f ~ "();\n";
254 				string call;
255 				foreach(_, f; partsVertex)
256 					call ~= '\t' ~ f ~ "();\n";
257 				return VERTEX_BASE.format(dec, call);
258 			}
259 
260 			int[string] attributes;
261 			string[string] partsVertex;
262 			string[] partsFragment;
263 		}
264 
265 
266 		static class MaterialUniform: Shader.Uniform {
267 
268 			string name;
269 			Data data;
270 			Type type;
271 
272 			private this(MaterialShader m, string name){
273 				this.name = name;
274 				super(name, m);
275 			}
276 			this(MaterialShader m, string name, const double n){
277 				this(m, name);
278 				this.type = Type.number;
279 				data.number = cast(int)n;
280 			}
281 			this(MaterialShader m, string name, const float[3] v){
282 				this(m, name);
283 				this.type = Type.vec;
284 				data.vec = v;
285 			}
286 			this(MaterialShader m, string name, const float[4] v){
287 				this(m, name);
288 				this.type = Type.vec4;
289 				data.vec4 = v;
290 			}
291 			this(MaterialShader m, string name, Texture t){
292 				this(m, name);
293 				type = Type.tex;
294 				data.tex = t;
295 			}
296 
297 			union Data {
298 				float[3] vec;
299 				float[4] vec4;
300 				int number;
301 				Texture tex;
302 			}
303 
304 			enum Type {
305 				vec,
306 				number,
307 				vec4,
308 				tex
309 			};
310 
311 		}
312 
313 }
314