/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.stage; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.PointF; import android.os.SystemClock; import android.util.Log; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.FPSLogger; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; 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.glutils.ShapeRenderer; import com.badlogic.gdx.math.Polygon; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.utils.Scaling; import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.badlogic.gdx.utils.viewport.ScalingViewport; import com.badlogic.gdx.utils.viewport.Viewport; import com.google.common.collect.Multimap; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.camera.CameraManager; import org.catrobat.catroid.common.BroadcastSequenceMap; import org.catrobat.catroid.common.BroadcastWaitSequenceMap; import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.common.LookData; import org.catrobat.catroid.common.ScreenModes; import org.catrobat.catroid.common.ScreenValues; import org.catrobat.catroid.content.BackgroundWaitHandler; import org.catrobat.catroid.content.BroadcastHandler; import org.catrobat.catroid.content.Look; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Scene; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.facedetection.FaceDetectionHandler; import org.catrobat.catroid.formulaeditor.DataContainer; import org.catrobat.catroid.io.SoundManager; import org.catrobat.catroid.physics.PhysicsDebugSettings; import org.catrobat.catroid.physics.PhysicsLook; import org.catrobat.catroid.physics.PhysicsObject; import org.catrobat.catroid.physics.PhysicsWorld; import org.catrobat.catroid.physics.shapebuilder.PhysicsShapeBuilder; import org.catrobat.catroid.ui.dialogs.StageDialog; import org.catrobat.catroid.utils.FlashUtil; import org.catrobat.catroid.utils.TouchUtil; import org.catrobat.catroid.utils.Utils; import org.catrobat.catroid.utils.VibratorUtil; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; public class StageListener implements ApplicationListener { private static final String TAG = StageListener.class.getSimpleName(); private static final int AXIS_WIDTH = 4; private static final float DELTA_ACTIONS_DIVIDER_MAXIMUM = 50f; private static final int ACTIONS_COMPUTATION_TIME_MAXIMUM = 8; private static final boolean DEBUG = false; private static final java.lang.String SEQUENCE = "Sequence("; public static final String BROADCAST_NOTIFY = ", BroadcastNotify)"; // needed for UiTests - is disabled to fix crashes with EMMA coverage // CHECKSTYLE DISABLE StaticVariableNameCheck FOR 1 LINES private static boolean DYNAMIC_SAMPLING_RATE_FOR_ACTIONS = true; private float deltaActionTimeDivisor = 10f; public static final String SCREENSHOT_AUTOMATIC_FILE_NAME = "automatic_screenshot" + Constants.IMAGE_STANDARD_EXTENSION; public static final String SCREENSHOT_MANUAL_FILE_NAME = "manual_screenshot" + Constants.IMAGE_STANDARD_EXTENSION; private FPSLogger fpsLogger; private Stage stage; private boolean paused = false; private boolean finished = false; private boolean reloadProject = false; public boolean firstFrameDrawn = false; private static boolean checkIfAutomaticScreenshotShouldBeTaken = true; private boolean makeAutomaticScreenshot = false; private boolean makeScreenshot = false; private String pathForSceneScreenshot; private int screenshotWidth; private int screenshotHeight; private int screenshotX; private int screenshotY; private byte[] screenshot = null; // in first frame, framebuffer could be empty and screenshot // would be white private boolean skipFirstFrameForAutomaticScreenshot; private Project project; private Scene scene; private PhysicsWorld physicsWorld; private OrthographicCamera camera; private Batch batch = null; private BitmapFont font; private Passepartout passepartout; private Viewport viewPort; public ShapeRenderer shapeRenderer; private PenActor penActor; private List<Sprite> sprites; private HashSet<Sprite> clonedSprites; private float virtualWidthHalf; private float virtualHeightHalf; private float virtualWidth; private float virtualHeight; private Texture axes; private boolean makeTestPixels = false; private byte[] testPixels; private int testX = 0; private int testY = 0; private int testWidth = 0; private int testHeight = 0; private StageDialog stageDialog; public int maximizeViewPortX = 0; public int maximizeViewPortY = 0; public int maximizeViewPortHeight = 0; public int maximizeViewPortWidth = 0; public boolean axesOn = false; private byte[] thumbnail; private Map<String, StageBackup> stageBackupMap = new HashMap(); private InputListener inputListener = null; private ShapeRenderer collisionPolygonDebugRenderer; private boolean drawDebugCollisionPolygons = false; private Map<Sprite, ShowBubbleActor> bubbleActorMap = new HashMap<>(); StageListener() { } @Override public void create() { font = new BitmapFont(); font.setColor(1f, 0f, 0.05f, 1f); font.getData().setScale(1.2f); deltaActionTimeDivisor = 10f; shapeRenderer = new ShapeRenderer(); project = ProjectManager.getInstance().getCurrentProject(); scene = ProjectManager.getInstance().getSceneToPlay(); pathForSceneScreenshot = Utils.buildScenePath(project.getName(), scene.getName()) + "/"; virtualWidth = project.getXmlHeader().virtualScreenWidth; virtualHeight = project.getXmlHeader().virtualScreenHeight; virtualWidthHalf = virtualWidth / 2; virtualHeightHalf = virtualHeight / 2; camera = new OrthographicCamera(); viewPort = new ExtendViewport(virtualWidth, virtualHeight, camera); if (batch == null) { batch = new SpriteBatch(); } else { batch = new SpriteBatch(1000, batch.getShader()); } stage = new Stage(viewPort, batch); initScreenMode(); initStageInputListener(); physicsWorld = scene.resetPhysicsWorld(); clonedSprites = new HashSet<>(); sprites = new ArrayList<>(scene.getSpriteList()); boolean addPenActor = true; for (Sprite sprite : sprites) { sprite.resetSprite(); sprite.look.createBrightnessContrastHueShader(); stage.addActor(sprite.look); if (addPenActor) { penActor = new PenActor(); stage.addActor(penActor); addPenActor = false; } } passepartout = new Passepartout(ScreenValues.SCREEN_WIDTH, ScreenValues.SCREEN_HEIGHT, maximizeViewPortWidth, maximizeViewPortHeight, virtualWidth, virtualHeight); stage.addActor(passepartout); if (DEBUG) { OrthoCamController camController = new OrthoCamController(camera); InputMultiplexer multiplexer = new InputMultiplexer(); multiplexer.addProcessor(camController); multiplexer.addProcessor(stage); Gdx.input.setInputProcessor(multiplexer); fpsLogger = new FPSLogger(); } else { Gdx.input.setInputProcessor(stage); } axes = new Texture(Gdx.files.internal("stage/red_pixel.bmp")); skipFirstFrameForAutomaticScreenshot = true; if (checkIfAutomaticScreenshotShouldBeTaken) { makeAutomaticScreenshot = project.manualScreenshotExists(SCREENSHOT_MANUAL_FILE_NAME) || scene .screenshotExists(SCREENSHOT_AUTOMATIC_FILE_NAME) || scene.screenshotExists(SCREENSHOT_MANUAL_FILE_NAME); } if (drawDebugCollisionPolygons) { collisionPolygonDebugRenderer.setProjectionMatrix(camera.combined); collisionPolygonDebugRenderer.setAutoShapeType(true); collisionPolygonDebugRenderer.setColor(Color.MAGENTA); } } public void cloneSpriteAndAddToStage(Sprite cloneMe) { Sprite copy = cloneMe.cloneForCloneBrick(); copy.look.createBrightnessContrastHueShader(); stage.addActor(copy.look); sprites.add(copy); clonedSprites.add(copy); Map<String, List<String>> scriptActions = new HashMap<>(); copy.createStartScriptActionSequenceAndPutToMap(scriptActions); precomputeActionsForBroadcastEvents(scriptActions); if (!copy.getLookDataList().isEmpty()) { copy.look.setLookData(copy.getLookDataList().get(0)); } copy.createWhenClonedAction(); } public void removeClonedSpriteFromStage(Sprite sprite) { if (!sprite.isClone) { return; } Scene currentScene = ProjectManager.getInstance().getSceneToPlay(); DataContainer userVariables = currentScene.getDataContainer(); userVariables.removeVariableListForSprite(sprite); BroadcastHandler.getScriptSpriteMap().remove(sprite); sprite.look.setLookVisible(false); sprite.look.remove(); sprites.remove(sprite); clonedSprites.remove(sprite); } private void disposeClonedSprites() { for (Scene scene : ProjectManager.getInstance().getCurrentProject().getSceneList()) { scene.removeAllClones(); } } private void initStageInputListener() { if (inputListener == null) { inputListener = new InputListener() { @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { TouchUtil.touchDown(event.getStageX(), event.getStageY(), pointer); return true; } @Override public void touchUp(InputEvent event, float x, float y, int pointer, int button) { TouchUtil.touchUp(pointer); } @Override public void touchDragged(InputEvent event, float x, float y, int pointer) { TouchUtil.updatePosition(event.getStageX(), event.getStageY(), pointer); } }; } stage.addListener(inputListener); collisionPolygonDebugRenderer = new ShapeRenderer(); } void menuResume() { if (reloadProject) { return; } paused = false; FaceDetectionHandler.resumeFaceDetection(); SoundManager.getInstance().resume(); } void menuPause() { if (finished || reloadProject) { return; } try { paused = true; SoundManager.getInstance().pause(); } catch (Exception exception) { Log.e(TAG, "Pausing menu failed!", exception); } } public void transitionToScene(String sceneName) { if (ProjectManager.getInstance().getCurrentProject().getSceneByName(sceneName) == null) { return; } stageBackupMap.put(scene.getName(), saveToBackup()); pause(); scene = ProjectManager.getInstance().getCurrentProject().getSceneByName(sceneName); ProjectManager.getInstance().setSceneToPlay(scene); if (stageBackupMap.containsKey(scene.getName())) { restoreFromBackup(stageBackupMap.get(scene.getName())); } if (scene.firstStart) { create(); } else { resume(); } Gdx.input.setInputProcessor(stage); } public void startScene(String sceneName) { Scene sceneToStart = ProjectManager.getInstance().getCurrentProject().getSceneByName(sceneName); if (sceneToStart == null) { return; } transitionToScene(sceneName); BroadcastSequenceMap.clear(sceneName); BroadcastWaitSequenceMap.clear(sceneName); BroadcastWaitSequenceMap.clearCurrentBroadcastEvent(); SoundManager.getInstance().clear(); stageBackupMap.remove(sceneName); scene.firstStart = true; create(); } public void reloadProject(StageDialog stageDialog) { if (reloadProject) { return; } this.stageDialog = stageDialog; if (!ProjectManager.getInstance().getStartScene().getName().equals(scene.getName())) { transitionToScene(ProjectManager.getInstance().getStartScene().getName()); } stageBackupMap.clear(); for (Scene scene : ProjectManager.getInstance().getCurrentProject().getSceneList()) { scene.firstStart = true; scene.getDataContainer().resetAllDataObjects(); } FlashUtil.reset(); VibratorUtil.reset(); TouchUtil.reset(); BackgroundWaitHandler.reset(); reloadProject = true; } @Override public void resume() { if (!paused) { FaceDetectionHandler.resumeFaceDetection(); SoundManager.getInstance().resume(); } for (Sprite sprite : sprites) { sprite.look.refreshTextures(); } } @Override public void pause() { if (finished) { return; } if (!paused) { FaceDetectionHandler.pauseFaceDetection(); SoundManager.getInstance().pause(); } } public void finish() { SoundManager.getInstance().clear(); if (thumbnail != null && !makeAutomaticScreenshot) { saveScreenshot(thumbnail, SCREENSHOT_AUTOMATIC_FILE_NAME); } PhysicsShapeBuilder.getInstance().reset(); CameraManager.getInstance().setToDefaultCamera(); finished = true; } @Override public void render() { if (CameraManager.getInstance().getState() == CameraManager.CameraState.previewRunning) { Gdx.gl20.glClearColor(0f, 0f, 0f, 0f); } else { Gdx.gl20.glClearColor(1f, 1f, 1f, 0f); } Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT); if (reloadProject) { int spriteSize = sprites.size(); stage.clear(); SoundManager.getInstance().clear(); physicsWorld = scene.resetPhysicsWorld(); Sprite sprite; boolean addPenActor = true; for (int i = 0; i < spriteSize; i++) { sprite = sprites.get(i); sprite.resetSprite(); sprite.look.createBrightnessContrastHueShader(); stage.addActor(sprite.look); if (addPenActor) { penActor = new PenActor(); stage.addActor(penActor); addPenActor = false; } } stage.addActor(passepartout); initStageInputListener(); paused = true; scene.firstStart = true; reloadProject = false; if (stageDialog != null) { synchronized (stageDialog) { stageDialog.notify(); } } } batch.setProjectionMatrix(camera.combined); shapeRenderer.setProjectionMatrix(camera.combined); if (scene.firstStart) { int spriteSize = sprites.size(); Map<String, List<String>> scriptActions = new HashMap<>(); for (int currentSprite = 0; currentSprite < spriteSize; currentSprite++) { Sprite sprite = sprites.get(currentSprite); sprite.createStartScriptActionSequenceAndPutToMap(scriptActions); if (!sprite.getLookDataList().isEmpty()) { sprite.look.setLookData(sprite.getLookDataList().get(0)); } } if (scriptActions.get(Constants.BROADCAST_SCRIPT) != null && !scriptActions.get(Constants.BROADCAST_SCRIPT).isEmpty()) { List<String> broadcastWaitNotifyActions = reconstructNotifyActions(scriptActions); Map<String, List<String>> notifyMap = new HashMap<>(); notifyMap.put(Constants.BROADCAST_NOTIFY_ACTION, broadcastWaitNotifyActions); scriptActions.putAll(notifyMap); } precomputeActionsForBroadcastEvents(scriptActions); scene.firstStart = false; } if (!paused) { float deltaTime = Gdx.graphics.getDeltaTime(); /* * Necessary for UiTests, when EMMA - code coverage is enabled. * * Without setting DYNAMIC_SAMPLING_RATE_FOR_ACTIONS to false(via reflection), before * the UiTest enters the stage, random segmentation faults(triggered by EMMA) will occur. * * Can be removed, when EMMA is replaced by an other code coverage tool, or when a * future EMMA - update will fix the bugs. */ if (!DYNAMIC_SAMPLING_RATE_FOR_ACTIONS) { physicsWorld.step(deltaTime); stage.act(deltaTime); } else { float optimizedDeltaTime = deltaTime / deltaActionTimeDivisor; long timeBeforeActionsUpdate = SystemClock.uptimeMillis(); while (deltaTime > 0f) { physicsWorld.step(optimizedDeltaTime); stage.act(optimizedDeltaTime); deltaTime -= optimizedDeltaTime; } long executionTimeOfActionsUpdate = SystemClock.uptimeMillis() - timeBeforeActionsUpdate; if (executionTimeOfActionsUpdate <= ACTIONS_COMPUTATION_TIME_MAXIMUM) { deltaActionTimeDivisor += 1f; deltaActionTimeDivisor = Math.min(DELTA_ACTIONS_DIVIDER_MAXIMUM, deltaActionTimeDivisor); } else { deltaActionTimeDivisor -= 1f; deltaActionTimeDivisor = Math.max(1f, deltaActionTimeDivisor); } } } if (!finished) { stage.draw(); firstFrameDrawn = true; } if (makeAutomaticScreenshot) { if (skipFirstFrameForAutomaticScreenshot) { skipFirstFrameForAutomaticScreenshot = false; } else { thumbnail = ScreenUtils.getFrameBufferPixels(screenshotX, screenshotY, screenshotWidth, screenshotHeight, true); makeAutomaticScreenshot = false; } } if (makeScreenshot) { screenshot = ScreenUtils.getFrameBufferPixels(screenshotX, screenshotY, screenshotWidth, screenshotHeight, true); makeScreenshot = false; } if (axesOn && !finished) { drawAxes(); } if (PhysicsDebugSettings.Render.RENDER_PHYSIC_OBJECT_LABELING) { printPhysicsLabelOnScreen(); } if (PhysicsDebugSettings.Render.RENDER_COLLISION_FRAMES && !finished) { physicsWorld.render(camera.combined); } if (DEBUG) { fpsLogger.log(); } if (makeTestPixels) { testPixels = ScreenUtils.getFrameBufferPixels(testX, testY, testWidth, testHeight, false); makeTestPixels = false; } if (drawDebugCollisionPolygons) { drawDebugCollisionPolygons(); } } private List<String> reconstructNotifyActions(Map<String, List<String>> actions) { List<String> broadcastWaitNotifyActions = new ArrayList<>(); for (String actionString : actions.get(Constants.BROADCAST_SCRIPT)) { String broadcastNotifyString = SEQUENCE + actionString.substring(0, actionString.indexOf(Constants.ACTION_SPRITE_SEPARATOR)) + BROADCAST_NOTIFY + actionString.substring(actionString.indexOf(Constants.ACTION_SPRITE_SEPARATOR)); broadcastWaitNotifyActions.add(broadcastNotifyString); } return broadcastWaitNotifyActions; } public void precomputeActionsForBroadcastEvents(Map<String, List<String>> currentActions) { Multimap<String, String> actionsToRestartMap = BroadcastHandler.getActionsToRestartMap(); if (!actionsToRestartMap.isEmpty()) { return; } List<String> actions = new ArrayList<>(); if (currentActions.get(Constants.START_SCRIPT) != null) { actions.addAll(currentActions.get(Constants.START_SCRIPT)); } if (currentActions.get(Constants.BROADCAST_SCRIPT) != null) { actions.addAll(currentActions.get(Constants.BROADCAST_SCRIPT)); } if (currentActions.get(Constants.BROADCAST_NOTIFY_ACTION) != null) { actions.addAll(currentActions.get(Constants.BROADCAST_NOTIFY_ACTION)); } if (currentActions.get(Constants.RASPI_SCRIPT) != null) { actions.addAll(currentActions.get(Constants.RASPI_SCRIPT)); } for (String action : actions) { for (String actionOfLook : actions) { if (action.equals(actionOfLook) || isFirstSequenceActionAndEqualsSecond(action, actionOfLook) || isFirstSequenceActionAndEqualsSecond(actionOfLook, action)) { if (!actionsToRestartMap.containsKey(action)) { actionsToRestartMap.put(action, actionOfLook); } else { actionsToRestartMap.get(action).add(actionOfLook); } } } } } private static boolean isFirstSequenceActionAndEqualsSecond(String action1, String action2) { String spriteOfAction1 = action1.substring(action1.indexOf(Constants.ACTION_SPRITE_SEPARATOR)); String spriteOfAction2 = action2.substring(action2.indexOf(Constants.ACTION_SPRITE_SEPARATOR)); if (!spriteOfAction1.equals(spriteOfAction2)) { return false; } if (!action1.startsWith(SEQUENCE) || !action1.contains(BROADCAST_NOTIFY)) { return false; } int startIndex1 = action1.indexOf(Constants.OPENING_BRACE) + 1; int endIndex1 = action1.indexOf(BROADCAST_NOTIFY); String innerAction1 = action1.substring(startIndex1, endIndex1); String action2Sub = action2.substring(0, action2.indexOf(Constants.ACTION_SPRITE_SEPARATOR)); return innerAction1.equals(action2Sub); } private void printPhysicsLabelOnScreen() { PhysicsObject tempPhysicsObject; final int fontOffset = 5; batch.setProjectionMatrix(camera.combined); batch.begin(); for (Sprite sprite : sprites) { if (sprite.look instanceof PhysicsLook) { tempPhysicsObject = physicsWorld.getPhysicsObject(sprite); font.draw(batch, "velocity_x: " + tempPhysicsObject.getVelocity().x, tempPhysicsObject.getX(), tempPhysicsObject.getY()); font.draw(batch, "velocity_y: " + tempPhysicsObject.getVelocity().y, tempPhysicsObject.getX(), tempPhysicsObject.getY() + font.getXHeight() + fontOffset); font.draw(batch, "angular velocity: " + tempPhysicsObject.getRotationSpeed(), tempPhysicsObject.getX(), tempPhysicsObject.getY() + font.getXHeight() * 2 + fontOffset * 2); font.draw(batch, "direction: " + tempPhysicsObject.getDirection(), tempPhysicsObject.getX(), tempPhysicsObject.getY() + font.getXHeight() * 3 + fontOffset * 3); } } batch.end(); } private void drawAxes() { batch.setProjectionMatrix(camera.combined); batch.begin(); batch.draw(axes, -virtualWidthHalf, -AXIS_WIDTH / 2, virtualWidth, AXIS_WIDTH); batch.draw(axes, -AXIS_WIDTH / 2, -virtualHeightHalf, AXIS_WIDTH, virtualHeight); GlyphLayout layout = new GlyphLayout(); layout.setText(font, String.valueOf((int) virtualHeightHalf)); font.draw(batch, "-" + (int) virtualWidthHalf, -virtualWidthHalf + 3, -layout.height / 2); font.draw(batch, String.valueOf((int) virtualWidthHalf), virtualWidthHalf - layout.width, -layout.height / 2); font.draw(batch, "-" + (int) virtualHeightHalf, layout.height / 2, -virtualHeightHalf + layout.height + 3); font.draw(batch, String.valueOf((int) virtualHeightHalf), layout.height / 2, virtualHeightHalf - 3); font.draw(batch, "0", layout.height / 2, -layout.height / 2); batch.end(); } @Override public void resize(int width, int height) { } @Override public void dispose() { if (!finished) { this.finish(); } disposeStageButKeepActors(); font.dispose(); axes.dispose(); disposeTextures(); Log.e(TAG, "dispose"); disposeClonedSprites(); } public boolean makeManualScreenshot() { makeScreenshot = true; while (makeScreenshot) { Thread.yield(); } return saveScreenshot(this.screenshot, SCREENSHOT_MANUAL_FILE_NAME); } private boolean saveScreenshot(byte[] screenshot, String fileName) { int length = screenshot.length; Bitmap fullScreenBitmap; Bitmap centerSquareBitmap; int[] colors = new int[length / 4]; if (colors.length != screenshotWidth * screenshotHeight || colors.length == 0) { return false; } for (int i = 0; i < length; i += 4) { colors[i / 4] = android.graphics.Color.argb(255, screenshot[i + 0] & 0xFF, screenshot[i + 1] & 0xFF, screenshot[i + 2] & 0xFF); } fullScreenBitmap = Bitmap.createBitmap(colors, 0, screenshotWidth, screenshotWidth, screenshotHeight, Config.ARGB_8888); if (screenshotWidth < screenshotHeight) { int verticalMargin = (screenshotHeight - screenshotWidth) / 2; centerSquareBitmap = Bitmap.createBitmap(fullScreenBitmap, 0, verticalMargin, screenshotWidth, screenshotWidth); } else if (screenshotWidth > screenshotHeight) { int horizontalMargin = (screenshotWidth - screenshotHeight) / 2; centerSquareBitmap = Bitmap.createBitmap(fullScreenBitmap, horizontalMargin, 0, screenshotHeight, screenshotHeight); } else { centerSquareBitmap = Bitmap.createBitmap(fullScreenBitmap, 0, 0, screenshotWidth, screenshotHeight); } FileHandle imageScene = Gdx.files.absolute(pathForSceneScreenshot + fileName); OutputStream streamScene = imageScene.write(false); try { new File(pathForSceneScreenshot + Constants.NO_MEDIA_FILE).createNewFile(); centerSquareBitmap.compress(Bitmap.CompressFormat.PNG, 100, streamScene); streamScene.close(); } catch (IOException e) { return false; } return true; } public byte[] getPixels(int x, int y, int width, int height) { testX = x; testY = y; testWidth = width; testHeight = height; makeTestPixels = true; while (makeTestPixels) { Thread.yield(); } byte[] copyOfTestPixels = new byte[testPixels.length]; System.arraycopy(testPixels, 0, copyOfTestPixels, 0, testPixels.length); return copyOfTestPixels; } public void toggleScreenMode() { switch (project.getScreenMode()) { case MAXIMIZE: project.setScreenMode(ScreenModes.STRETCH); break; case STRETCH: project.setScreenMode(ScreenModes.MAXIMIZE); break; } initScreenMode(); if (checkIfAutomaticScreenshotShouldBeTaken) { makeAutomaticScreenshot = project.manualScreenshotExists(SCREENSHOT_MANUAL_FILE_NAME); } } public void clearBackground() { penActor.reset(); } private void initScreenMode() { switch (project.getScreenMode()) { case STRETCH: screenshotWidth = ScreenValues.SCREEN_WIDTH; screenshotHeight = ScreenValues.SCREEN_HEIGHT; screenshotX = 0; screenshotY = 0; viewPort = new ScalingViewport(Scaling.stretch, virtualWidth, virtualHeight, camera); break; case MAXIMIZE: screenshotWidth = maximizeViewPortWidth; screenshotHeight = maximizeViewPortHeight; screenshotX = maximizeViewPortX; screenshotY = maximizeViewPortY; viewPort = new ExtendViewport(virtualWidth, virtualHeight, camera); break; default: break; } viewPort.update(ScreenValues.SCREEN_WIDTH, ScreenValues.SCREEN_HEIGHT, false); camera.position.set(0, 0, 0); camera.update(); } private void disposeTextures() { List<Sprite> sprites = scene.getSpriteList(); int spriteSize = sprites.size(); for (int i = 0; i > spriteSize; i++) { List<LookData> data = sprites.get(i).getLookDataList(); int dataSize = data.size(); for (int j = 0; j < dataSize; j++) { LookData lookData = data.get(j); lookData.getPixmap().dispose(); lookData.getTextureRegion().getTexture().dispose(); } } } private void disposeStageButKeepActors() { stage.unfocusAll(); batch.dispose(); } public void addActor(Actor actor) { stage.addActor(actor); } public Stage getStage() { return stage; } public void removeActor(Look look) { look.remove(); } public void putBubbleActor(Sprite sprite, ShowBubbleActor actor) { bubbleActorMap.put(sprite, actor); } public void removeBubbleActorForSprite(Sprite sprite) { bubbleActorMap.remove(sprite); } public ShowBubbleActor getBubbleActorForSprite(Sprite sprite) { return bubbleActorMap.get(sprite); } public List<Sprite> getSpritesFromStage() { return sprites; } private class StageBackup { public Stage stage; public boolean paused; public boolean finished; public boolean reloadProject; public boolean flashState; public long timeToVibrate; public PhysicsWorld physicsWorld; public OrthographicCamera camera; public Batch batch; public BitmapFont font; public Passepartout passepartout; public Viewport viewPort; public List<Sprite> sprites; public boolean axesOn = false; public float deltaActionTimeDivisor; public boolean cameraRunning; public Map<Sprite, ShowBubbleActor> bubbleActorMap; public PenActor penActor; public StageBackup() { } } private StageBackup saveToBackup() { StageBackup backup = new StageBackup(); backup.stage = stage; backup.paused = paused; backup.finished = finished; backup.reloadProject = reloadProject; backup.flashState = FlashUtil.isOn(); if (backup.flashState) { FlashUtil.flashOff(); } backup.timeToVibrate = VibratorUtil.getTimeToVibrate(); backup.physicsWorld = physicsWorld; backup.camera = camera; backup.batch = batch; backup.font = font; backup.passepartout = passepartout; backup.viewPort = viewPort; backup.sprites = sprites; backup.axesOn = axesOn; backup.deltaActionTimeDivisor = deltaActionTimeDivisor; backup.cameraRunning = CameraManager.getInstance().isCameraActive(); if (backup.cameraRunning) { CameraManager.getInstance().pauseForScene(); } backup.bubbleActorMap = bubbleActorMap; backup.penActor = penActor; return backup; } private void restoreFromBackup(StageBackup backup) { stage = backup.stage; paused = backup.paused; finished = backup.finished; reloadProject = backup.reloadProject; if (backup.flashState) { FlashUtil.flashOn(); } if (backup.timeToVibrate > 0) { VibratorUtil.resumeVibrator(); VibratorUtil.setTimeToVibrate(backup.timeToVibrate); } else { VibratorUtil.pauseVibrator(); } physicsWorld = backup.physicsWorld; camera = backup.camera; batch = backup.batch; font = backup.font; passepartout = backup.passepartout; viewPort = backup.viewPort; sprites = backup.sprites; axesOn = backup.axesOn; deltaActionTimeDivisor = backup.deltaActionTimeDivisor; if (backup.cameraRunning) { CameraManager.getInstance().resumeForScene(); } bubbleActorMap = backup.bubbleActorMap; penActor = backup.penActor; } public void drawDebugCollisionPolygons() { boolean drawPolygons = true; boolean drawBoundingBoxes = false; boolean drawPolygonPoints = false; boolean drawTouchingAreas = true; Color colorPolygons = Color.MAGENTA; Color colorBoundingBoxes = Color.MAROON; Color colorPolygonPoints = Color.BLACK; Color colorTouchingAreas = Color.RED; int lineWidth = 5; Gdx.gl20.glLineWidth(lineWidth / camera.zoom); collisionPolygonDebugRenderer.setAutoShapeType(true); collisionPolygonDebugRenderer.begin(); for (Sprite sprite : sprites.subList(1, sprites.size())) { Polygon[] polygonsForSprite = sprite.look.getCurrentCollisionPolygon(); if (polygonsForSprite != null) { for (Polygon polygonToDraw : polygonsForSprite) { if (drawPolygons) { collisionPolygonDebugRenderer.setColor(colorPolygons); collisionPolygonDebugRenderer.polygon(polygonToDraw.getTransformedVertices()); } if (drawBoundingBoxes) { Rectangle r = polygonToDraw.getBoundingRectangle(); collisionPolygonDebugRenderer.setColor(colorBoundingBoxes); collisionPolygonDebugRenderer.rect(r.getX(), r.getY(), r.getWidth(), r.getHeight(), Color.CYAN, Color .CYAN, Color.CYAN, Color.CYAN); } if (drawPolygonPoints) { collisionPolygonDebugRenderer.setColor(colorPolygonPoints); float[] points = polygonToDraw.getTransformedVertices(); for (int i = 0; i < points.length; i += 2) { collisionPolygonDebugRenderer.circle(points[i], points[i + 1], 10); } } } if (drawTouchingAreas) { ArrayList<PointF> touchingPoints = TouchUtil.getCurrentTouchingPoints(); collisionPolygonDebugRenderer.setColor(colorTouchingAreas); for (PointF point : touchingPoints) { collisionPolygonDebugRenderer.circle(point.x, point.y, Constants.COLLISION_WITH_FINGER_TOUCH_RADIUS); } } } } collisionPolygonDebugRenderer.end(); } }