/* * 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.physics.shapebuilder; import android.util.Log; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.physics.box2d.Shape; import org.catrobat.catroid.common.LookData; import java.util.HashMap; import java.util.Map; public final class PhysicsShapeBuilder { private static final String TAG = PhysicsShapeBuilder.class.getSimpleName(); private static final float[] ACCURACY_LEVELS = { 0.125f, 0.25f, 0.50f, 0.75f, 1.0f }; private static PhysicsShapeBuilder instance = null; public static PhysicsShapeBuilder getInstance() { if (instance == null) { instance = new PhysicsShapeBuilder(); } return instance; } private PhysicsShapeBuilderStrategy strategy = new PhysicsShapeBuilderStrategyFastHull(); private Map<String, ImageShapes> imageShapesMap = new HashMap<>(); private PhysicsShapeBuilder() { } public void reset() { strategy = new PhysicsShapeBuilderStrategyFastHull(); imageShapesMap = new HashMap<>(); } public synchronized Shape[] getScaledShapes(LookData lookData, float scaleFactor) throws RuntimeException { if (scaleFactor < 0) { throw new RuntimeException("scaleFactor can not be smaller than 0"); } else if (lookData == null) { throw new RuntimeException("get shape for null lookData not possible"); } Pixmap pixmap = lookData.getPixmap(); if (pixmap == null) { Log.e(TAG, "pixmap should not be null"); return null; } String imageIdentifier = lookData.getChecksum(); if (!imageShapesMap.containsKey(imageIdentifier)) { imageShapesMap.put(imageIdentifier, new ImageShapes(pixmap)); } float accuracyLevel = getAccuracyLevel(scaleFactor); Shape[] shapes = imageShapesMap.get(imageIdentifier).getShapes(accuracyLevel); if (shapes == null) { Log.e(TAG, "shapes should not be null"); return null; } return PhysicsShapeScaleUtils.scaleShapes(shapes, scaleFactor); } private static float getAccuracyLevel(float scaleFactor) { if (ACCURACY_LEVELS.length == 0) { return 0; } if (ACCURACY_LEVELS.length == 1) { return ACCURACY_LEVELS[0]; } for (int accuracyIdx = 0; accuracyIdx < ACCURACY_LEVELS.length - 1; accuracyIdx++) { float average = (ACCURACY_LEVELS[accuracyIdx] + ACCURACY_LEVELS[accuracyIdx]) / 2; if (scaleFactor < average) { return ACCURACY_LEVELS[accuracyIdx]; } } return ACCURACY_LEVELS[ACCURACY_LEVELS.length - 1]; } /** * Saves computed shapes in different accuracies for one image. (All in baseline -> 100%) */ private class ImageShapes { private static final int MAX_ORIGINAL_PIXMAP_SIZE = 512; private Map<String, Shape[]> shapeMap = new HashMap<>(); private Pixmap pixmap; private float sizeAdjustmentScaleFactor = 1; public ImageShapes(Pixmap pixmap) { if (pixmap == null) { throw new RuntimeException("Pixmap must not null"); } this.pixmap = pixmap; int width = this.pixmap.getWidth(); int height = this.pixmap.getHeight(); if (width > MAX_ORIGINAL_PIXMAP_SIZE || height > MAX_ORIGINAL_PIXMAP_SIZE) { if (width > height) { sizeAdjustmentScaleFactor = (float) MAX_ORIGINAL_PIXMAP_SIZE / width; } else { sizeAdjustmentScaleFactor = (float) MAX_ORIGINAL_PIXMAP_SIZE / height; } } } private String getShapeKey(float accuracyLevel) { return String.valueOf((int) (accuracyLevel * 100)); } private Shape[] computeNewShape(float accuracy) { int width = pixmap.getWidth(); int height = pixmap.getHeight(); int scaledWidth = Math.round(width * sizeAdjustmentScaleFactor * accuracy); int scaledHeight = Math.round(height * sizeAdjustmentScaleFactor * accuracy); if (scaledWidth < 1) { scaledWidth = 1; } if (scaledHeight < 1) { scaledHeight = 1; } Pixmap.setFilter(Pixmap.Filter.NearestNeighbour); Pixmap scaledPixmap = new Pixmap(scaledWidth, scaledHeight, pixmap.getFormat()); scaledPixmap.drawPixmap(pixmap, 0, 0, width, height, 0, 0, scaledWidth, scaledHeight); Shape[] scaledShapes = strategy.build(scaledPixmap, 1.0f); return PhysicsShapeScaleUtils.scaleShapes(scaledShapes, 1.0f, sizeAdjustmentScaleFactor * accuracy); } public Shape[] getShapes(float accuracyLevel) throws RuntimeException { String shapeKey = getShapeKey(accuracyLevel); if (!shapeMap.containsKey(shapeKey)) { Shape[] shapes = computeNewShape(accuracyLevel); if (shapes == null) { return null; } shapeMap.put(shapeKey, shapes); } return shapeMap.get(shapeKey); } } }