All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bladecoder.engine.model.Sprite3DRenderer Maven / Gradle / Ivy

There is a newer version: 4.3.1
Show newest version
/*******************************************************************************
 * Copyright 2014 Rafael Garcia Moreno.
 * 
 * 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.bladecoder.engine.model;

import java.nio.IntBuffer;
import java.util.HashMap;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
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.environment.DirectionalShadowLight;
import com.badlogic.gdx.graphics.g3d.environment.PointLight;
import com.badlogic.gdx.graphics.g3d.model.Animation;
import com.badlogic.gdx.graphics.g3d.model.Node;
import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader.Config;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController.AnimationListener;
import com.badlogic.gdx.graphics.g3d.utils.DefaultShaderProvider;
import com.badlogic.gdx.graphics.g3d.utils.DepthShaderProvider;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.JsonValue;
import com.bladecoder.engine.actions.ActionCallback;
import com.bladecoder.engine.actions.ActionCallbackQueue;
import com.bladecoder.engine.anim.AnimationDesc;
import com.bladecoder.engine.anim.Tween;
import com.bladecoder.engine.assets.EngineAssetManager;
import com.bladecoder.engine.util.ActionCallbackSerialization;
import com.bladecoder.engine.util.EngineLogger;
import com.bladecoder.engine.util.SerializationHelper;
import com.bladecoder.engine.util.SerializationHelper.Mode;
import com.bladecoder.engine.util.Utils3D;

@SuppressWarnings("deprecation")
public class Sprite3DRenderer extends AnimationRenderer {

	private static final String FRAGMENT_SHADER = "com/bladecoder/engine/shading/cel.fragment.glsl";
	private static final String FLOOR_FRAGMENT_SHADER = "com/bladecoder/engine/shading/floor.fragment.glsl";
	private static final String VERTEX_SHADER = "com/bladecoder/engine/shading/cel.vertex.glsl";
	private final static boolean USE_FBO = false;
	private final static int MAX_BONES = 40;
	private final static Format FRAMEBUFFER_FORMAT = Format.RGBA4444;

	private static final Rectangle VIEWPORT = new Rectangle();
	private final static IntBuffer VIEWPORT_RESULTS = BufferUtils.newIntBuffer(16);

	private int currentCount;
	private Tween.Type currentAnimationType;

	private TextureRegion tex;

	private Environment environment;
	private Environment shadowEnvironment;

	private FrameBuffer fb = null;

	private int width = 200, height = 200;

	private Vector3 cameraPos;
	private Vector3 cameraRot;
	private String cameraName = "Camera";
	private float cameraFOV = 49.3f;

	// Rotation of the model in the Y axis
	private float modelRotation = 0;

	// CREATE STATIC BATCHS FOR EFICIENCY
	private static ModelBatch modelBatch;
	private static ModelBatch shadowBatch;
	private static ModelBatch floorBatch;

	// TODO Move shadowLight to static for memory eficiency.
	// This implies that the shadow must be calculated in the draw method and
	// not in the update
	private final DirectionalShadowLight shadowLight = (DirectionalShadowLight) new DirectionalShadowLight(1024, 1024,
			30f, 30f, 1f, 100f).set(1f, 1f, 1f, 0.01f, -1f, 0.01f);

	PointLight celLight;

	String celLightName = "Light";

	private ActionCallback animationCb = null;

	private float lastAnimationTime = 0;

	private boolean renderShadow = true;

	class ModelCacheEntry extends CacheEntry {
		ModelInstance modelInstance;
		AnimationController controller;
		PerspectiveCamera camera3d;
	}

	public Sprite3DRenderer() {

	}

	@Override
	public String getCurrentAnimationId() {
		return currentAnimation.id;
	}

	@Override
	public String[] getInternalAnimations(AnimationDesc anim) {
		retrieveSource(anim.source);

		Array animations = ((ModelCacheEntry)sourceCache.get(anim.source)).modelInstance.animations;
		String[] result = new String[animations.size];

		for (int i = 0; i < animations.size; i++) {
			Animation a = animations.get(i);
			result[i] = a.id;
		}

		return result;
	}

	/**
	 * Render the 3D model into the texture
	 */
	private void renderTex() {
		updateViewport();

		fb.begin();

		Gdx.gl.glClearColor(0, 0, 0, 0);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

		drawModel();

		fb.end((int) VIEWPORT.x, (int) VIEWPORT.y, (int) VIEWPORT.width, (int) VIEWPORT.height);
	}

	/**
	 * Generates the Shadow Map
	 */
	private void genShadowMap() {
		updateViewport();
		
		ModelCacheEntry cs = (ModelCacheEntry)currentSource;

		shadowLight.begin(Vector3.Zero, cs.camera3d.direction);
		shadowBatch.begin(shadowLight.getCamera());
		shadowBatch.render(cs.modelInstance);
		shadowBatch.end();
		shadowLight.end();

		Gdx.graphics.getGL20().glViewport((int) VIEWPORT.x, (int) VIEWPORT.y, (int) VIEWPORT.width,
				(int) VIEWPORT.height);
	}

	private void drawModel() {
		if (currentSource != null) {
			
			ModelCacheEntry cs = (ModelCacheEntry)currentSource;

			// DRAW SHADOW
			if (renderShadow) {
				floorBatch.begin(cs.camera3d);
				floorBatch.render(Utils3D.getFloor(), shadowEnvironment);
				floorBatch.end();
			}

			// DRAW MODEL
			modelBatch.begin(cs.camera3d);

			if (EngineLogger.debugMode() && EngineLogger.debugLevel == EngineLogger.DEBUG1)
				modelBatch.render(Utils3D.getAxes(), environment);

			modelBatch.render(cs.modelInstance, environment);

			modelBatch.end();
		}
	}

	public void setCameraPos(float x, float y, float z) {
		if (cameraPos == null)
			cameraPos = new Vector3(x, y, z);
		else
			cameraPos.set(x, y, z);
	}

	public void setCameraRot(float x, float y, float z) {
		if (cameraRot == null)
			cameraRot = new Vector3(x, y, z);
		else
			cameraRot.set(x, y, z);
	}

	public void setCameraFOV(float fov) {
		this.cameraFOV = fov;
	}

	public void setCameraName(String name) {
		this.cameraName = name;
	}

	public void setCelLightName(String name) {
		this.celLightName = name;
	}

	public float getCameraFOV() {
		return cameraFOV;
	}

	public String getCameraName() {
		return cameraName;
	}

	public Vector2 getSpriteSize() {
		return new Vector2(width, height);
	}

	private PerspectiveCamera getCamera(ModelInstance modelInstance) {
		PerspectiveCamera camera3d = new PerspectiveCamera(cameraFOV, width, height);

		if (cameraPos == null) {
			Node n = null;

			// SET CAMERA POS FROM MODEL IF EXISTS
			n = modelInstance.getNode(cameraName);

			if (n != null) {
				cameraPos = n.translation;
			} else {
				cameraPos = new Vector3(0, 0, 5);
			}
		}

		if (cameraRot == null) {
			Node n = null;

			// SET CAMERA POS FROM MODEL IF EXISTS
			n = modelInstance.getNode(cameraName);

			if (n != null) {
				float rx = (float) (MathUtils.radiansToDegrees
						* Math.asin(2 * n.rotation.x * n.rotation.y + 2 * n.rotation.z * n.rotation.w));
				float ry = (float) (MathUtils.radiansToDegrees
						* Math.atan2(2 * n.rotation.x * n.rotation.w - 2 * n.rotation.y * n.rotation.z,
								1 - 2 * n.rotation.x * n.rotation.x - 2 * n.rotation.z * n.rotation.z));
				float rz = (float) (Math.atan2(2 * n.rotation.y * n.rotation.w - 2 * n.rotation.x * n.rotation.z,
						1 - 2 * n.rotation.y * n.rotation.y - 2 * n.rotation.z * n.rotation.z));

				setCameraRot(rx, ry, rz);
			} else {
				cameraRot = new Vector3();
			}
		}

		camera3d.position.set(cameraPos);

		camera3d.rotate(cameraRot.x, 1, 0, 0);
		camera3d.rotate(cameraRot.y, 0, 1, 0);
		camera3d.rotate(cameraRot.z, 0, 0, 1);

		camera3d.near = 0.1f;
		camera3d.far = 30;
		camera3d.update();

		return camera3d;
	}

	private final AnimationListener animationListener = new AnimationListener() {

		@Override
		public void onLoop(com.badlogic.gdx.graphics.g3d.utils.AnimationController.AnimationDesc animation) {
		}

		@Override
		public void onEnd(com.badlogic.gdx.graphics.g3d.utils.AnimationController.AnimationDesc animation) {
			if (animationCb != null) {
				ActionCallbackQueue.add(animationCb);
			}
		}
	};

	// public Array getAnimations() {
	// return modelInstance.animations;
	// }

	@Override
	public void startAnimation(String id, Tween.Type repeatType, int count, ActionCallback cb) {
		AnimationDesc fa = fanims.get(id);

		if (fa == null) {
			EngineLogger.error("AnimationDesc not found: " + id);

			return;
		}

		if (currentAnimation != null && currentAnimation.disposeWhenPlayed)
			disposeSource(currentAnimation.source);

		currentAnimation = fa;
		currentSource = sourceCache.get(fa.source);
		animationCb = cb;

		if (currentSource == null || currentSource.refCounter < 1) {
			// If the source is not loaded. Load it.
			loadSource(fa.source);
			EngineAssetManager.getInstance().finishLoading();

			retrieveSource(fa.source);

			currentSource = sourceCache.get(fa.source);

			if (currentSource == null) {
				EngineLogger.error("Could not load AnimationDesc: " + id);
				currentAnimation = null;

				return;
			}
		}

		if (repeatType == Tween.Type.SPRITE_DEFINED) {
			currentAnimationType = currentAnimation.animationType;
			currentCount = currentAnimation.count;
		} else {
			currentCount = count;
			currentAnimationType = repeatType;
		}

		lastAnimationTime = 0;
		float speed = currentAnimation.duration;

		if (currentAnimationType == Tween.Type.REVERSE || currentAnimationType == Tween.Type.REVERSE_REPEAT)
			speed *= -1;
		
		
		ModelCacheEntry cs = (ModelCacheEntry)currentSource;

		if (cs.modelInstance.getAnimation(id) != null) {
			animationCb = cb;
			cs.controller.setAnimation(id, currentCount, speed, animationListener);
			computeBbox();
			return;
		}

		int idx = id.indexOf('.');

		if (idx != -1) {
			String s = id.substring(0, idx);
			String dir = id.substring(idx + 1);

			lookat(dir);

			if (cs.modelInstance.getAnimation(s) != null) {
				cs.controller.setAnimation(s, count, speed, animationListener);

				computeBbox();
				return;
			}
		}

		// ERROR CASE
		EngineLogger.error("Animation NOT FOUND: " + id);

		for (Animation a : cs.modelInstance.animations) {
			EngineLogger.debug(a.id);
		}

		if (cb != null) {
			ActionCallbackQueue.add(cb);
		}

		computeBbox();
	}

	@Override
	public void startAnimation(String id, Tween.Type repeatType, int count, ActionCallback cb, String direction) {
		startAnimation(id, repeatType, count, null);

		if (direction != null && currentAnimation != null)
			lookat(direction);
	}

	@Override
	public void startAnimation(String id, Tween.Type repeatType, int count, ActionCallback cb, Vector2 p0, Vector2 pf) {
		startAnimation(id, repeatType, count, null);

		if (currentAnimation != null) {
			Vector2 tmp = new Vector2(pf);
			float angle = tmp.sub(p0).angle() + 90;
			lookat(angle);
		}
	}

	private void lookat(String dir) {
		EngineLogger.debug("LOOKAT DIRECTION - " + dir);

		if (dir.equals(AnimationDesc.BACK))
			lookat(180);
		else if (dir.equals(AnimationDesc.FRONT))
			lookat(0);
		else if (dir.equals(AnimationDesc.LEFT))
			lookat(270);
		else if (dir.equals(AnimationDesc.RIGHT))
			lookat(90);
		else if (dir.equals(AnimationDesc.BACKLEFT))
			lookat(225);
		else if (dir.equals(AnimationDesc.BACKRIGHT))
			lookat(135);
		else if (dir.equals(AnimationDesc.FRONTLEFT))
			lookat(-45);
		else if (dir.equals(AnimationDesc.FRONTRIGHT))
			lookat(45);
		else
			EngineLogger.error("LOOKAT: Direction not found - " + dir);

	}

	private void lookat(float angle) {
		((ModelCacheEntry)currentSource).modelInstance.transform.setToRotation(Vector3.Y, angle);
		modelRotation = angle;
	}

	public void setSpriteSize(Vector2 size) {
		this.width = (int) size.x;
		this.height = (int) size.y;
	}

	@Override
	public void update(float delta) {
		ModelCacheEntry cs = (ModelCacheEntry)currentSource;
		
		if (cs != null && cs.controller.current != null
				&& cs.controller.current.loopCount != 0) {
			cs.controller.update(delta);
			lastAnimationTime += delta;

			// GENERATE SHADOW MAP
			if (renderShadow)
				genShadowMap();

			if (USE_FBO)
				renderTex();
		}
	}

	@Override
	public float getWidth() {
		return width;
	}

	@Override
	public float getHeight() {
		return height;
	}
	
	private static final Vector3 tmp = new Vector3();

	@Override
	public void draw(SpriteBatch batch, float x, float y, float scale, float rotation, Color tint) {

		x = x - getWidth() / 2 * scale;

		if (USE_FBO) {
			if(tint != null)
				batch.setColor(tint);
			
			batch.draw(tex, x, y, 0, 0, width, height, scale, scale, 0);
			
			if(tint != null)
				batch.setColor(Color.WHITE);
		} else {
			float p0x, p0y, pfx, pfy;

			updateViewport();

			// get screen coords for x and y
			tmp.set(x, y, 0);

			tmp.mul(batch.getTransformMatrix());
			tmp.prj(batch.getProjectionMatrix());
			p0x = VIEWPORT.width * (tmp.x + 1) / 2;
			p0y = VIEWPORT.height * (tmp.y + 1) / 2;

			tmp.set(x + width * scale, y + height * scale, 0);
			tmp.mul(batch.getTransformMatrix());
			tmp.prj(batch.getProjectionMatrix());
			pfx = VIEWPORT.width * (tmp.x + 1) / 2;
			pfy = VIEWPORT.height * (tmp.y + 1) / 2;

			batch.end();

			Gdx.gl20.glViewport((int) (p0x + VIEWPORT.x), (int) (p0y + VIEWPORT.y), (int) (pfx - p0x),
					(int) (pfy - p0y));

			Gdx.gl.glClear(GL20.GL_DEPTH_BUFFER_BIT
					| (Gdx.graphics.getBufferFormat().coverageSampling ? GL20.GL_COVERAGE_BUFFER_BIT_NV : 0));

			drawModel();

			Gdx.gl20.glViewport((int) VIEWPORT.x, (int) VIEWPORT.y, (int) VIEWPORT.width, (int) VIEWPORT.height);
			batch.begin();
		}
	}

	private void createEnvirontment() {
		environment = new Environment();

		// environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.8f,
		// 0.8f, 0.8f, 1f));

		// environment.add(new DirectionalLight().set(1f, 1f, 1f, 1f, -1f,
		// -1f));

		if (celLight == null) {
			Node n = null;

			if (currentSource != null)
				n = ((ModelCacheEntry)currentSource).modelInstance.getNode(celLightName);

			if (n != null) {
				celLight = new PointLight().set(1f, 1f, 1f, n.translation, 1f);
			} else {
				celLight = new PointLight().set(1f, 1f, 1f, 0.5f, 1f, 1f, 1f);
			}
		}

		environment.add(celLight);

		if (renderShadow) {
			shadowEnvironment = new Environment();
			shadowEnvironment.add(shadowLight);
			shadowEnvironment.shadowMap = shadowLight;
		}
	}

	private static void updateViewport() {
		// GET CURRENT VIEWPORT SIZE
		Gdx.gl20.glGetIntegerv(GL20.GL_VIEWPORT, VIEWPORT_RESULTS);
		VIEWPORT.x = VIEWPORT_RESULTS.get(0);
		VIEWPORT.y = VIEWPORT_RESULTS.get(1);
		VIEWPORT.width = VIEWPORT_RESULTS.get(2);
		VIEWPORT.height = VIEWPORT_RESULTS.get(3);
	}

	public static void createBatchs() {
		Config modelConfigShader = new Config(Gdx.files.classpath(VERTEX_SHADER).readString(),
				Gdx.files.classpath(FRAGMENT_SHADER).readString());

		modelConfigShader.numBones = MAX_BONES;
		modelConfigShader.numDirectionalLights = 0;
		modelConfigShader.numPointLights = 0;
		modelConfigShader.numSpotLights = 0;

		modelBatch = new ModelBatch(new DefaultShaderProvider(modelConfigShader));

		shadowBatch = new ModelBatch(new DepthShaderProvider());
		floorBatch = new ModelBatch(new DefaultShaderProvider(Gdx.files.classpath(VERTEX_SHADER),
				Gdx.files.classpath(FLOOR_FRAGMENT_SHADER)));
	}

	private void loadSource(String source) {
		ModelCacheEntry entry = (ModelCacheEntry)sourceCache.get(source);

		if (entry == null) {
			entry = new ModelCacheEntry();
			sourceCache.put(source, entry);
		}

		if (entry.refCounter == 0) {
			EngineAssetManager.getInstance().loadModel3D(source);
		}

		entry.refCounter++;
	}

	private void retrieveSource(String source) {
		ModelCacheEntry entry = (ModelCacheEntry)sourceCache.get(source);

		if (entry == null || entry.refCounter < 1) {
			loadSource(source);
			EngineAssetManager.getInstance().finishLoading();
			entry = (ModelCacheEntry)sourceCache.get(source);
		}

		if (entry.modelInstance == null) {
			Model model3d = EngineAssetManager.getInstance().getModel3D(source);
			entry.modelInstance = new ModelInstance(model3d);
			entry.controller = new AnimationController(entry.modelInstance);
			entry.camera3d = getCamera(entry.modelInstance);
		}
	}

	private void disposeSource(String source) {
		ModelCacheEntry entry = (ModelCacheEntry)sourceCache.get(source);

		if (entry.refCounter == 1) {
			EngineAssetManager.getInstance().disposeModel3D(source);
			entry.modelInstance = null;
		}

		entry.refCounter--;
	}

	@Override
	public void loadAssets() {
		for (AnimationDesc fa : fanims.values()) {
			if (fa.preload)
				loadSource(fa.source);
		}

		if (currentAnimation != null && !currentAnimation.preload) {
			loadSource(currentAnimation.source);
		} else if (currentAnimation == null && initAnimation != null) {
			AnimationDesc fa = fanims.get(initAnimation);

			if (!fa.preload)
				loadSource(fa.source);
		}
	}

	@Override
	public void retrieveAssets() {
		// create STATIC BATCHS if not created yet
		if (modelBatch == null)
			createBatchs();
		
		createEnvirontment();
		
		for (String key : sourceCache.keySet()) {
			if (sourceCache.get(key).refCounter > 0)
				retrieveSource(key);
		}

		if (currentAnimation != null) { // RESTORE FA
			ModelCacheEntry entry = (ModelCacheEntry)sourceCache.get(currentAnimation.source);
			currentSource = entry;

			float speed = currentAnimation.duration;

			if (currentAnimationType == Tween.Type.REVERSE || currentAnimationType == Tween.Type.REVERSE_REPEAT)
				speed *= -1;

			((ModelCacheEntry)currentSource).controller.setAnimation(currentAnimation.id, currentCount, speed, animationListener);

			update(lastAnimationTime);

		} else if (initAnimation != null) {
			startAnimation(initAnimation, Tween.Type.SPRITE_DEFINED, 1, null);

			if (currentAnimation != null)
				lookat(modelRotation);
		}

		if (currentSource != null && renderShadow)
			genShadowMap();

		if (USE_FBO) {
			fb = new FrameBuffer(FRAMEBUFFER_FORMAT, width, height, true);

			tex = new TextureRegion(fb.getColorBufferTexture());
			tex.flip(false, true);

			renderTex();
		}

		computeBbox();
	}

	@Override
	public void dispose() {
		for (String key : sourceCache.keySet()) {
			if(sourceCache.get(key).refCounter > 0)
				EngineAssetManager.getInstance().disposeModel3D(key);
		}

		sourceCache.clear();
		currentSource = null;
		environment = null;
		shadowEnvironment = null;

		if (USE_FBO)
			fb.dispose();
	}

	public static void disposeBatchs() {

		if (modelBatch == null)
			return;

		modelBatch.dispose();
		shadowBatch.dispose();
		floorBatch.dispose();

		modelBatch = shadowBatch = floorBatch = null;
	}

	@Override
	public void write(Json json) {
		super.write(json);
		
		if (SerializationHelper.getInstance().getMode() == Mode.MODEL) {
			float worldScale = EngineAssetManager.getInstance().getScale();
			json.writeValue("width", width / worldScale);
			json.writeValue("height", height / worldScale);
			json.writeValue("cameraPos", cameraPos, cameraPos == null ? null : Vector3.class);
			json.writeValue("cameraRot", cameraRot, cameraRot == null ? null : Vector3.class);
			json.writeValue("cameraName", cameraName, cameraName == null ? null : String.class);
			json.writeValue("cameraFOV", cameraFOV);
			json.writeValue("renderShadow", renderShadow);
		} else {


			json.writeValue("animationCb", ActionCallbackSerialization.find(animationCb));

			json.writeValue("currentCount", currentCount);
			json.writeValue("currentAnimationType", currentAnimationType);
			json.writeValue("lastAnimationTime", lastAnimationTime);

			// TODO: SAVE AND RESTORE CURRENT DIRECTION
			// TODO: shadowlight, cel light
		}

		json.writeValue("modelRotation", modelRotation);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void read(Json json, JsonValue jsonData) {
		super.read(json, jsonData);
		
		if (SerializationHelper.getInstance().getMode() == Mode.MODEL) {
			fanims = json.readValue("fanims", HashMap.class, AnimationDesc.class, jsonData);
			
			float worldScale = EngineAssetManager.getInstance().getScale();
			width = (int) (json.readValue("width", Integer.class, jsonData) * worldScale);
			height = (int) (json.readValue("height", Integer.class, jsonData) * worldScale);
			cameraPos = json.readValue("cameraPos", Vector3.class, jsonData);
			cameraRot = json.readValue("cameraRot", Vector3.class, jsonData);
			cameraName = json.readValue("cameraName", String.class, jsonData);
			cameraFOV = json.readValue("cameraFOV", Float.class, jsonData);
			renderShadow = json.readValue("renderShadow", Boolean.class, jsonData);
		} else {

			animationCb = ActionCallbackSerialization.find(json.readValue("animationCb", String.class, jsonData));

			currentCount = json.readValue("currentCount", Integer.class, jsonData);
			currentAnimationType = json.readValue("currentAnimationType", Tween.Type.class, jsonData);
			lastAnimationTime = json.readValue("lastAnimationTime", Float.class, jsonData);
		}

		modelRotation = json.readValue("modelRotation", Float.class, jsonData);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy