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