/******************************************************************************* * 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.engineeditor.scneditor; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.MessageFormat; import java.util.List; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; import com.badlogic.gdx.graphics.glutils.HdpiUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.ui.Widget; import com.badlogic.gdx.scenes.scene2d.utils.Drawable; import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack; import com.badlogic.gdx.scenes.scene2d.utils.TiledDrawable; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Timer; import com.badlogic.gdx.utils.Timer.Task; import com.bladecoder.engine.anim.AnimationDesc; import com.bladecoder.engine.anim.Tween; import com.bladecoder.engine.assets.EngineAssetManager; import com.bladecoder.engine.model.AnimationRenderer; import com.bladecoder.engine.model.BaseActor; import com.bladecoder.engine.model.InteractiveActor; import com.bladecoder.engine.model.Scene; import com.bladecoder.engine.model.SceneLayer; import com.bladecoder.engine.model.SpriteActor; import com.bladecoder.engine.model.World; import com.bladecoder.engine.spine.SpineRenderer; import com.bladecoder.engine.util.RectangleRenderer; import com.bladecoder.engineeditor.Ctx; import com.bladecoder.engineeditor.common.EditorLogger; import com.bladecoder.engineeditor.common.Message; import com.bladecoder.engineeditor.model.Project; public class ScnWidget extends Widget { private static final Color BLACK_TRANSPARENT = new Color(0f, 0f, 0f, 0.5f); // TMPs to avoid GC calls private final Vector3 tmpV3 = new Vector3(); private final Vector2 tmpV2 = new Vector2(); private final Vector2 tmp2V2 = new Vector2(); private final SpriteBatch sceneBatch = new SpriteBatch(); private final CanvasDrawer drawer = new CanvasDrawer(); private final AnimationDrawer faRenderer = new AnimationDrawer(); private final ScnWidgetInputListener inputListner = new ScnWidgetInputListener(this); private final Rectangle bounds = new Rectangle(); private final Rectangle scissors = new Rectangle(); private Scene scn; private BaseActor selectedActor = null; private boolean inScene = false; private boolean animation = true; private static final int[] zoomLevels = { 5, 10, 16, 25, 33, 50, 66, 100, 150, 200, 300, 400, 600, 800, 1000 }; private int zoomLevel = 100; private BitmapFont bigFont; private BitmapFont defaultFont; private TiledDrawable tile; private Drawable background; private boolean loading = false; private boolean loadingError = false; private boolean showWalkZone; private final GlyphLayout textLayout = new GlyphLayout(); private final OrthographicCamera camera = new OrthographicCamera(); /** * The NOTIFY_PROJECT_LOADED listener is called from other thread. This flag * is to recreate the scene in the OpenGL thread. */ private boolean projectLoadedFlag = false; public ScnWidget(Skin skin) { bigFont = skin.get("big-font", BitmapFont.class); defaultFont = skin.get("default-font", BitmapFont.class); setSize(150, 150); tile = new TiledDrawable(Ctx.assetManager.getIcon("transparent-light")); background = skin.getDrawable("background"); faRenderer.setViewport(getWidth(), getHeight()); setLayoutEnabled(true); addListener(inputListner); Ctx.project.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent e) { EditorLogger.debug("ScnWidget Listener: " + e.getPropertyName()); if (e.getPropertyName().equals(Project.NOTIFY_SCENE_SELECTED)) { if (!projectLoadedFlag) setSelectedScene(Ctx.project.getSelectedScene()); } else if (e.getPropertyName().equals(Project.NOTIFY_ACTOR_SELECTED)) { if (!projectLoadedFlag) setSelectedActor(Ctx.project.getSelectedActor()); } else if (e.getPropertyName().equals(Project.NOTIFY_ANIM_SELECTED)) { if (!projectLoadedFlag && Ctx.project.getSelectedFA() != null) setSelectedFA(Ctx.project.getSelectedFA()); } else if (e.getPropertyName().equals(Project.NOTIFY_PROJECT_LOADED)) { projectLoadedFlag = true; } else if (e.getPropertyName().equals("scene")) { setSelectedScene(Ctx.project.getSelectedScene()); setSelectedActor(Ctx.project.getSelectedActor()); } else if (e.getPropertyName().equals("init_animation")) { if (!inScene) setSelectedFA(null); } } }); showWalkZone = Boolean.parseBoolean(Ctx.project.getEditorConfig().getProperty("view.showWalkZone", "false")); inScene = Boolean.parseBoolean(Ctx.project.getEditorConfig().getProperty("view.inScene", "false")); animation = Boolean.parseBoolean(Ctx.project.getEditorConfig().getProperty("view.animation", "true")); } public OrthographicCamera getCamera() { return camera; } @Override public void act(float delta) { if (projectLoadedFlag) { projectLoadedFlag = false; if (scn != null) { scn.dispose(); scn = null; } setSelectedScene(Ctx.project.getSelectedScene()); setSelectedActor(Ctx.project.getSelectedActor()); setSelectedFA(Ctx.project.getSelectedFA()); } if (scn != null && !loading && !loadingError) { if (!inScene) faRenderer.update(delta); // scn.update(delta); for (SceneLayer layer : scn.getLayers()) layer.update(); if (animation) { for (BaseActor a : scn.getActors().values()) { boolean v = a.isVisible(); a.setVisible(true); a.update(delta); a.setVisible(v); } } handleKeyPositioning(); } } private void handleKeyPositioning() { if (getStage() == null || getStage().getKeyboardFocus() != this) return; if (Gdx.input.isKeyPressed(Keys.UP) || Gdx.input.isKeyPressed(Keys.DOWN) || Gdx.input.isKeyPressed(Keys.LEFT) || Gdx.input.isKeyPressed(Keys.RIGHT)) { BaseActor selActor = getSelectedActor(); if (Gdx.input.isKeyPressed(Keys.UP)) // p.translate(0, 1); selActor.setPosition(selActor.getX(), selActor.getY() + 1); else if (Gdx.input.isKeyPressed(Keys.DOWN)) // p.translate(0, -1); selActor.setPosition(selActor.getX(), selActor.getY() - 1); else if (Gdx.input.isKeyPressed(Keys.LEFT)) // p.translate(-1, 0); selActor.setPosition(selActor.getX() - 1, selActor.getY()); else if (Gdx.input.isKeyPressed(Keys.RIGHT)) // p.translate(1, 0); selActor.setPosition(selActor.getX() + 1, selActor.getY()); } } @Override public void draw(Batch batch, float parentAlpha) { validate(); Color tmp = batch.getColor(); batch.setColor(Color.WHITE); if (scn != null && !loading && !loadingError) { // BACKGROUND batch.disableBlending(); tile.draw(batch, getX(), getY(), getWidth(), getHeight()); batch.enableBlending(); Vector3 v = new Vector3(getX(), getY(), 0); v = v.prj(batch.getTransformMatrix()); batch.end(); HdpiUtils.glViewport((int) v.x, (int) v.y, (int) getWidth(), (int) (getHeight())); getStage().calculateScissors(bounds, scissors); if (ScissorStack.pushScissors(scissors)) { // WORLD CAMERA sceneBatch.setProjectionMatrix(camera.combined); sceneBatch.begin(); Array<AtlasRegion> scnBackground = scn.getBackground(); if (scnBackground != null) { sceneBatch.disableBlending(); float x = 0; for (AtlasRegion tile : scnBackground) { sceneBatch.draw(tile, x, 0f); x += tile.getRegionWidth(); } sceneBatch.enableBlending(); } // draw layers from bottom to top List<SceneLayer> layers = scn.getLayers(); for (int i = layers.size() - 1; i >= 0; i--) { SceneLayer layer = layers.get(i); if (!layer.isVisible()) continue; List<InteractiveActor> actors = layer.getActors(); for (InteractiveActor a : actors) { if (a instanceof SpriteActor) { boolean visibility = a.isVisible(); a.setVisible(true); ((SpriteActor) a).draw(sceneBatch); a.setVisible(visibility); } } } sceneBatch.end(); ScissorStack.popScissors(); } drawer.drawBGBounds(); if (showWalkZone && scn.getPolygonalNavGraph() != null) { drawer.drawBBoxWalkZone(scn, false); drawer.drawPolygonVertices(scn.getPolygonalNavGraph().getWalkZone(), Color.GREEN); } drawer.drawBBoxActors(scn); if (selectedActor != null) { drawer.drawSelectedActor(selectedActor); } getStage().getViewport().apply(); // SCREEN CAMERA batch.begin(); drawFakeDepthMarkers((SpriteBatch) batch); if (!inScene) { faRenderer.draw((SpriteBatch) batch); } // DRAW COORDS Vector2 coords = new Vector2(Gdx.input.getX(), Gdx.input.getY()); screenToWorldCoords(coords); String str = MessageFormat.format("({0}, {1})", (int) coords.x, (int) coords.y); textLayout.setText(defaultFont, str); RectangleRenderer.draw((SpriteBatch) batch, 0f, getY() + getHeight() - textLayout.height - 15, textLayout.width + 10, textLayout.height + 10, BLACK_TRANSPARENT); defaultFont.draw(batch, textLayout, 5, getHeight() + getY() - 10); batch.setColor(tmp); } else { background.draw(batch, getX(), getY(), getWidth(), getHeight()); String s; if (loading) { s = "LOADING..."; Timer.post(new Task() { @Override public void run() { loading = false; try { EngineAssetManager.getInstance().finishLoading(); scn.retrieveAssets(); // disable Spine events for (BaseActor a : scn.getActors().values()) { if (a instanceof SpriteActor && ((SpriteActor) a).getRenderer() instanceof SpineRenderer) { ((SpineRenderer) ((SpriteActor) a).getRenderer()).enableEvents(false); } } drawer.setCamera(camera); invalidate(); } catch (Exception e) { Message.showMsg(getStage(), "Could not load assets for scene", 4); EditorLogger.printStackTrace(e); loadingError = true; loading = false; } } }); } else if (loadingError) { s = "ERROR IN SCENE DATA. CANNOT DISPLAY SCENE"; } else if (Ctx.project.getProjectDir() == null) { s = "CREATE OR LOAD A PROJECT"; } else { s = "THERE ARE NO SCENES IN THIS CHAPTER YET"; } textLayout.setText(bigFont, s); bigFont.draw(batch, textLayout, (getWidth() - textLayout.width) / 2, getHeight() / 2 + bigFont.getLineHeight() * 3); } } private void drawFakeDepthMarkers(SpriteBatch batch) { int margin = 5; Vector2 d = scn.getDepthVector(); if (d == null) return; tmp2V2.x = 0; tmp2V2.y = d.y; worldToScreenCoords(tmp2V2); String s = "100%"; textLayout.setText(defaultFont, s); float posx = tmp2V2.x - textLayout.width - 20; RectangleRenderer.draw((SpriteBatch) batch, posx, tmp2V2.y, textLayout.width + margin * 2, textLayout.height + margin * 2, Color.BLACK); RectangleRenderer.draw((SpriteBatch) batch, tmp2V2.x - 20, tmp2V2.y, 20, 2, Color.BLACK); defaultFont.draw(batch, textLayout, posx + margin, tmp2V2.y + textLayout.height + margin); tmp2V2.x = 0; tmp2V2.y = d.x; worldToScreenCoords(tmp2V2); s = "0%"; textLayout.setText(defaultFont, s); posx = tmp2V2.x - textLayout.width - 20; RectangleRenderer.draw((SpriteBatch) batch, posx, tmp2V2.y, textLayout.width + margin * 2, textLayout.height + margin * 2, Color.BLACK); RectangleRenderer.draw((SpriteBatch) batch, tmp2V2.x - 20, tmp2V2.y, 20, 2, Color.BLACK); defaultFont.draw(batch, textLayout, posx + margin, tmp2V2.y + textLayout.height + margin); } public void setInSceneSprites(boolean v) { inScene = v; Ctx.project.getEditorConfig().setProperty("view.inScene", Boolean.toString(inScene)); if (!inScene) setSelectedFA(null); } public boolean getInSceneSprites() { return inScene; } public void setAnimation(boolean v) { animation = v; Ctx.project.getEditorConfig().setProperty("view.animation", Boolean.toString(animation)); } public boolean getAnimation() { return animation; } public void setAnimationRenderer(BaseActor a, AnimationDesc fa) { try { faRenderer.setActor(a); faRenderer.setAnimation(fa); } catch (Exception e) { Message.showMsg(getStage(), "Could not retrieve assets for sprite: " + fa.id, 4); EditorLogger.printStackTrace(e); faRenderer.setAnimation(null); } } public boolean getShowWalkZone() { return showWalkZone; } public void setShowWalkZone(boolean v) { showWalkZone = v; Ctx.project.getEditorConfig().setProperty("view.showWalkZone", Boolean.toString(showWalkZone)); } @Override public void layout() { // EditorLogger.debug("LAYOUT SIZE CHANGED - X: " + getX() + " Y: " // + getY() + " Width: " + getWidth() + " Height: " + getHeight()); // EditorLogger.debug("Last Point coords - X: " + (getX() + getWidth()) // + " Y: " + (getY() + getHeight())); localToScreenCoords(tmpV2.set(getX() + getWidth(), getY() + getHeight())); // EditorLogger.debug("Screen Last Point coords: " + tmpV2); faRenderer.setViewport(getWidth(), getHeight()); bounds.set(getX(), getY(), getWidth(), getHeight()); // SETS WORLD CAMERA if (scn != null) { float aspect = getWidth() / getHeight(); float wWidth = World.getInstance().getWidth(); float wHeight = World.getInstance().getHeight(); float aspectWorld = wWidth / wHeight; if (aspectWorld > aspect) { wHeight = wWidth / aspect; } else { wWidth = wHeight * aspect; } zoomLevel = 100; camera.setToOrtho(false, wWidth, wHeight); camera.zoom = 1f; camera.position.set(World.getInstance().getWidth() / 2, World.getInstance().getHeight() / 2, 0); camera.update(); zoom(+1); } } public void zoom(int amount) { if (zoomLevel == zoomLevels[0] && amount < 0) { zoomLevel = zoomLevels[1]; } else if (zoomLevel == zoomLevels[zoomLevels.length - 1] && amount > 0) { zoomLevel = zoomLevels[zoomLevels.length - 2]; } else { for (int i = 1; i < zoomLevels.length - 1; i++) { if (zoomLevels[i] == zoomLevel) { zoomLevel = amount > 0 ? zoomLevels[i - 1] : zoomLevels[i + 1]; break; } } } if (scn != null) { camera.zoom = 100f / zoomLevel; camera.update(); } } public void translate(Vector2 delta) { // EditorLogger.debug("TRANSLATING - X: " + delta.x + " Y: " + delta.y); if (scn != null) { camera.translate(-delta.x, -delta.y, 0); camera.update(); } } public void localToScreenCoords(Vector2 coords) { localToStageCoordinates(coords); getStage().stageToScreenCoordinates(coords); } public void localToWorldCoords(Vector2 coords) { localToStageCoordinates(coords); getStage().stageToScreenCoordinates(coords); tmpV3.set(coords.x, coords.y, 0); camera.unproject(tmpV3, getX(), getY(), getWidth(), getHeight()); coords.set(tmpV3.x, tmpV3.y); } public void screenToWorldCoords(Vector2 coords) { tmpV2.set(0, 0); localToStageCoordinates(tmpV2); // getStage().stageToScreenCoordinates(tmpV2); tmpV3.set(coords.x, coords.y, 0); camera.unproject(tmpV3, tmpV2.x, tmpV2.y, getWidth(), getHeight()); coords.set(tmpV3.x, tmpV3.y); } public void worldToScreenCoords(Vector2 coords) { tmpV2.set(getX(), getY()); localToStageCoordinates(tmpV2); tmpV3.set(coords.x, coords.y, 0); camera.project(tmpV3, tmpV2.x, tmpV2.y, getWidth(), getHeight()); coords.set(tmpV3.x, tmpV3.y); stageToLocalCoordinates(coords); } public Scene getScene() { return scn; } public BaseActor getSelectedActor() { return selectedActor; } public void setSelectedScene(Scene s) { if (scn != null) { scn.dispose(); faRenderer.dispose(); scn = null; EngineAssetManager.getInstance().clear(); } loadingError = false; setSelectedActor(null); if (s != null) { scn = s; scn.loadAssets(); loading = true; } // SETS WORLD CAMERA if (scn != null) { float aspect = getWidth() / getHeight(); float wWidth = World.getInstance().getWidth(); float wHeight = World.getInstance().getHeight(); float aspectWorld = wWidth / wHeight; if (aspectWorld > aspect) { wHeight = wWidth / aspect; } else { wWidth = wHeight * aspect; } zoomLevel = 100; camera.setToOrtho(false, wWidth, wHeight); camera.zoom = 1f; camera.update(); // translate(new Vector2((-getWidth() + wWidth ) / 2 * // camera.zoom, // (-getHeight() + wHeight) / 2 * camera.zoom)); translate(new Vector2(0, (-getHeight() + wHeight) / 2)); } } public void setSelectedActor(BaseActor actor) { BaseActor a = null; if (scn != null && actor != null) { a = actor; } selectedActor = a; // faRenderer.setActor(a); setAnimationRenderer(null, null); } public void setSelectedFA(String selFA) { if (selectedActor instanceof SpriteActor && ((SpriteActor) selectedActor).getRenderer() instanceof AnimationRenderer) { AnimationRenderer s = (AnimationRenderer) ((SpriteActor) selectedActor).getRenderer(); if (selFA == null || (s.getAnimations().get(selFA) == null && s.getAnimations().get(AnimationDesc.getFlipId(selFA)) == null)) { selFA = s.getInitAnimation(); } if (selFA != null && (s.getAnimations().get(selFA) != null || s.getAnimations().get(AnimationDesc.getFlipId(selFA)) != null)) { setAnimationRenderer(selectedActor, s.getAnimations().get(selFA)); String animInScene = selFA; if (!inScene && s.getInitAnimation() != null) animInScene = s.getInitAnimation(); try { ((SpriteActor) selectedActor).startAnimation(animInScene, Tween.Type.REPEAT, Tween.INFINITY, null); } catch (Exception e) { setAnimationRenderer(selectedActor, null); s.getAnimations().remove(selFA); } } else { setAnimationRenderer(selectedActor, null); } } else { setAnimationRenderer(selectedActor, null); } } public void dispose() { if (scn != null) { scn.dispose(); scn = null; } faRenderer.dispose(); } }