/******************************************************************************* * Copyright 2015 See AUTHORS file. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.mygdx.game.blender; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.assets.loaders.ModelLoader; import com.badlogic.gdx.assets.loaders.TextureLoader; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.particles.ParticleEffect; import com.badlogic.gdx.graphics.g3d.particles.ParticleEffectLoader; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ArrayMap; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.JsonValue; import com.badlogic.gdx.utils.ObjectMap; import com.mygdx.game.GameScreen; import com.mygdx.game.blender.objects.BlenderCamera; import com.mygdx.game.blender.objects.BlenderEmpty; import com.mygdx.game.blender.objects.BlenderLight; import com.mygdx.game.blender.objects.BlenderModel; import com.mygdx.game.blender.objects.BlenderObject; /** * Reads blender json exported from a particular .blend file, and loads * all {@link Model} assets required to render the scene. * </p> * When the json is read, Game object placeholders called {@link BlenderObject}s are created * and placed in 3D space. * * @author jsjolund */ public class BlenderAssetManager implements Disposable { /** * Holds an id reference to an asset by id, e.g. Model.class -> "dog" -> Model * or Texture.class -> "dogtexture" -> Texture * * @param <T> */ private class DisposableHolder<T extends Disposable> implements Disposable { ObjectMap<Class<T>, ObjectMap<String, T>> map = new ObjectMap<Class<T>, ObjectMap<String, T>>(); /** * Add a disposable asset to be held * * @param name Asset name * @param asset The asset * @param type Type of asset */ public void add(String name, T asset, Class<T> type) { if (!map.containsKey(type)) { map.put(type, new ObjectMap<String, T>()); } ObjectMap<String, T> innerMap = map.get(type); if (innerMap.containsKey(name)) { throw new GdxRuntimeException("Asset name is already used, try changing it: '" + name + "'"); } innerMap.put(name, asset); } /** * Get a held asset * * @param name * @param type * @return */ public T get(String name, Class<T> type) { return map.get(type).get(name); } public boolean contains(String id, Class<T> type) { return map.containsKey(type) && map.get(type).containsKey(id); } @Override public void dispose() { for (ObjectMap.Entry<Class<T>, ObjectMap<String, T>> entryClass : map) { for (ObjectMap.Entry<String, T> entryId : entryClass.value) { entryId.value.dispose(); } } map.clear(); } } private class BlenderTexture extends BlenderObject { String filePath; public BlenderTexture(String name, String filePath) { this.name = name; this.filePath = filePath; } } private final String modelPath; private final String modelExt; private final String pfxPath; private final ModelLoader.ModelParameters modelParameters; private final TextureLoader.TextureParameter textureParameter; private final ParticleEffectLoader.ParticleEffectLoadParameter pfxParameter; private final AssetManager assetManager = GameScreen.screen.getGame().getAssetManager(); private final DisposableHolder disposableHolder = new DisposableHolder(); public BlenderAssetManager( ModelLoader.ModelParameters modelParameters, TextureLoader.TextureParameter textureParameter, ParticleEffectLoader.ParticleEffectLoadParameter pfxParameter, String pfxPath, String modelPath, String modelExt) { this.modelExt = modelExt; this.modelPath = modelPath; this.pfxPath = pfxPath; this.modelParameters = modelParameters; this.textureParameter = textureParameter; this.pfxParameter = pfxParameter; } public <T extends Disposable> void manageDisposable(String assetId, T asset, Class<T> type) { disposableHolder.add(assetId, asset, type); } public <T> void manageDisposableFromPath(String assetId, String localPath, Class<T> type) { if (type == Texture.class) { sceneData.textures.add(new BlenderTexture(assetId, localPath)); assetManager.load(localPath, Texture.class, textureParameter); } else if (type == Model.class) { sceneData.models.add(new BlenderModel(assetId, localPath)); assetManager.load(localPath, Model.class, modelParameters); } else { throw new GdxRuntimeException("Asset type not supported '" + type + "'"); } } private static class BlenderObjectMap<T extends BlenderObject> extends ArrayMap<String, Array<T>> { public void add(T object) { if (containsKey(object.name)) { get(object.name).add(object); } else { Array<T> array = new Array<T>(); put(object.name, array); array.add(object); } } public void addAll(Array<T> objects) { for (T object : objects) { add(object); } } public void addAll(BlenderObjectMap<T> other) { for (Array<T> value : other.values()) { addAll(value); } } public Array<T> removeByName(String name) { return removeKey(name); } public Array<T> getByName(String name) { return get(name); } } private static class BlenderSceneData implements Json.Serializable { final BlenderObjectMap<BlenderTexture> textures = new BlenderObjectMap<BlenderTexture>(); final BlenderObjectMap<BlenderModel> models = new BlenderObjectMap<BlenderModel>(); final BlenderObjectMap<BlenderEmpty> empties = new BlenderObjectMap<BlenderEmpty>(); final BlenderObjectMap<BlenderLight> lights = new BlenderObjectMap<BlenderLight>(); final BlenderObjectMap<BlenderCamera> cameras = new BlenderObjectMap<BlenderCamera>(); @Override public void write(Json json) { } @Override public void read(Json json, JsonValue jsonData) { for (JsonValue category : jsonData) { if (category.name.equals("model")) { models.addAll(json.readValue(Array.class, BlenderModel.class, category)); } else if (category.name.equals("empty")) { empties.addAll(json.readValue(Array.class, BlenderEmpty.class, category)); } else if (category.name.equals("light")) { lights.addAll(json.readValue(Array.class, BlenderLight.class, category)); } else if (category.name.equals("camera")) { cameras.addAll(json.readValue(Array.class, BlenderCamera.class, category)); } } } public void add(BlenderSceneData other) { textures.addAll(other.textures); models.addAll(other.models); empties.addAll(other.empties); lights.addAll(other.lights); cameras.addAll(other.cameras); } } BlenderSceneData sceneData = new BlenderSceneData(); public void load(String jsonPath) { BlenderSceneData newData = new Json().fromJson(BlenderSceneData.class, Gdx.files.internal(jsonPath)); // Load models with assetmanager for (String model : newData.models.keys()) { BlenderModel bModel = newData.models.get(model).first(); String filePath = modelPath + bModel.model_file_name + modelExt; assetManager.load(filePath, Model.class, modelParameters); } // Load particle effects with assetmanager for (Array<BlenderEmpty> empties : newData.empties.values()) { for (BlenderEmpty empty : empties) { if (empty.custom_properties.containsKey("pfx")) { String filePath = pfxPath + empty.custom_properties.get("pfx"); assetManager.load(filePath, ParticleEffect.class, pfxParameter); } } } sceneData.add(newData); } public <T extends Disposable> T getAsset(String assetId, Class<T> type) { if (disposableHolder.contains(assetId, type)) { return (T) disposableHolder.get(assetId, type); } String filePath = null; if (type == Model.class) { try { String fileName = sceneData.models.getByName(assetId).first().model_file_name; filePath = modelPath + fileName + modelExt; } catch (Exception e) { throw new GdxRuntimeException("Could not find asset type:'" + type + "', name:'" + assetId + "'"); } } else if (type == Texture.class) { try { filePath = sceneData.textures.getByName(assetId).first().filePath; } catch (Exception e) { throw new GdxRuntimeException("Could not find asset type:'" + type + "', name:'" + assetId + "'"); } } else if (type == ParticleEffect.class) { try { filePath = pfxPath + assetId; } catch (Exception e) { throw new GdxRuntimeException("Could not find asset type:'" + type + "', name:'" + assetId + "'"); } } assetManager.finishLoadingAsset(filePath); return assetManager.get(filePath, type); } @SuppressWarnings("unchecked") private <S extends BlenderObjectMap<T>, T extends BlenderObject> S getTypeMap(Class<T> objClass) { S map = null; if (objClass == BlenderModel.class) { map = (S) sceneData.models; } else if (objClass == BlenderLight.class) { map = (S) sceneData.lights; } else if (objClass == BlenderEmpty.class) { map = (S) sceneData.empties; } else if (objClass == BlenderCamera.class) { map = (S) sceneData.cameras; } else { throw new GdxRuntimeException("Unknown map for type '" + objClass + "'"); } return map; } public <T extends BlenderObject> Array<String> getPlaceholderIdsByType(Class<T> objClass) { return getTypeMap(objClass).keys().toArray(); } public <T extends BlenderObject> void addPlaceholder(T instance, Class<T> type) { getTypeMap(type).add(instance); } public <T extends BlenderObject> void addPlaceholders(Array<T> instances, Class<T> type) { getTypeMap(type).addAll(instances); } public <S extends Array<T>, T extends BlenderObject> S getPlaceholders(String assetName, Class<T> type, S out) { Array<T> instances = getTypeMap(type).getByName(assetName); if (instances != null) { out.addAll(instances); } return out; } public <S extends Array<T>, T extends BlenderObject> S getAllPlaceholders(Class<T> type, S out) { BlenderObjectMap<T> map = getTypeMap(type); for (ObjectMap.Entry<String, Array<T>> entry : map) { out.addAll(entry.value); } return out; } @Override public void dispose() { disposableHolder.dispose(); } }