Home | History | Annotate | Download | only in g3d
      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 
     72 		public void clear () {
     73 			for (final Shader shader : shaders)
     74 				shader.dispose();
     75 			shaders.clear();
     76 		}
     77 
     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
     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
    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
    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
    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 
    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 
    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 
    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
    218 	protected void render (ModelBatch batch, Array<ModelInstance> instances) {
    219 	}
    220 
    221 	final Vector3 dirLightRotAxis = new Vector3(-1, -1, -1).nor();
    222 
    223 	@Override
    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
    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
    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
    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
    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 
    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
    364 	public boolean keyUp (int keycode) {
    365 		if (keycode == Keys.SPACE || keycode == Keys.MENU) switchAnimation();
    366 		return super.keyUp(keycode);
    367 	}
    368 }
    369