/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.tests.g3d;
import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Cubemap;
import com.badlogic.gdx.graphics.Cubemap.CubemapSide;
import com.badlogic.gdx.graphics.g3d.Attributes;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.Renderable;
import com.badlogic.gdx.graphics.g3d.Shader;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.attributes.CubemapAttribute;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
import com.badlogic.gdx.graphics.g3d.model.Animation;
import com.badlogic.gdx.graphics.g3d.shaders.BaseShader;
import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController;
import com.badlogic.gdx.graphics.g3d.utils.DefaultShaderProvider;
import com.badlogic.gdx.graphics.glutils.FacedCubemapData;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.List;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.tests.g3d.shaders.MultiPassShader;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.StringBuilder;
public class ShaderCollectionTest extends BaseG3dHudTest {
/** Desktop only: Set this to an absolute path to load the shader files from an alternative location. */
final static String hotLoadFolder = null;
/** Desktop only: Set this to an absolute path to save the generated shader files. */
final static String tempFolder = System.getProperty("java.io.tmp");
protected String shaders[] = new String[] {"<default>", "depth", "gouraud", "phong", "normal", "fur", "cubemap", "reflect",
"test"};
protected String environments[] = new String[] {"<none>", "debug", "environment_01", "environment_02"};
protected String materials[] = new String[] {"diffuse_green", "badlogic_normal", "brick01", "brick02", "brick03",
"chesterfield", "cloth01", "cloth02", "elephant01", "elephant02", "fur01", "grass01", "metal01", "metal02", "mirror01",
"mirror02", "moon01", "plastic01", "stone01", "stone02", "wood01", "wood02"};
public static class TestShaderProvider extends DefaultShaderProvider {
public boolean error = false;
public String name = "default";
public void clear () {
for (final Shader shader : shaders)
shader.dispose();
shaders.clear();
}
public boolean revert () {
if (config.vertexShader == null || config.fragmentShader == null) return false;
config.vertexShader = null;
config.fragmentShader = null;
clear();
return true;
}
@Override
public Shader getShader (Renderable renderable) {
try {
return super.getShader(renderable);
} catch (Throwable e) {
if (tempFolder != null && Gdx.app.getType() == ApplicationType.Desktop)
Gdx.files.absolute(tempFolder).child(name + ".log.txt").writeString(e.getMessage(), false);
if (!revert()) {
Gdx.app.error("ShaderCollectionTest", e.getMessage());
throw new GdxRuntimeException("Error creating shader, cannot revert to default shader", e);
}
error = true;
Gdx.app.error("ShaderTest", "Could not create shader, reverted to default shader.", e);
return super.getShader(renderable);
}
}
@Override
protected Shader createShader (Renderable renderable) {
if (config.vertexShader != null && config.fragmentShader != null && tempFolder != null
&& Gdx.app.getType() == ApplicationType.Desktop) {
String prefix = DefaultShader.createPrefix(renderable, config);
Gdx.files.absolute(tempFolder).child(name + ".vertex.glsl").writeString(prefix + config.vertexShader, false);
Gdx.files.absolute(tempFolder).child(name + ".fragment.glsl").writeString(prefix + config.fragmentShader, false);
}
BaseShader result = new MultiPassShader(renderable, config);
if (tempFolder != null && Gdx.app.getType() == ApplicationType.Desktop)
Gdx.files.absolute(tempFolder).child(name + ".log.txt").writeString(result.program.getLog(), false);
return result;
}
}
protected Environment environment;
protected DirectionalLight dirLight;
protected TestShaderProvider shaderProvider;
protected FileHandle shaderRoot;
protected ModelBatch shaderBatch;
protected CollapsableWindow shadersWindow, materialsWindow, environmentsWindow;
protected ObjectMap<ModelInstance, AnimationController> animationControllers = new ObjectMap<ModelInstance, AnimationController>();
protected String currentModel = null;
protected String currentMaterial = null;
protected boolean loadingMaterial = false;
Cubemap cubemap;
@Override
public void create () {
super.create();
environment = new Environment();
environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.1f, 0.1f, 0.1f, 1.f));
environment.add(dirLight = new DirectionalLight().set(0.8f, 0.8f, 0.8f, -0.5f, -1.0f, -0.8f));
shaderProvider = new TestShaderProvider();
shaderBatch = new ModelBatch(shaderProvider);
cam.position.set(1, 1, 1);
cam.lookAt(0, 0, 0);
cam.update();
showAxes = true;
onModelClicked("g3d/shapes/teapot.g3dj");
shaderRoot = (hotLoadFolder != null && Gdx.app.getType() == ApplicationType.Desktop) ? Gdx.files.absolute(hotLoadFolder)
: Gdx.files.internal("data/g3d/shaders");
}
@Override
public void dispose () {
shaderBatch.dispose();
shaderBatch = null;
shaderProvider = null;
if (cubemap != null) cubemap.dispose();
cubemap = null;
super.dispose();
}
public void setEnvironment (String name) {
if (name == null) return;
if (cubemap != null) {
cubemap.dispose();
cubemap = null;
}
if (name.equals("<none>")) {
if (environment.has(CubemapAttribute.EnvironmentMap)) {
environment.remove(CubemapAttribute.EnvironmentMap);
shaderProvider.clear();
}
} else {
FileHandle root = Gdx.files.internal("data/g3d/environment");
FacedCubemapData faces = new FacedCubemapData(root.child(name + "_PX.png"), root.child(name+"_NX.png"),
root.child(name + "_PY.png"), root.child(name + "_NY.png"), root.child(name + "_PZ.png"),
root.child(name + "_NZ.png"), false); // FIXME mipmapping on desktop
cubemap = new Cubemap(faces);
faces.load(CubemapSide.NegativeX, root.child(name + "_NX.png"));
cubemap.load(faces);
if (!environment.has(CubemapAttribute.EnvironmentMap)) shaderProvider.clear();
environment.set(new CubemapAttribute(CubemapAttribute.EnvironmentMap, cubemap));
}
}
public void setMaterial (String name) {
if (name == null) return;
if (currentlyLoading != null) {
Gdx.app.error("ModelTest", "Wait for the current model/material to be loaded.");
return;
}
currentlyLoading = "data/g3d/materials/" + name + ".g3dj";
loadingMaterial = true;
if (!name.equals(currentMaterial)) assets.load(currentlyLoading, Model.class);
loading = true;
}
public void setShader (String name) {
shaderProvider.error = false;
if (name.equals("<default>")) {
shaderProvider.config.vertexShader = null;
shaderProvider.config.fragmentShader = null;
shaderProvider.name = "default";
} else {
ShaderLoader loader = new ShaderLoader(shaderRoot);
shaderProvider.config.vertexShader = loader.load(name + ".glsl:VS");
shaderProvider.config.fragmentShader = loader.load(name + ".glsl:FS");
shaderProvider.name = name;
}
shaderProvider.clear();
}
private final Vector3 tmpV = new Vector3();
private final Quaternion tmpQ = new Quaternion();
private final BoundingBox bounds = new BoundingBox();
@Override
protected void render (ModelBatch batch, Array<ModelInstance> instances) {
}
final Vector3 dirLightRotAxis = new Vector3(-1, -1, -1).nor();
@Override
public void render (Array<ModelInstance> instances) {
dirLight.direction.rotate(dirLightRotAxis, Gdx.graphics.getDeltaTime() * 45f);
super.render(null);
for (ObjectMap.Entry<ModelInstance, AnimationController> e : animationControllers.entries())
e.value.update(Gdx.graphics.getDeltaTime());
shaderBatch.begin(cam);
shaderBatch.render(instances, environment);
shaderBatch.end();
}
@Override
protected void getStatus (StringBuilder stringBuilder) {
super.getStatus(stringBuilder);
if (shaderProvider.error)
stringBuilder.append(" ERROR CREATING SHADER, REVERTED TO DEFAULT");
else {
for (final ModelInstance instance : instances) {
if (instance.animations.size > 0) {
stringBuilder.append(" press space or menu to switch animation");
break;
}
}
}
}
protected String currentlyLoading;
@Override
protected void onModelClicked (final String name) {
if (name == null) return;
if (currentlyLoading != null) {
Gdx.app.error("ModelTest", "Wait for the current model/material to be loaded.");
return;
}
currentlyLoading = "data/" + name;
loadingMaterial = false;
if (!name.equals(currentModel)) assets.load(currentlyLoading, Model.class);
loading = true;
}
@Override
protected void onLoaded () {
if (currentlyLoading == null || currentlyLoading.length() == 0) return;
if (loadingMaterial) {
loadingMaterial = false;
if (currentMaterial != null && !currentMaterial.equals(currentlyLoading)) assets.unload(currentMaterial);
currentMaterial = currentlyLoading;
currentlyLoading = null;
ModelInstance instance = instances.get(0);
if (instance != null) {
instance.materials.get(0).clear();
instance.materials.get(0).set(assets.get(currentMaterial, Model.class).materials.get(0));
}
} else {
if (currentModel != null && !currentModel.equals(currentlyLoading)) assets.unload(currentModel);
currentModel = currentlyLoading;
currentlyLoading = null;
instances.clear();
animationControllers.clear();
final ModelInstance instance = new ModelInstance(assets.get(currentModel, Model.class), transform);
instances.add(instance);
if (instance.animations.size > 0) animationControllers.put(instance, new AnimationController(instance));
instance.calculateBoundingBox(bounds);
cam.position.set(1, 1, 1).nor().scl(bounds.getDimensions(tmpV).len() * 0.75f).add(bounds.getCenter(tmpV));
cam.up.set(0, 1, 0);
cam.lookAt(inputController.target.set(bounds.getCenter(tmpV)));
cam.far = Math.max(100f, bounds.getDimensions(tmpV).len() * 2.0f);
cam.update();
moveRadius = bounds.getDimensions(tmpV).len() * 0.25f;
}
}
@Override
protected void createHUD () {
super.createHUD();
final List<String> shadersList = new List(skin);
shadersList.setItems(shaders);
shadersList.addListener(new ClickListener() {
@Override
public void clicked (InputEvent event, float x, float y) {
if (!shadersWindow.isCollapsed() && getTapCount() == 2) {
setShader(shadersList.getSelected());
shadersWindow.collapse();
}
}
});
shadersWindow = addListWindow("Shaders", shadersList, -1, -1);
final List<String> materialsList = new List(skin);
materialsList.setItems(materials);
materialsList.addListener(new ClickListener() {
@Override
public void clicked (InputEvent event, float x, float y) {
if (!materialsWindow.isCollapsed() && getTapCount() == 2) {
setMaterial(materialsList.getSelected());
materialsWindow.collapse();
}
}
});
materialsWindow = addListWindow("Materials", materialsList, modelsWindow.getWidth(), -1);
final List<String> environmentsList = new List(skin);
environmentsList.setItems(environments);
environmentsList.addListener(new ClickListener() {
@Override
public void clicked (InputEvent event, float x, float y) {
if (!environmentsWindow.isCollapsed() && getTapCount() == 2) {
setEnvironment(environmentsList.getSelected());
environmentsWindow.collapse();
}
}
});
environmentsWindow = addListWindow("Environments", environmentsList, materialsWindow.getRight(), -1);
}
protected void switchAnimation () {
for (ObjectMap.Entry<ModelInstance, AnimationController> e : animationControllers.entries()) {
int animIndex = 0;
if (e.value.current != null) {
for (int i = 0; i < e.key.animations.size; i++) {
final Animation animation = e.key.animations.get(i);
if (e.value.current.animation == animation) {
animIndex = i;
break;
}
}
}
animIndex = (animIndex + 1) % e.key.animations.size;
e.value.animate(e.key.animations.get(animIndex).id, -1, 1f, null, 0.2f);
}
}
@Override
public boolean keyUp (int keycode) {
if (keycode == Keys.SPACE || keycode == Keys.MENU) switchAnimation();
return super.keyUp(keycode);
}
}