• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.tests.g3d;
18 
19 import com.badlogic.gdx.Application.ApplicationType;
20 import com.badlogic.gdx.Gdx;
21 import com.badlogic.gdx.Input.Keys;
22 import com.badlogic.gdx.files.FileHandle;
23 import com.badlogic.gdx.graphics.Cubemap;
24 import com.badlogic.gdx.graphics.Cubemap.CubemapSide;
25 import com.badlogic.gdx.graphics.g3d.Attributes;
26 import com.badlogic.gdx.graphics.g3d.Environment;
27 import com.badlogic.gdx.graphics.g3d.Model;
28 import com.badlogic.gdx.graphics.g3d.ModelBatch;
29 import com.badlogic.gdx.graphics.g3d.ModelInstance;
30 import com.badlogic.gdx.graphics.g3d.Renderable;
31 import com.badlogic.gdx.graphics.g3d.Shader;
32 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
33 import com.badlogic.gdx.graphics.g3d.attributes.CubemapAttribute;
34 import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
35 import com.badlogic.gdx.graphics.g3d.model.Animation;
36 import com.badlogic.gdx.graphics.g3d.shaders.BaseShader;
37 import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader;
38 import com.badlogic.gdx.graphics.g3d.utils.AnimationController;
39 import com.badlogic.gdx.graphics.g3d.utils.DefaultShaderProvider;
40 import com.badlogic.gdx.graphics.glutils.FacedCubemapData;
41 import com.badlogic.gdx.math.Quaternion;
42 import com.badlogic.gdx.math.Vector3;
43 import com.badlogic.gdx.math.collision.BoundingBox;
44 import com.badlogic.gdx.scenes.scene2d.InputEvent;
45 import com.badlogic.gdx.scenes.scene2d.ui.List;
46 import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
47 import com.badlogic.gdx.tests.g3d.shaders.MultiPassShader;
48 import com.badlogic.gdx.utils.Array;
49 import com.badlogic.gdx.utils.GdxRuntimeException;
50 import com.badlogic.gdx.utils.ObjectMap;
51 import com.badlogic.gdx.utils.StringBuilder;
52 
53 public class ShaderCollectionTest extends BaseG3dHudTest {
54 	/** Desktop only: Set this to an absolute path to load the shader files from an alternative location. */
55 	final static String hotLoadFolder = null;
56 	/** Desktop only: Set this to an absolute path to save the generated shader files. */
57 	final static String tempFolder = System.getProperty("java.io.tmp");
58 
59 	protected String shaders[] = new String[] {"<default>", "depth", "gouraud", "phong", "normal", "fur", "cubemap", "reflect",
60 		"test"};
61 
62 	protected String environments[] = new String[] {"<none>", "debug", "environment_01", "environment_02"};
63 
64 	protected String materials[] = new String[] {"diffuse_green", "badlogic_normal", "brick01", "brick02", "brick03",
65 		"chesterfield", "cloth01", "cloth02", "elephant01", "elephant02", "fur01", "grass01", "metal01", "metal02", "mirror01",
66 		"mirror02", "moon01", "plastic01", "stone01", "stone02", "wood01", "wood02"};
67 
68 	public static class TestShaderProvider extends DefaultShaderProvider {
69 		public boolean error = false;
70 		public String name = "default";
71 
clear()72 		public void clear () {
73 			for (final Shader shader : shaders)
74 				shader.dispose();
75 			shaders.clear();
76 		}
77 
revert()78 		public boolean revert () {
79 			if (config.vertexShader == null || config.fragmentShader == null) return false;
80 			config.vertexShader = null;
81 			config.fragmentShader = null;
82 			clear();
83 			return true;
84 		}
85 
86 		@Override
getShader(Renderable renderable)87 		public Shader getShader (Renderable renderable) {
88 			try {
89 				return super.getShader(renderable);
90 			} catch (Throwable e) {
91 				if (tempFolder != null && Gdx.app.getType() == ApplicationType.Desktop)
92 					Gdx.files.absolute(tempFolder).child(name + ".log.txt").writeString(e.getMessage(), false);
93 				if (!revert()) {
94 					Gdx.app.error("ShaderCollectionTest", e.getMessage());
95 					throw new GdxRuntimeException("Error creating shader, cannot revert to default shader", e);
96 				}
97 				error = true;
98 				Gdx.app.error("ShaderTest", "Could not create shader, reverted to default shader.", e);
99 				return super.getShader(renderable);
100 			}
101 		}
102 
103 		@Override
createShader(Renderable renderable)104 		protected Shader createShader (Renderable renderable) {
105 			if (config.vertexShader != null && config.fragmentShader != null && tempFolder != null
106 				&& Gdx.app.getType() == ApplicationType.Desktop) {
107 				String prefix = DefaultShader.createPrefix(renderable, config);
108 				Gdx.files.absolute(tempFolder).child(name + ".vertex.glsl").writeString(prefix + config.vertexShader, false);
109 				Gdx.files.absolute(tempFolder).child(name + ".fragment.glsl").writeString(prefix + config.fragmentShader, false);
110 			}
111 			BaseShader result = new MultiPassShader(renderable, config);
112 			if (tempFolder != null && Gdx.app.getType() == ApplicationType.Desktop)
113 				Gdx.files.absolute(tempFolder).child(name + ".log.txt").writeString(result.program.getLog(), false);
114 			return result;
115 		}
116 	}
117 
118 	protected Environment environment;
119 	protected DirectionalLight dirLight;
120 	protected TestShaderProvider shaderProvider;
121 	protected FileHandle shaderRoot;
122 	protected ModelBatch shaderBatch;
123 	protected CollapsableWindow shadersWindow, materialsWindow, environmentsWindow;
124 	protected ObjectMap<ModelInstance, AnimationController> animationControllers = new ObjectMap<ModelInstance, AnimationController>();
125 	protected String currentModel = null;
126 	protected String currentMaterial = null;
127 	protected boolean loadingMaterial = false;
128 	Cubemap cubemap;
129 
130 	@Override
create()131 	public void create () {
132 		super.create();
133 		environment = new Environment();
134 		environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.1f, 0.1f, 0.1f, 1.f));
135 		environment.add(dirLight = new DirectionalLight().set(0.8f, 0.8f, 0.8f, -0.5f, -1.0f, -0.8f));
136 
137 		shaderProvider = new TestShaderProvider();
138 		shaderBatch = new ModelBatch(shaderProvider);
139 
140 		cam.position.set(1, 1, 1);
141 		cam.lookAt(0, 0, 0);
142 		cam.update();
143 		showAxes = true;
144 
145 		onModelClicked("g3d/shapes/teapot.g3dj");
146 
147 		shaderRoot = (hotLoadFolder != null && Gdx.app.getType() == ApplicationType.Desktop) ? Gdx.files.absolute(hotLoadFolder)
148 			: Gdx.files.internal("data/g3d/shaders");
149 	}
150 
151 	@Override
dispose()152 	public void dispose () {
153 		shaderBatch.dispose();
154 		shaderBatch = null;
155 		shaderProvider = null;
156 		if (cubemap != null) cubemap.dispose();
157 		cubemap = null;
158 		super.dispose();
159 	}
160 
setEnvironment(String name)161 	public void setEnvironment (String name) {
162 		if (name == null) return;
163 		if (cubemap != null) {
164 			cubemap.dispose();
165 			cubemap = null;
166 		}
167 		if (name.equals("<none>")) {
168 			if (environment.has(CubemapAttribute.EnvironmentMap)) {
169 				environment.remove(CubemapAttribute.EnvironmentMap);
170 				shaderProvider.clear();
171 			}
172 		} else {
173 			FileHandle root = Gdx.files.internal("data/g3d/environment");
174 			FacedCubemapData faces = new FacedCubemapData(root.child(name + "_PX.png"), root.child(name+"_NX.png"),
175 				root.child(name + "_PY.png"), root.child(name + "_NY.png"), root.child(name + "_PZ.png"),
176 				root.child(name + "_NZ.png"), false); // FIXME mipmapping on desktop
177 			cubemap = new Cubemap(faces);
178 			faces.load(CubemapSide.NegativeX, root.child(name + "_NX.png"));
179 			cubemap.load(faces);
180 			if (!environment.has(CubemapAttribute.EnvironmentMap)) shaderProvider.clear();
181 			environment.set(new CubemapAttribute(CubemapAttribute.EnvironmentMap, cubemap));
182 		}
183 	}
184 
setMaterial(String name)185 	public void setMaterial (String name) {
186 		if (name == null) return;
187 		if (currentlyLoading != null) {
188 			Gdx.app.error("ModelTest", "Wait for the current model/material to be loaded.");
189 			return;
190 		}
191 
192 		currentlyLoading = "data/g3d/materials/" + name + ".g3dj";
193 		loadingMaterial = true;
194 		if (!name.equals(currentMaterial)) assets.load(currentlyLoading, Model.class);
195 		loading = true;
196 	}
197 
setShader(String name)198 	public void setShader (String name) {
199 		shaderProvider.error = false;
200 		if (name.equals("<default>")) {
201 			shaderProvider.config.vertexShader = null;
202 			shaderProvider.config.fragmentShader = null;
203 			shaderProvider.name = "default";
204 		} else {
205 			ShaderLoader loader = new ShaderLoader(shaderRoot);
206 			shaderProvider.config.vertexShader = loader.load(name + ".glsl:VS");
207 			shaderProvider.config.fragmentShader = loader.load(name + ".glsl:FS");
208 			shaderProvider.name = name;
209 		}
210 		shaderProvider.clear();
211 	}
212 
213 	private final Vector3 tmpV = new Vector3();
214 	private final Quaternion tmpQ = new Quaternion();
215 	private final BoundingBox bounds = new BoundingBox();
216 
217 	@Override
render(ModelBatch batch, Array<ModelInstance> instances)218 	protected void render (ModelBatch batch, Array<ModelInstance> instances) {
219 	}
220 
221 	final Vector3 dirLightRotAxis = new Vector3(-1, -1, -1).nor();
222 
223 	@Override
render(Array<ModelInstance> instances)224 	public void render (Array<ModelInstance> instances) {
225 		dirLight.direction.rotate(dirLightRotAxis, Gdx.graphics.getDeltaTime() * 45f);
226 
227 		super.render(null);
228 		for (ObjectMap.Entry<ModelInstance, AnimationController> e : animationControllers.entries())
229 			e.value.update(Gdx.graphics.getDeltaTime());
230 		shaderBatch.begin(cam);
231 		shaderBatch.render(instances, environment);
232 		shaderBatch.end();
233 	}
234 
235 	@Override
getStatus(StringBuilder stringBuilder)236 	protected void getStatus (StringBuilder stringBuilder) {
237 		super.getStatus(stringBuilder);
238 
239 		if (shaderProvider.error)
240 			stringBuilder.append(" ERROR CREATING SHADER, REVERTED TO DEFAULT");
241 		else {
242 			for (final ModelInstance instance : instances) {
243 				if (instance.animations.size > 0) {
244 					stringBuilder.append(" press space or menu to switch animation");
245 					break;
246 				}
247 			}
248 		}
249 	}
250 
251 	protected String currentlyLoading;
252 
253 	@Override
onModelClicked(final String name)254 	protected void onModelClicked (final String name) {
255 		if (name == null) return;
256 		if (currentlyLoading != null) {
257 			Gdx.app.error("ModelTest", "Wait for the current model/material to be loaded.");
258 			return;
259 		}
260 
261 		currentlyLoading = "data/" + name;
262 		loadingMaterial = false;
263 		if (!name.equals(currentModel)) assets.load(currentlyLoading, Model.class);
264 		loading = true;
265 	}
266 
267 	@Override
onLoaded()268 	protected void onLoaded () {
269 		if (currentlyLoading == null || currentlyLoading.length() == 0) return;
270 
271 		if (loadingMaterial) {
272 			loadingMaterial = false;
273 			if (currentMaterial != null && !currentMaterial.equals(currentlyLoading)) assets.unload(currentMaterial);
274 			currentMaterial = currentlyLoading;
275 			currentlyLoading = null;
276 			ModelInstance instance = instances.get(0);
277 			if (instance != null) {
278 				instance.materials.get(0).clear();
279 				instance.materials.get(0).set(assets.get(currentMaterial, Model.class).materials.get(0));
280 			}
281 		} else {
282 			if (currentModel != null && !currentModel.equals(currentlyLoading)) assets.unload(currentModel);
283 			currentModel = currentlyLoading;
284 			currentlyLoading = null;
285 
286 			instances.clear();
287 			animationControllers.clear();
288 			final ModelInstance instance = new ModelInstance(assets.get(currentModel, Model.class), transform);
289 			instances.add(instance);
290 			if (instance.animations.size > 0) animationControllers.put(instance, new AnimationController(instance));
291 
292 			instance.calculateBoundingBox(bounds);
293 			cam.position.set(1, 1, 1).nor().scl(bounds.getDimensions(tmpV).len() * 0.75f).add(bounds.getCenter(tmpV));
294 			cam.up.set(0, 1, 0);
295 			cam.lookAt(inputController.target.set(bounds.getCenter(tmpV)));
296 			cam.far = Math.max(100f, bounds.getDimensions(tmpV).len() * 2.0f);
297 			cam.update();
298 			moveRadius = bounds.getDimensions(tmpV).len() * 0.25f;
299 		}
300 	}
301 
302 	@Override
createHUD()303 	protected void createHUD () {
304 		super.createHUD();
305 
306 		final List<String> shadersList = new List(skin);
307 		shadersList.setItems(shaders);
308 		shadersList.addListener(new ClickListener() {
309 			@Override
310 			public void clicked (InputEvent event, float x, float y) {
311 				if (!shadersWindow.isCollapsed() && getTapCount() == 2) {
312 					setShader(shadersList.getSelected());
313 					shadersWindow.collapse();
314 				}
315 			}
316 		});
317 		shadersWindow = addListWindow("Shaders", shadersList, -1, -1);
318 
319 		final List<String> materialsList = new List(skin);
320 		materialsList.setItems(materials);
321 		materialsList.addListener(new ClickListener() {
322 			@Override
323 			public void clicked (InputEvent event, float x, float y) {
324 				if (!materialsWindow.isCollapsed() && getTapCount() == 2) {
325 					setMaterial(materialsList.getSelected());
326 					materialsWindow.collapse();
327 				}
328 			}
329 		});
330 		materialsWindow = addListWindow("Materials", materialsList, modelsWindow.getWidth(), -1);
331 
332 		final List<String> environmentsList = new List(skin);
333 		environmentsList.setItems(environments);
334 		environmentsList.addListener(new ClickListener() {
335 			@Override
336 			public void clicked (InputEvent event, float x, float y) {
337 				if (!environmentsWindow.isCollapsed() && getTapCount() == 2) {
338 					setEnvironment(environmentsList.getSelected());
339 					environmentsWindow.collapse();
340 				}
341 			}
342 		});
343 		environmentsWindow = addListWindow("Environments", environmentsList, materialsWindow.getRight(), -1);
344 	}
345 
switchAnimation()346 	protected void switchAnimation () {
347 		for (ObjectMap.Entry<ModelInstance, AnimationController> e : animationControllers.entries()) {
348 			int animIndex = 0;
349 			if (e.value.current != null) {
350 				for (int i = 0; i < e.key.animations.size; i++) {
351 					final Animation animation = e.key.animations.get(i);
352 					if (e.value.current.animation == animation) {
353 						animIndex = i;
354 						break;
355 					}
356 				}
357 			}
358 			animIndex = (animIndex + 1) % e.key.animations.size;
359 			e.value.animate(e.key.animations.get(animIndex).id, -1, 1f, null, 0.2f);
360 		}
361 	}
362 
363 	@Override
keyUp(int keycode)364 	public boolean keyUp (int keycode) {
365 		if (keycode == Keys.SPACE || keycode == Keys.MENU) switchAnimation();
366 		return super.keyUp(keycode);
367 	}
368 }
369