├── .gitignore ├── LICENSE ├── NOTICE ├── README.markdown ├── build.gradle ├── editor ├── build.gradle ├── libs │ ├── css-engine-swing-api-sources.jar │ ├── css-engine-swing-api.jar │ ├── tween-engine-api-sources.jar │ └── tween-engine-api.jar ├── src │ └── main │ │ ├── java │ │ └── aurelienribon │ │ │ ├── Res.java │ │ │ ├── accessors │ │ │ └── SpriteAccessor.java │ │ │ ├── bodyeditor │ │ │ ├── Ctx.java │ │ │ ├── DynamicObjectsManager.java │ │ │ ├── IoManager.java │ │ │ ├── RigidBodiesManager.java │ │ │ ├── Settings.java │ │ │ ├── canvas │ │ │ │ ├── Assets.java │ │ │ │ ├── Canvas.java │ │ │ │ ├── CanvasDrawer.java │ │ │ │ ├── InputHelper.java │ │ │ │ ├── Label.java │ │ │ │ ├── PanZoomInputProcessor.java │ │ │ │ ├── dynamicobjects │ │ │ │ │ ├── BodiesList.java │ │ │ │ │ ├── BodiesListItem.java │ │ │ │ │ └── DynamicObjectsScreen.java │ │ │ │ └── rigidbodies │ │ │ │ │ ├── RigidBodiesScreen.java │ │ │ │ │ ├── RigidBodiesScreenDrawer.java │ │ │ │ │ └── input │ │ │ │ │ ├── CreationInputProcessor.java │ │ │ │ │ ├── EditionInputProcessor.java │ │ │ │ │ └── TestInputProcessor.java │ │ │ ├── io │ │ │ │ └── JsonIo.java │ │ │ ├── maths │ │ │ │ ├── Clipper.java │ │ │ │ ├── Tracer.java │ │ │ │ ├── earclipping │ │ │ │ │ ├── bayazit │ │ │ │ │ │ └── BayazitDecomposer.java │ │ │ │ │ └── ewjordan │ │ │ │ │ │ ├── EwjordanDecomposer.java │ │ │ │ │ │ ├── Polygon.java │ │ │ │ │ │ └── Triangle.java │ │ │ │ └── trace │ │ │ │ │ └── TextureConverter.java │ │ │ ├── models │ │ │ │ ├── CircleModel.java │ │ │ │ ├── DynamicObjectModel.java │ │ │ │ ├── PolygonModel.java │ │ │ │ ├── RigidBodyModel.java │ │ │ │ └── ShapeModel.java │ │ │ ├── ui │ │ │ │ ├── AutoTraceParamsDialog.form │ │ │ │ ├── AutoTraceParamsDialog.java │ │ │ │ ├── DynamicObjectsPanel.form │ │ │ │ ├── DynamicObjectsPanel.java │ │ │ │ ├── HelpDialog.form │ │ │ │ ├── HelpDialog.java │ │ │ │ ├── Main.java │ │ │ │ ├── MainWindow.form │ │ │ │ ├── MainWindow.java │ │ │ │ ├── ProjectPanel.form │ │ │ │ ├── ProjectPanel.java │ │ │ │ ├── RepairImagePathsDialog.form │ │ │ │ ├── RepairImagePathsDialog.java │ │ │ │ ├── RigidBodiesCreationDialog.form │ │ │ │ ├── RigidBodiesCreationDialog.java │ │ │ │ ├── RigidBodiesOptionsPanel.form │ │ │ │ ├── RigidBodiesOptionsPanel.java │ │ │ │ ├── RigidBodiesPanel.form │ │ │ │ └── RigidBodiesPanel.java │ │ │ └── utils │ │ │ │ └── ShapeUtils.java │ │ │ └── utils │ │ │ ├── gdx │ │ │ ├── PolygonUtils.java │ │ │ ├── SpriteUtils.java │ │ │ ├── TextureUtils.java │ │ │ └── VectorUtils.java │ │ │ ├── io │ │ │ ├── FilenameHelper.java │ │ │ └── HttpUtils.java │ │ │ ├── notifications │ │ │ ├── AutoListModel.java │ │ │ ├── ChangeListener.java │ │ │ ├── Changeable.java │ │ │ ├── ChangeableObject.java │ │ │ ├── ChangeableSupport.java │ │ │ └── ObservableList.java │ │ │ └── ui │ │ │ └── SwingHelper.java │ │ └── resources │ │ ├── css │ │ └── style.css │ │ ├── data │ │ ├── ball.png │ │ ├── transparent-dark.png │ │ ├── transparent-light.png │ │ ├── unknown.png │ │ ├── v00.png │ │ ├── v01.png │ │ ├── v10.png │ │ └── white.png │ │ └── gfx │ │ ├── autoTrace.png │ │ ├── bg.png │ │ ├── comingSoon.png │ │ ├── ic_add.png │ │ ├── ic_createShape.png │ │ ├── ic_delete.png │ │ ├── ic_down.png │ │ ├── ic_edit.png │ │ ├── ic_editShape.png │ │ ├── ic_error.png │ │ ├── ic_export.png │ │ ├── ic_file.png │ │ ├── ic_gear.png │ │ ├── ic_help.png │ │ ├── ic_import.png │ │ ├── ic_loading.gif │ │ ├── ic_lock.png │ │ ├── ic_manual.png │ │ ├── ic_new.png │ │ ├── ic_ok.png │ │ ├── ic_open.png │ │ ├── ic_remove.png │ │ ├── ic_save.png │ │ ├── ic_shape.png │ │ ├── ic_test.png │ │ ├── ic_texture.png │ │ ├── ic_unlock.png │ │ ├── ic_up.png │ │ ├── ic_warning.png │ │ ├── ic_wrench.png │ │ ├── newBody.png │ │ ├── title.png │ │ └── unknown.png └── test-me! │ ├── gfx │ ├── test01.png │ ├── test02 (non POT).png │ ├── test03 (multi shapes).png │ ├── test04 (non square).png │ └── test05 (non square).png │ └── test.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── loader-libgdx-demo ├── build.gradle ├── libs │ ├── gdx-backend-lwjgl-natives.jar │ ├── gdx-backend-lwjgl-sources.jar │ ├── gdx-backend-lwjgl.jar │ ├── gdx-natives.jar │ ├── gdx-sources.jar │ ├── gdx.jar │ ├── tween-engine-api-sources.jar │ └── tween-engine-api.jar └── src │ └── main │ ├── java │ └── aurelienribon │ │ └── bodyeditor │ │ ├── App.java │ │ └── Main.java │ └── resources │ └── data │ ├── gfx │ ├── ball.png │ ├── bottle.png │ └── white.png │ └── test.json ├── loader-libgdx ├── build.gradle └── src │ └── aurelienribon │ ├── bodyeditor │ └── BodyEditorLoader.java │ └── bodyeditorloader.gwt.xml ├── readmeImgs ├── aurelienribon - box2d-editor is apache 2.png ├── pbe-02.jpg ├── pbe-04.jpg ├── pbe-autotrace.jpg ├── pbe-circle-shapes.jpg ├── pbe-loader-demo.jpg ├── pbe-reference-point.jpg ├── pbe-workflow.jpg └── slide-physics-body-editor.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | ## General 2 | hs_err_pid* 3 | .gradle 4 | 5 | ## Intellij 6 | .idea/ 7 | *.ipr 8 | *.iws 9 | *.iml 10 | out/ 11 | local.properties 12 | 13 | ## Eclipse 14 | .classpath 15 | .project 16 | .metadata 17 | **/bin/ 18 | tmp/ 19 | *.tmp 20 | *.bak 21 | *.swp 22 | *~.nib 23 | 24 | .settings/ 25 | .loadpath 26 | .externalToolBuilders/ 27 | *.launch 28 | 29 | ## NetBeans 30 | **/nbproject/private/ 31 | build/ 32 | nbbuild/ 33 | dist/ 34 | nbdist/ 35 | nbactions.xml 36 | nb-configuration.xml 37 | 38 | ## OS Specific 39 | .DS_Store 40 | Icon 41 | ehthumbs.db 42 | Thumbs.db 43 | 44 | editor/manifest.mf 45 | editor/nbbuild.xml 46 | editor/build/* 47 | editor/dist/* 48 | editor/nbproject/* 49 | editor/bin/* 50 | editor/.classpath 51 | editor/.project 52 | editor/*.jar 53 | 54 | loader-libgdx/manifest.mf 55 | loader-libgdx/nbbuild.xml 56 | loader-libgdx/build/* 57 | loader-libgdx/dist/* 58 | loader-libgdx/nbproject/* 59 | loader-libgdx/bin/* 60 | loader-libgdx/.classpath 61 | loader-libgdx/.project 62 | loader-libgdx/*.jar 63 | 64 | loader-libgdx-demo/manifest.mf 65 | loader-libgdx-demo/nbbuild.xml 66 | loader-libgdx-demo/build/* 67 | loader-libgdx-demo/dist/* 68 | loader-libgdx-demo/nbproject/* 69 | loader-libgdx-demo/bin/* 70 | loader-libgdx-demo/.classpath 71 | loader-libgdx-demo/.project 72 | loader-libgdx-demo/*.jar 73 | 74 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Physics Body Editor (Also known as box2d-editor) 2 | 3 | This product includes software developed by 4 | aurelienribon - http://www.aurelienribon.com/blog/projects/physics-body-editor/ 5 | MovingBlocks - http://terasology.org 6 | 7 | Licensing: 8 | Comment states code is Apache 2: http://www.aurelienribon.com/blog/projects/physics-body-editor/?replytocom=2632#respond 9 | Archived link of blog + comments: https://archive.is/YjOvx 10 | Screenshot of comment: https://github.com/PrivateAlpha/box2d-editor/blob/master/readmeImgs/aurelienribon%20-%20box2d-editor%20is%20apache%202.png 11 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Physics Body Editor 2 | ========== 3 |

Physics Body Editor

4 | 5 | Introduction 6 | -------- 7 | 8 | Physics Body Editor is all about making your life easier with physics engines. Specifically, it targets the creation of collision shapes for your game objects: we call them rigid bodies. It can also let you combine these objects together and link them with joints to create complex objects: we call them dynamic objects. 9 | 10 | The problem we want to solve is as follows: have a look at the image on the right, I wanted to create a bottle that can hold objects inside it. At first, I used a drawing tool to draw my shape points over the bottle image, and I reported the values in my game. For each point, I had to convert from pixel units to world units of course. Boring. Oh, and guess what? It didn’t work! Indeed, physics engines usually only work with convex polygons! On to decompose the shape into multiple convex polygons by hand… More than boring. And of course, each time I wanted to do a little change, I had to go over the same process. 11 | 12 | I guess you understand why such automated tool can be handy: it converts pixel units to world units, decomposes the shape into multiple convex polygons, and lets you test the result directly! 13 | Features 14 | 15 | * Automatically decomposes concave shapes into convex polygons, 16 | * Automatically traces your images if needed, 17 | * Supports multiple outlines for a single body, 18 | * Supports polygon and circle shapes, 19 | * Reference point location can be changed, 20 | * Visual configurable grid with snap-to-grid option, 21 | * Built-in collision tester! Throw balls at your body to test it, 22 | * Loader provided for LibGDX game framework, 23 | * Simple export format (JSON), to let you create your own loader for any framework in any language. 24 | 25 |

From the editor to the game

26 |

Features!

27 | 28 | Technologies 29 | -------- 30 | 31 | The application uses the following technologies: 32 | 33 | * [LibGDX](https://github.com/libgdx/libgdx), the most awesome game dev library, for the rendering of the canvas area, 34 | * [Box2d](http://box2d.org/), as the embedded physics engine (available in Java thanks to libGDX), 35 | * [Farseer engine](http://farseerphysics.codeplex.com/), for its auto-trace and polygon decomposition algorithms. 36 | 37 | Getting Started 38 | -------- 39 | * [Wiki](https://github.com/MovingBlocks/box2d-editor/wiki) 40 | * [YouTube](https://youtu.be/KASY91EiTXQ) 41 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | } 6 | 7 | allprojects { 8 | apply plugin: "eclipse" 9 | apply plugin: "idea" 10 | 11 | version = '1.3.1' 12 | ext { 13 | appName = 'Physics Body Editor' 14 | gdxVersion = '1.12.1' 15 | } 16 | 17 | repositories { 18 | // Good ole Maven central 19 | mavenCentral() 20 | 21 | // Repos for LibGDX 22 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 23 | maven { url "https://oss.sonatype.org/content/repositories/releases/" } 24 | 25 | // Terasology Artifactory for any shared libs 26 | //maven { url "http://artifactory.terasology.org/artifactory/virtual-repo-live" } 27 | } 28 | } 29 | 30 | tasks.eclipse.doLast { 31 | delete ".project" 32 | } 33 | cleanIdea.doLast { 34 | new File('box2d-editor.iws').delete() 35 | } 36 | -------------------------------------------------------------------------------- /editor/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'application' 3 | } 4 | 5 | sourceCompatibility = 1.8 6 | [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 7 | project.ext.mainClassName = "aurelienribon.bodyeditor.ui.Main" 8 | dependencies { 9 | implementation "com.badlogicgames.gdx:gdx:$gdxVersion" 10 | implementation "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" 11 | implementation "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion" 12 | implementation "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop" 13 | implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion" 14 | implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" 15 | 16 | implementation "net.java.balloontip:balloontip:1.2.1" 17 | implementation "commons-io:commons-io:2.1" 18 | implementation "org.json:json:20090211" 19 | 20 | implementation files('./libs/css-engine-swing-api.jar') 21 | implementation files('./libs/tween-engine-api.jar') 22 | 23 | } 24 | 25 | sourceSets { 26 | main { 27 | java 28 | { 29 | srcDirs = ["src/main/java"] 30 | destinationDirectory.set(new File("$buildDir/classes")) 31 | } 32 | resources.srcDirs = ["src/main/resources"] 33 | output.resourcesDir 'build/classes' 34 | } 35 | } 36 | 37 | application { 38 | mainClass = 'aurelienribon.bodyeditor.ui.Main' 39 | } 40 | 41 | jar { 42 | archiveFileName = "PhysicsBodyEditor.jar" 43 | 44 | // doFirst { 45 | // copy { 46 | // from 'src/SolAppListener.gwt.xml' 47 | // into 'build/classes/main' 48 | // } 49 | // } 50 | } 51 | 52 | 53 | distributions { 54 | main { 55 | contents { 56 | from('test-me!') { 57 | into 'test-me!' 58 | } 59 | } 60 | } 61 | } 62 | 63 | eclipse.project { 64 | name = appName + "-editor" 65 | } 66 | 67 | idea { 68 | module { 69 | // Change around the output a bit 70 | inheritOutputDirs = false 71 | outputDir = file('build/classes') 72 | testOutputDir = file('build/testClasses') 73 | downloadSources = true 74 | } 75 | } -------------------------------------------------------------------------------- /editor/libs/css-engine-swing-api-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/box2d-editor/05c7ea15b80fff859c6abc91c1761979cbfd31a3/editor/libs/css-engine-swing-api-sources.jar -------------------------------------------------------------------------------- /editor/libs/css-engine-swing-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/box2d-editor/05c7ea15b80fff859c6abc91c1761979cbfd31a3/editor/libs/css-engine-swing-api.jar -------------------------------------------------------------------------------- /editor/libs/tween-engine-api-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/box2d-editor/05c7ea15b80fff859c6abc91c1761979cbfd31a3/editor/libs/tween-engine-api-sources.jar -------------------------------------------------------------------------------- /editor/libs/tween-engine-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MovingBlocks/box2d-editor/05c7ea15b80fff859c6abc91c1761979cbfd31a3/editor/libs/tween-engine-api.jar -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/Res.java: -------------------------------------------------------------------------------- 1 | package aurelienribon; 2 | 3 | import javax.swing.*; 4 | import java.io.InputStream; 5 | import java.net.URL; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 11 | */ 12 | public class Res { 13 | private static Map imageIcons = new HashMap(); 14 | 15 | public static ImageIcon getImage(String name) { 16 | if (!imageIcons.containsKey(name)) { 17 | URL url = Res.class.getResource(name); 18 | if (url == null) throw new RuntimeException("File not found: " + name); 19 | imageIcons.put(name, new ImageIcon(url)); 20 | } 21 | 22 | return imageIcons.get(name); 23 | } 24 | 25 | public static InputStream getStream(String name) { 26 | InputStream is = Res.class.getResourceAsStream(name); 27 | if (is == null) throw new RuntimeException("File not found: " + name); 28 | return is; 29 | } 30 | 31 | public static URL getUrl(String name) { 32 | URL url = Res.class.getResource(name); 33 | if (url == null) throw new RuntimeException("File not found: " + name); 34 | return url; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/accessors/SpriteAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 MovingBlocks 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package aurelienribon.accessors; 17 | 18 | import aurelienribon.tweenengine.TweenAccessor; 19 | import com.badlogic.gdx.graphics.Color; 20 | import com.badlogic.gdx.graphics.g2d.Sprite; 21 | 22 | public class SpriteAccessor implements TweenAccessor { 23 | public static final int POS_XY = 1; 24 | public static final int POS_X = 10; 25 | public static final int POS_Y = 11; 26 | public static final int CPOS_XY = 2; 27 | public static final int SCALE_XY = 3; 28 | public static final int ROTATION = 4; 29 | public static final int OPACITY = 5; 30 | public static final int TINT = 6; 31 | 32 | @Override 33 | public int getValues(Sprite target, int tweenType, float[] returnValues) { 34 | switch (tweenType) { 35 | case POS_XY: 36 | returnValues[0] = target.getX(); 37 | returnValues[1] = target.getY(); 38 | return 2; 39 | 40 | case POS_X: 41 | returnValues[0] = target.getX(); 42 | return 1; 43 | 44 | case POS_Y: 45 | returnValues[0] = target.getY(); 46 | return 1; 47 | 48 | case CPOS_XY: 49 | returnValues[0] = target.getX() + target.getWidth() / 2; 50 | returnValues[1] = target.getY() + target.getHeight() / 2; 51 | return 2; 52 | 53 | case SCALE_XY: 54 | returnValues[0] = target.getScaleX(); 55 | returnValues[1] = target.getScaleY(); 56 | return 2; 57 | 58 | case ROTATION: 59 | returnValues[0] = target.getRotation(); 60 | return 1; 61 | 62 | case OPACITY: 63 | returnValues[0] = target.getColor().a; 64 | return 1; 65 | 66 | case TINT: 67 | returnValues[0] = target.getColor().r; 68 | returnValues[1] = target.getColor().g; 69 | returnValues[2] = target.getColor().b; 70 | return 3; 71 | 72 | default: 73 | assert false; 74 | return -1; 75 | } 76 | } 77 | 78 | @Override 79 | public void setValues(Sprite target, int tweenType, float[] newValues) { 80 | switch (tweenType) { 81 | case POS_XY: 82 | target.setPosition(newValues[0], newValues[1]); 83 | break; 84 | 85 | case POS_X: 86 | target.setPosition(newValues[0], target.getY()); 87 | break; 88 | 89 | case POS_Y: 90 | target.setPosition(target.getX(), newValues[0]); 91 | break; 92 | 93 | case CPOS_XY: 94 | target.setPosition(newValues[0] - target.getWidth() / 2, newValues[1] - target.getHeight() / 2); 95 | break; 96 | 97 | case SCALE_XY: 98 | target.setScale(newValues[0], newValues[1]); 99 | break; 100 | 101 | case ROTATION: 102 | target.setRotation(newValues[0]); 103 | break; 104 | 105 | case OPACITY: 106 | Color c = target.getColor(); 107 | c.set(c.r, c.g, c.b, newValues[0]); 108 | target.setColor(c); 109 | break; 110 | 111 | case TINT: 112 | c = target.getColor(); 113 | c.set(newValues[0], newValues[1], newValues[2], c.a); 114 | target.setColor(c); 115 | break; 116 | 117 | default: 118 | assert false; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/Ctx.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor; 2 | 3 | import aurelienribon.bodyeditor.ui.MainWindow; 4 | 5 | /** 6 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 7 | */ 8 | public class Ctx { 9 | public static final IoManager io = new IoManager(); 10 | public static final RigidBodiesManager bodies = new RigidBodiesManager(); 11 | public static final DynamicObjectsManager objects = new DynamicObjectsManager(); 12 | public static final MainWindow window = new MainWindow(); 13 | } 14 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/DynamicObjectsManager.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor; 2 | 3 | import aurelienribon.bodyeditor.models.DynamicObjectModel; 4 | import aurelienribon.utils.notifications.ChangeableObject; 5 | import aurelienribon.utils.notifications.ObservableList; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 11 | */ 12 | public class DynamicObjectsManager extends ChangeableObject { 13 | public static final String PROP_SELECTION = "selection"; 14 | 15 | private final ObservableList models = new ObservableList(this); 16 | private DynamicObjectModel selectedModel = null; 17 | 18 | public DynamicObjectsManager() { 19 | models.addListChangedListener(new ObservableList.ListChangeListener() { 20 | @Override 21 | public void changed(Object source, List added, List removed) { 22 | if (!models.contains(selectedModel)) select(null); 23 | } 24 | }); 25 | } 26 | 27 | public ObservableList getModels() { 28 | return models; 29 | } 30 | 31 | public DynamicObjectModel getSelectedModel() { 32 | assert selectedModel == null || models.contains(selectedModel); 33 | return selectedModel; 34 | } 35 | 36 | public void select(DynamicObjectModel model) { 37 | assert model == null || models.contains(model); 38 | selectedModel = model; 39 | firePropertyChanged(PROP_SELECTION); 40 | } 41 | 42 | public DynamicObjectModel getModel(String name) { 43 | for (DynamicObjectModel model : models) 44 | if (model.getName().equals(name)) return model; 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/IoManager.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor; 2 | 3 | import aurelienribon.bodyeditor.io.JsonIo; 4 | import aurelienribon.utils.io.FilenameHelper; 5 | import aurelienribon.utils.notifications.ChangeableObject; 6 | import org.apache.commons.io.FileUtils; 7 | import org.json.JSONException; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | /** 13 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 14 | */ 15 | public class IoManager extends ChangeableObject { 16 | public static final String PROP_PROJECTFILE = "projectFile"; 17 | private File projectFile; 18 | 19 | public File getProjectFile() { 20 | return projectFile; 21 | } 22 | 23 | public File getProjectDir() { 24 | return projectFile.getParentFile(); 25 | } 26 | 27 | public void setProjectFile(File projectFile) { 28 | this.projectFile = projectFile; 29 | firePropertyChanged(PROP_PROJECTFILE); 30 | } 31 | 32 | public void exportToFile() throws IOException, JSONException { 33 | assert projectFile != null; 34 | 35 | String str = JsonIo.serialize(); 36 | FileUtils.writeStringToFile(projectFile, str); 37 | } 38 | 39 | public void importFromFile() throws IOException, JSONException { 40 | assert projectFile != null; 41 | assert projectFile.isFile(); 42 | 43 | Ctx.bodies.getModels().clear(); 44 | String str = FileUtils.readFileToString(projectFile); 45 | 46 | JsonIo.deserialize(str); 47 | } 48 | 49 | public String buildImagePath(File imgFile) { 50 | return FilenameHelper.getRelativePath(imgFile.getPath(), projectFile.getParent()); 51 | } 52 | 53 | public File getImageFile(String imgPath) { 54 | if (imgPath == null) return null; 55 | File file = new File(projectFile.getParent(), imgPath); 56 | return file; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/RigidBodiesManager.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor; 2 | 3 | import aurelienribon.bodyeditor.models.RigidBodyModel; 4 | import aurelienribon.utils.notifications.ChangeableObject; 5 | import aurelienribon.utils.notifications.ObservableList; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 11 | */ 12 | public class RigidBodiesManager extends ChangeableObject { 13 | public static final String PROP_SELECTION = "selection"; 14 | 15 | private final ObservableList models = new ObservableList(this); 16 | private RigidBodyModel selectedModel; 17 | 18 | public RigidBodiesManager() { 19 | models.addListChangedListener(new ObservableList.ListChangeListener() { 20 | @Override 21 | public void changed(Object source, List added, List removed) { 22 | if (!models.contains(selectedModel)) select(null); 23 | } 24 | }); 25 | } 26 | 27 | public ObservableList getModels() { 28 | return models; 29 | } 30 | 31 | public RigidBodyModel getSelectedModel() { 32 | assert selectedModel == null || models.contains(selectedModel); 33 | return selectedModel; 34 | } 35 | 36 | public void select(RigidBodyModel model) { 37 | assert model == null || models.contains(model); 38 | selectedModel = model; 39 | firePropertyChanged(PROP_SELECTION); 40 | } 41 | 42 | public RigidBodyModel getModel(String name) { 43 | for (RigidBodyModel model : models) 44 | if (model.getName().equals(name)) return model; 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/Settings.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor; 2 | 3 | import aurelienribon.bodyeditor.maths.Clipper.Polygonizer; 4 | 5 | /** 6 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 7 | */ 8 | public class Settings { 9 | public static boolean isImageDrawn = true; 10 | public static boolean isShapeDrawn = true; 11 | public static boolean isPolygonDrawn = true; 12 | public static boolean isPhysicsDebugEnabled = false; 13 | public static boolean isSnapToGridEnabled = false; 14 | public static boolean isAxisShown = true; 15 | public static boolean isGridShown = false; 16 | public static float gridGap = 0.03f; 17 | public static Polygonizer polygonizer = Polygonizer.BAYAZIT; 18 | public static float autoTraceHullTolerance = 2.5f; 19 | public static int autoTraceAlphaTolerance = 128; 20 | public static boolean autoTraceMultiPartDetection = false; 21 | public static boolean autoTraceHoleDetection = false; 22 | } 23 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/canvas/Assets.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor.canvas; 2 | 3 | import aurelienribon.bodyeditor.Ctx; 4 | import aurelienribon.bodyeditor.models.RigidBodyModel; 5 | import aurelienribon.utils.gdx.TextureUtils; 6 | import aurelienribon.utils.notifications.ObservableList; 7 | import com.badlogic.gdx.assets.AssetManager; 8 | import com.badlogic.gdx.graphics.Texture; 9 | import com.badlogic.gdx.graphics.g2d.TextureRegion; 10 | 11 | import java.io.File; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 18 | */ 19 | public class Assets extends AssetManager { 20 | private static Assets instance = new Assets(); 21 | 22 | public static Assets inst() { 23 | return instance; 24 | } 25 | 26 | private final Map rigidBodiesRegions = new HashMap(); 27 | private TextureRegion unknownRegion; 28 | 29 | public void initialize() { 30 | String[] texturesNearest = new String[]{ 31 | "data/transparent-light.png", 32 | "data/transparent-dark.png", 33 | "data/white.png" 34 | }; 35 | 36 | String[] texturesLinear = new String[]{ 37 | "data/ball.png", 38 | "data/v00.png", 39 | "data/v01.png", 40 | "data/v10.png", 41 | "data/unknown.png" 42 | }; 43 | 44 | for (String tex : texturesNearest) load(tex, Texture.class); 45 | for (String tex : texturesLinear) load(tex, Texture.class); 46 | 47 | while (update() == false) { 48 | } 49 | 50 | for (String tex : texturesLinear) { 51 | get(tex, Texture.class).setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); 52 | } 53 | 54 | unknownRegion = new TextureRegion(get("data/unknown.png", Texture.class)); 55 | 56 | Ctx.bodies.getModels().addListChangedListener(new ObservableList.ListChangeListener() { 57 | @Override 58 | public void changed(Object source, List added, List removed) { 59 | for (RigidBodyModel body : removed) { 60 | TextureRegion region = rigidBodiesRegions.remove(body); 61 | if (region != null) region.getTexture().dispose(); 62 | } 63 | 64 | for (RigidBodyModel body : added) { 65 | load(body); 66 | } 67 | } 68 | }); 69 | } 70 | 71 | public TextureRegion getRegion(RigidBodyModel body) { 72 | if (!body.isImagePathValid()) return unknownRegion; 73 | if (body.getImagePath() == null) return null; 74 | if (!rigidBodiesRegions.containsKey(body)) load(body); 75 | return rigidBodiesRegions.get(body); 76 | } 77 | 78 | private void load(RigidBodyModel body) { 79 | if (!body.isImagePathValid()) return; 80 | if (body.getImagePath() == null) return; 81 | 82 | File file = Ctx.io.getImageFile(body.getImagePath()); 83 | TextureRegion region = TextureUtils.getPOTTexture(file.getPath()); 84 | rigidBodiesRegions.put(body, region); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/canvas/Canvas.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor.canvas; 2 | 3 | import aurelienribon.accessors.SpriteAccessor; 4 | import aurelienribon.bodyeditor.Ctx; 5 | import aurelienribon.bodyeditor.RigidBodiesManager; 6 | import aurelienribon.bodyeditor.Settings; 7 | import aurelienribon.bodyeditor.canvas.dynamicobjects.DynamicObjectsScreen; 8 | import aurelienribon.bodyeditor.canvas.rigidbodies.RigidBodiesScreen; 9 | import aurelienribon.tweenengine.Tween; 10 | import aurelienribon.utils.notifications.ChangeListener; 11 | import com.badlogic.gdx.ApplicationAdapter; 12 | import com.badlogic.gdx.Gdx; 13 | import com.badlogic.gdx.InputMultiplexer; 14 | import com.badlogic.gdx.graphics.Color; 15 | import com.badlogic.gdx.graphics.OrthographicCamera; 16 | import com.badlogic.gdx.graphics.Texture; 17 | import com.badlogic.gdx.graphics.g2d.BitmapFont; 18 | import com.badlogic.gdx.graphics.g2d.Sprite; 19 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; 20 | import com.badlogic.gdx.math.Vector2; 21 | import com.badlogic.gdx.math.Vector3; 22 | 23 | import java.util.List; 24 | import java.util.Locale; 25 | import java.util.concurrent.CopyOnWriteArrayList; 26 | 27 | /** 28 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 29 | */ 30 | public class Canvas extends ApplicationAdapter { 31 | public OrthographicCamera worldCamera; 32 | public OrthographicCamera screenCamera; 33 | public SpriteBatch batch; 34 | public BitmapFont font; 35 | public CanvasDrawer drawer; 36 | public InputMultiplexer input; 37 | 38 | public enum Mode {BODIES, OBJECTS} 39 | 40 | private Mode mode = Mode.BODIES; 41 | 42 | private RigidBodiesScreen rigidBodiesScreen; 43 | private DynamicObjectsScreen dynamicObjectsScreen; 44 | 45 | private Sprite infoLabel; 46 | private Texture backgroundTexture; 47 | 48 | @Override 49 | public void create() { 50 | Assets.inst().initialize(); 51 | Tween.registerAccessor(Sprite.class, new SpriteAccessor()); 52 | 53 | worldCamera = new OrthographicCamera(); 54 | screenCamera = new OrthographicCamera(); 55 | resetCameras(); 56 | 57 | batch = new SpriteBatch(); 58 | font = new BitmapFont(); 59 | drawer = new CanvasDrawer(batch, worldCamera); 60 | 61 | backgroundTexture = Assets.inst().get("data/transparent-light.png", Texture.class); 62 | backgroundTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat); 63 | 64 | infoLabel = new Sprite(Assets.inst().get("data/white.png", Texture.class)); 65 | infoLabel.setPosition(0, 0); 66 | infoLabel.setSize(120, 60); 67 | infoLabel.setColor(new Color(0x2A / 255f, 0x3B / 255f, 0x56 / 255f, 180 / 255f)); 68 | 69 | input = new InputMultiplexer(); 70 | input.addProcessor(new PanZoomInputProcessor(this)); 71 | Gdx.input.setInputProcessor(input); 72 | 73 | rigidBodiesScreen = new RigidBodiesScreen(this); 74 | dynamicObjectsScreen = new DynamicObjectsScreen(this); 75 | 76 | initializeSelectionListeners(); 77 | } 78 | 79 | // ------------------------------------------------------------------------- 80 | // Init 81 | // ------------------------------------------------------------------------- 82 | 83 | private void initializeSelectionListeners() { 84 | Ctx.bodies.addChangeListener(new ChangeListener() { 85 | @Override 86 | public void propertyChanged(Object source, String propertyName) { 87 | if (propertyName.equals(RigidBodiesManager.PROP_SELECTION)) { 88 | if (Ctx.bodies.getSelectedModel() != null) { 89 | Mode oldMode = mode; 90 | mode = Mode.BODIES; 91 | if (mode != oldMode) fireModeChanged(mode); 92 | } 93 | } 94 | } 95 | }); 96 | 97 | Ctx.objects.addChangeListener(new ChangeListener() { 98 | @Override 99 | public void propertyChanged(Object source, String propertyName) { 100 | if (propertyName.equals(RigidBodiesManager.PROP_SELECTION)) { 101 | if (Ctx.objects.getSelectedModel() != null) { 102 | Mode oldMode = mode; 103 | mode = Mode.OBJECTS; 104 | if (mode != oldMode) fireModeChanged(mode); 105 | } 106 | } 107 | } 108 | }); 109 | } 110 | 111 | // ------------------------------------------------------------------------- 112 | // Render 113 | // ------------------------------------------------------------------------- 114 | 115 | @Override 116 | public void render() { 117 | float w = Gdx.graphics.getWidth(); 118 | float h = Gdx.graphics.getHeight(); 119 | 120 | Gdx.gl30.glClearColor(1, 1, 1, 1); 121 | Gdx.gl30.glClear(Gdx.gl30.GL_COLOR_BUFFER_BIT); 122 | 123 | batch.setProjectionMatrix(screenCamera.combined); 124 | batch.begin(); 125 | batch.disableBlending(); 126 | float tw = backgroundTexture.getWidth(); 127 | float th = backgroundTexture.getHeight(); 128 | batch.draw(backgroundTexture, 0f, 0f, w, h, 0f, 0f, w / tw, h / th); 129 | batch.enableBlending(); 130 | batch.end(); 131 | 132 | rigidBodiesScreen.render(); 133 | dynamicObjectsScreen.render(); 134 | 135 | batch.setProjectionMatrix(screenCamera.combined); 136 | batch.begin(); 137 | infoLabel.draw(batch); 138 | font.setColor(Color.WHITE); 139 | font.draw(batch, String.format(Locale.US, "Zoom: %.0f %%", 100f / worldCamera.zoom), 10, 45); 140 | font.draw(batch, "Fps: " + Gdx.graphics.getFramesPerSecond(), 10, 25); 141 | batch.end(); 142 | } 143 | 144 | @Override 145 | public void resize(int width, int height) { 146 | Gdx.gl30.glViewport(0, 0, width, height); 147 | resetCameras(); 148 | } 149 | 150 | // ------------------------------------------------------------------------- 151 | // Public API 152 | // ------------------------------------------------------------------------- 153 | 154 | public Vector2 screenToWorld(int x, int y) { 155 | Vector3 v3 = new Vector3(x, y, 0); 156 | worldCamera.unproject(v3); 157 | return new Vector2(v3.x, v3.y); 158 | } 159 | 160 | public Vector2 alignedScreenToWorld(int x, int y) { 161 | Vector2 p = screenToWorld(x, y); 162 | if (Settings.isSnapToGridEnabled) { 163 | float gap = Settings.gridGap; 164 | p.x = Math.round(p.x / gap) * gap; 165 | p.y = Math.round(p.y / gap) * gap; 166 | } 167 | return p; 168 | } 169 | 170 | // ------------------------------------------------------------------------- 171 | // Events 172 | // ------------------------------------------------------------------------- 173 | 174 | private final List listeners = new CopyOnWriteArrayList(); 175 | 176 | public static interface Listener { 177 | public void modeChanged(Mode mode); 178 | } 179 | 180 | public void addListener(Listener listener) { 181 | listeners.add(listener); 182 | } 183 | 184 | private void fireModeChanged(Mode mode) { 185 | for (Listener listener : listeners) listener.modeChanged(mode); 186 | } 187 | 188 | // ------------------------------------------------------------------------- 189 | // Internals 190 | // ------------------------------------------------------------------------- 191 | 192 | private void resetCameras() { 193 | float w = Gdx.graphics.getWidth(); 194 | float h = Gdx.graphics.getHeight(); 195 | 196 | worldCamera.viewportWidth = w / 400; 197 | worldCamera.viewportHeight = w / 400 * h / w; 198 | worldCamera.position.set(0.5f, 0.5f, 0); 199 | worldCamera.update(); 200 | 201 | screenCamera.viewportWidth = w; 202 | screenCamera.viewportHeight = h; 203 | screenCamera.position.set(w / 2, h / 2, 0); 204 | screenCamera.update(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/canvas/InputHelper.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor.canvas; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | import com.badlogic.gdx.Input.Keys; 5 | 6 | /** 7 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 8 | */ 9 | public class InputHelper { 10 | public static boolean isCtrlDown() { 11 | return Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) 12 | || Gdx.input.isKeyPressed(Keys.CONTROL_RIGHT) 13 | || Gdx.input.isKeyPressed(Keys.C); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /editor/src/main/java/aurelienribon/bodyeditor/canvas/Label.java: -------------------------------------------------------------------------------- 1 | package aurelienribon.bodyeditor.canvas; 2 | 3 | import aurelienribon.tweenengine.Tween; 4 | import aurelienribon.tweenengine.TweenAccessor; 5 | import aurelienribon.tweenengine.TweenManager; 6 | import aurelienribon.utils.gdx.SpriteUtils; 7 | import com.badlogic.gdx.Gdx; 8 | import com.badlogic.gdx.graphics.Color; 9 | import com.badlogic.gdx.graphics.Texture; 10 | import com.badlogic.gdx.graphics.g2d.BitmapFont; 11 | import com.badlogic.gdx.graphics.g2d.GlyphLayout; 12 | import com.badlogic.gdx.graphics.g2d.Sprite; 13 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; 14 | 15 | /** 16 | * @author Aurelien Ribon | http://www.aurelienribon.com/ 17 | */ 18 | public class Label { 19 | static { 20 | Tween.registerAccessor(Label.class, new Accessor()); 21 | } 22 | 23 | public static enum Anchor {TOP_LEFT, BOTTOM_LEFT, TOP_RIGHT, BOTTOM_RIGHT} 24 | 25 | private static enum State {SHOWN, HIDDEN, HIDDEN_SEMI} 26 | 27 | private String text; 28 | private Sprite icon; 29 | private TouchCallback callback; 30 | private final BitmapFont font; 31 | private final Color color; 32 | private final Anchor anchor; 33 | private GlyphLayout layout = new GlyphLayout(); 34 | 35 | private final TweenManager tweenManager = new TweenManager(); 36 | private final Sprite bg; 37 | private final float y, w, h; 38 | private float offsetX; 39 | private boolean isTouchOver = false; 40 | private State state = State.HIDDEN; 41 | 42 | public Label(float y, float w, float h, String text, BitmapFont font, Color color, Anchor anchor) { 43 | this.y = y; 44 | this.w = w; 45 | this.h = h; 46 | this.text = text; 47 | this.font = font; 48 | this.color = color; 49 | this.anchor = anchor; 50 | 51 | this.bg = new Sprite(Assets.inst().get("data/white.png", Texture.class)); 52 | bg.setSize(w * 11 / 10, h); 53 | bg.setColor(color); 54 | 55 | offsetX = -w; 56 | } 57 | 58 | // ------------------------------------------------------------------------- 59 | // Callback 60 | // ------------------------------------------------------------------------- 61 | 62 | public static interface TouchCallback { 63 | public void touchDown(Label source); 64 | 65 | public void touchEnter(Label source); 66 | 67 | public void touchExit(Label source); 68 | } 69 | 70 | // ------------------------------------------------------------------------- 71 | // Public API 72 | // ------------------------------------------------------------------------- 73 | 74 | public void setText(String text) { 75 | this.text = text; 76 | } 77 | 78 | public void setIcon(String path) { 79 | this.icon = new Sprite(Assets.inst().get(path, Texture.class)); 80 | } 81 | 82 | public void setCallback(TouchCallback callback) { 83 | this.callback = callback; 84 | } 85 | 86 | public void hide() { 87 | if (state == State.HIDDEN) return; 88 | tweenManager.killTarget(this); 89 | Tween.to(this, Accessor.OFFSET_X, 0.3f).target(-w).start(tweenManager); 90 | Tween.to(this, Accessor.ALPHA, 0.3f).target(color.a).start(tweenManager); 91 | isTouchOver = false; 92 | state = State.HIDDEN; 93 | } 94 | 95 | public void hideSemi() { 96 | if (state == State.HIDDEN_SEMI) return; 97 | tweenManager.killTarget(this); 98 | Tween.to(this, Accessor.OFFSET_X, 0.3f).target(w / 10 - w).start(tweenManager); 99 | Tween.to(this, Accessor.ALPHA, 0.3f).target(color.a).start(tweenManager); 100 | isTouchOver = false; 101 | state = State.HIDDEN_SEMI; 102 | } 103 | 104 | public void show() { 105 | if (state == State.SHOWN) return; 106 | tweenManager.killTarget(this); 107 | Tween.to(this, Accessor.OFFSET_X, 0.3f).target(0).start(tweenManager); 108 | Tween.to(this, Accessor.ALPHA, 0.3f).target(color.a).start(tweenManager); 109 | state = State.SHOWN; 110 | } 111 | 112 | public void tiltOn() { 113 | float tx; 114 | 115 | switch (state) { 116 | case SHOWN: 117 | tx = w / 10; 118 | break; 119 | case HIDDEN_SEMI: 120 | tx = -w + w / 10 + w / 10; 121 | break; 122 | default: 123 | return; 124 | } 125 | 126 | tweenManager.killTarget(this); 127 | Tween.to(this, Accessor.ALPHA, 0.2f).target(1).start(tweenManager); 128 | Tween.to(this, Accessor.OFFSET_X, 0.2f).target(tx).start(tweenManager); 129 | } 130 | 131 | public void tiltOff() { 132 | float tx; 133 | 134 | switch (state) { 135 | case SHOWN: 136 | tx = 0; 137 | break; 138 | case HIDDEN_SEMI: 139 | tx = -w + w / 10; 140 | break; 141 | default: 142 | return; 143 | } 144 | 145 | tweenManager.killTarget(this); 146 | Tween.to(this, Accessor.ALPHA, 0.2f).target(color.a).start(tweenManager); 147 | Tween.to(this, Accessor.OFFSET_X, 0.2f).target(tx).start(tweenManager); 148 | } 149 | 150 | public void draw(SpriteBatch batch) { 151 | tweenManager.update(Gdx.graphics.getDeltaTime()); 152 | 153 | float sw = Gdx.graphics.getWidth(); 154 | float sh = Gdx.graphics.getHeight(); 155 | float x = isAnchorLeft() ? offsetX : sw - w - offsetX; 156 | float bgX = isAnchorLeft() ? x - w / 10 : x; 157 | 158 | layout.setText(font, text); 159 | float width = layout.width;// contains the width of the current set text 160 | float textH = layout.height; // contains the height of the current set text 161 | 162 | bg.setPosition(bgX, sh - y); 163 | bg.draw(batch); 164 | 165 | if (icon != null) { 166 | icon.setPosition(x + 10, sh - y + h / 2 - icon.getHeight() / 2); 167 | icon.draw(batch); 168 | font.setColor(Color.WHITE); 169 | font.draw(batch, text, x + 10 + icon.getWidth() + 10, sh - y + h / 2 + textH / 2); 170 | } else { 171 | font.setColor(Color.WHITE); 172 | font.draw(batch, text, x + 10, sh - y + h / 2 + textH / 2); 173 | } 174 | } 175 | 176 | public boolean touchMoved(int x, int y) { 177 | y = Gdx.graphics.getHeight() - y - 1; 178 | if (isOver(x, y) && !isTouchOver && state == State.SHOWN) { 179 | isTouchOver = true; 180 | tiltOn(); 181 | if (callback != null) callback.touchEnter(this); 182 | } else if (!isOver(x, y) && isTouchOver) { 183 | isTouchOver = false; 184 | tiltOff(); 185 | if (callback != null) callback.touchExit(this); 186 | } 187 | return isOver(x, y); 188 | } 189 | 190 | public boolean touchDown(int x, int y) { 191 | y = Gdx.graphics.getHeight() - y - 1; 192 | if (isOver(x, y) && callback != null) callback.touchDown(this); 193 | return isOver(x, y); 194 | } 195 | 196 | // ------------------------------------------------------------------------- 197 | // Helpers 198 | // ------------------------------------------------------------------------- 199 | 200 | private boolean isOver(float x, float y) { 201 | return SpriteUtils.isOver(bg, x, y); 202 | } 203 | 204 | private boolean isAnchorLeft() { 205 | return anchor == Anchor.BOTTOM_LEFT || anchor == Anchor.TOP_LEFT; 206 | } 207 | 208 | // ------------------------------------------------------------------------- 209 | // Tween Accessor 210 | // ------------------------------------------------------------------------- 211 | 212 | private static class Accessor implements TweenAccessor