├── settings.gradle
├── .gitignore
├── src
├── test
│ ├── resources
│ │ ├── test.png
│ │ ├── test.json
│ │ ├── test.scml
│ │ └── test.scon
│ └── java
│ │ └── com
│ │ └── brashmokey
│ │ └── spriter
│ │ ├── JsonReaderTest.java
│ │ └── SCONReaderTest.java
└── main
│ ├── java
│ └── com
│ │ └── brashmonkey
│ │ └── spriter
│ │ ├── SpriterException.java
│ │ ├── JsonReader.java
│ │ ├── File.java
│ │ ├── FileReference.java
│ │ ├── Dimension.java
│ │ ├── Meta.java
│ │ ├── IKObject.java
│ │ ├── Folder.java
│ │ ├── Interpolator.java
│ │ ├── CCDResolver.java
│ │ ├── Point.java
│ │ ├── IKResolver.java
│ │ ├── Loader.java
│ │ ├── Rectangle.java
│ │ ├── Box.java
│ │ ├── PlayerTweener.java
│ │ ├── Data.java
│ │ ├── Curve.java
│ │ ├── Calculator.java
│ │ ├── Mainline.java
│ │ ├── Entity.java
│ │ ├── Animation.java
│ │ ├── TweenedAnimation.java
│ │ ├── Timeline.java
│ │ ├── Drawer.java
│ │ ├── Spriter.java
│ │ ├── SCMLReader.java
│ │ └── SCONReader.java
│ └── resources
│ └── com
│ └── brashmonkey
│ └── spriter
│ └── spriter.gwt.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── LICENSE.txt
├── gradlew.bat
├── pom.xml
├── gradlew
└── README.md
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'spriter'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .idea
3 | build
4 | out
5 | target
--------------------------------------------------------------------------------
/src/test/resources/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Trixt0r/spriter/HEAD/src/test/resources/test.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Trixt0r/spriter/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/SpriterException.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * An Exception which will be thrown if a Spriter specific issue happens at runtime.
5 | * @author Trixt0r
6 | *
7 | */
8 | public class SpriterException extends RuntimeException {
9 |
10 | private static final long serialVersionUID = 1L;
11 |
12 | public SpriterException(String message){
13 | super(message);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/resources/com/brashmonkey/spriter/spriter.gwt.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/JsonReader.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import org.json.*;
4 |
5 | import java.io.*;
6 |
7 | /** Lightweight JSON parser. Uses org.json library.
8 | * @author mrdlink */
9 | public class JsonReader {
10 |
11 | private JsonReader(){}
12 |
13 | public static JSONObject parse (String json) {
14 | return new JSONObject(json);
15 | }
16 |
17 | public static JSONObject parse (InputStream input) throws IOException{
18 | StringBuilder textBuilder = new StringBuilder();
19 | Reader reader = new BufferedReader(new InputStreamReader(input));
20 | int c = 0;
21 | while ((c = reader.read()) != -1) {
22 | textBuilder.append((char) c);
23 | }
24 | reader.close();
25 |
26 | return new JSONObject(textBuilder.toString());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | /**************************************************************************
2 | * Copyright 2016 by Trixt0r
3 | * (https://github.com/Trixt0r, Heinrich Reich, e-mail: h.reich90@gmail.com)
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ***************************************************************************/
17 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/File.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * Represents a file in a Spriter SCML file.
5 | * A file has an {@link #id}, a {@link #name}.
6 | * A {@link #size} and a {@link #pivot} point, i.e. origin of an image do not have to be set since a file can be a sound file.
7 | * @author Trixt0r
8 | *
9 | */
10 | public class File {
11 |
12 | public final int id;
13 | public final String name;
14 | public final Dimension size;
15 | public final Point pivot;
16 |
17 | File(int id, String name, Dimension size, Point pivot){
18 | this.id = id;
19 | this.name = name;
20 | this.size = size;
21 | this.pivot = pivot;
22 | }
23 |
24 | /**
25 | * Returns whether this file is a sprite, i.e. an image which is going to be animated, or not.
26 | * @return whether this file is a sprite or not.
27 | */
28 | public boolean isSprite(){
29 | return pivot != null && size != null;
30 | }
31 |
32 | public String toString(){
33 | return getClass().getSimpleName()+"|[id: "+id+", name: "+name+", size: "+size+", pivot: "+pivot;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/FileReference.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * Represents a reference to a specific file.
5 | * A file reference consists of a folder and file index.
6 | * @author Trixt0r
7 | *
8 | */
9 | public class FileReference {
10 |
11 | public int folder, file;
12 |
13 | public FileReference(int folder, int file){
14 | this.set(folder, file);
15 | }
16 |
17 | @Override
18 | public int hashCode(){
19 | return folder*10000+file;//We can have 10000 files per folder
20 | }
21 |
22 | @Override
23 | public boolean equals(Object ref){
24 | if(ref instanceof FileReference){
25 | return this.file == ((FileReference)ref).file && this.folder == ((FileReference)ref).folder;
26 | } else return false;
27 | }
28 |
29 | public void set(int folder, int file){
30 | this.folder = folder;
31 | this.file = file;
32 | }
33 |
34 | public void set(FileReference ref){
35 | this.set(ref.folder, ref.file);
36 | }
37 |
38 | public boolean hasFile(){
39 | return this.file != -1;
40 | }
41 |
42 | public boolean hasFolder(){
43 | return this.folder != -1;
44 | }
45 |
46 | public String toString(){
47 | return "[folder: "+folder+", file: "+file+"]";
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Dimension.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * Represents a dimension in a 2D space.
5 | * A dimension has a width and a height.
6 | * @author Trixt0r
7 | *
8 | */
9 | public class Dimension {
10 |
11 | public float width, height;
12 |
13 | /**
14 | * Creates a new dimension with the given size.
15 | * @param width the width of the dimension
16 | * @param height the height of the dimension
17 | */
18 | public Dimension(float width, float height){
19 | this.set(width, height);
20 | }
21 |
22 | /**
23 | * Creates a new dimension with the given size.
24 | * @param size the size
25 | */
26 | public Dimension(Dimension size){
27 | this.set(size);
28 | }
29 |
30 | /**
31 | * Sets the size of this dimension to the given size.
32 | * @param width the width of the dimension
33 | * @param height the height of the dimension
34 | */
35 | public void set(float width, float height){
36 | this.width = width;
37 | this.height = height;
38 | }
39 |
40 | /**
41 | * Sets the size of this dimension to the given size.
42 | * @param size the size
43 | */
44 | public void set(Dimension size){
45 | this.set(size.width, size.height);
46 | }
47 |
48 | public String toString(){
49 | return "["+width+"x"+height+"]";
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/test/java/com/brashmokey/spriter/JsonReaderTest.java:
--------------------------------------------------------------------------------
1 | package com.brashmokey.spriter;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import com.brashmonkey.spriter.JsonReader;
6 | import org.json.JSONObject;
7 | import org.junit.Test;
8 |
9 | import java.io.*;
10 |
11 | public class JsonReaderTest {
12 |
13 | @Test
14 | public void should_load_json_object_from_json_string () throws IOException {
15 | StringBuilder textBuilder = new StringBuilder();
16 | BufferedReader reader = new BufferedReader(new FileReader(new File("src/test/resources/test.scon")));
17 | int c = 0;
18 | while ((c = reader.read()) != -1) {
19 | textBuilder.append((char) c);
20 | }
21 | reader.close();
22 |
23 | JSONObject object = JsonReader.parse(textBuilder.toString());
24 |
25 | assertTrue(object instanceof JSONObject);
26 | assertTrue(object.toMap().keySet().contains("scon_version"));
27 | }
28 |
29 | @Test
30 | public void should_load_json_object_from_input_stream () throws IOException {
31 | JSONObject object = JsonReader.parse(new FileInputStream(new File("src/test/resources/test.scon")));
32 |
33 | assertTrue(object instanceof JSONObject);
34 | assertTrue(object.toMap().keySet().contains("scon_version"));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Meta.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | public class Meta {
4 |
5 | Var[] vars;
6 |
7 | static class Var {
8 | int id;
9 | String name;
10 | Value def;
11 | Key[] keys;
12 |
13 | static class Key {
14 | int id;
15 | long time;
16 | Value value;
17 |
18 | public Value getValue() {
19 | return value;
20 | }
21 |
22 | public Class> getType() {
23 | return this.value.getClass();
24 | }
25 |
26 | public int getId() {
27 | return this.id;
28 | }
29 |
30 | public long getTime() {
31 | return this.time;
32 | }
33 | }
34 |
35 | static class Value {
36 | Object value;
37 |
38 | public int getInt() {
39 | return (Integer)value;
40 | }
41 |
42 | public long getLong() {
43 | return (Long)value;
44 | }
45 |
46 | public String getString() {
47 | return (String)value;
48 | }
49 | }
50 |
51 | public Key get(long time) {
52 | for (Key key: this.keys)
53 | if (key.time == time)
54 | return key;
55 | return null;
56 | }
57 |
58 | public boolean has(long time) {
59 | return this.get(time) != null;
60 | }
61 |
62 | public String getName() {
63 | return this.name;
64 | }
65 |
66 | public int getId() {
67 | return this.id;
68 | }
69 |
70 | public Value getDefault() {
71 | return this.def;
72 | }
73 | }
74 |
75 | public Var getVar(long time) {
76 | for (Var var: this.vars)
77 | if (var.get(time) != null)
78 | return var;
79 | return null;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/IKObject.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * An inverse kinematics objects which defines a constraint for a {@link IKResolver}.
5 | *
6 | * @author Trixt0r
7 | *
8 | */
9 | public class IKObject extends Point {
10 |
11 | int chainLength, iterations;
12 |
13 | /**
14 | * Creates a new IKObject with the given constraints.
15 | * @param x x coordinate constraint
16 | * @param y y coordinate constraint
17 | * @param length the chain length constraint.
18 | * @param iterations the number of iterations.
19 | */
20 | public IKObject(float x, float y, int length, int iterations) {
21 | super(x, y);
22 | this.setLength(length);
23 | this.setIterations(iterations);
24 | }
25 |
26 | /**
27 | * Sets the chain length of this ik object.
28 | * The chain length indicates how many parent bones should get affected, when a {@link IKResolver} resolves the constraints.
29 | * @param chainLength the chain length
30 | * @return this ik object for chained operations
31 | * @throws SpriterException if the chain length is smaller than 0
32 | */
33 | public IKObject setLength(int chainLength){
34 | if(chainLength < 0) throw new SpriterException("The chain has to be at least 0!");
35 | this.chainLength = chainLength;
36 | return this;
37 | }
38 |
39 | /**
40 | * Sets the number of iterations.
41 | * The more iterations a {@link IKResolver} is asked to do, the more precise the result will be.
42 | * @param iterations number of iterations
43 | * @return this ik object for chained operations
44 | * @throws SpriterException if the number of iterations is smaller than 0
45 | */
46 | public IKObject setIterations(int iterations){
47 | if(iterations < 0) throw new SpriterException("The number of iterations has to be at least 1!");
48 | this.iterations = iterations;
49 | return this;
50 | }
51 |
52 | /**
53 | * Returns the current set chain length.
54 | * @return the chain length
55 | */
56 | public int getChainLength(){
57 | return this.chainLength;
58 | }
59 |
60 | /**
61 | * Returns the current set number of iterations.
62 | * @return the number of iterations
63 | */
64 | public int getIterations(){
65 | return this.iterations;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Folder.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * Represents a folder in a Spriter SCML file.
5 | * A folder has at least an {@link #id}, {@link #name} and {@link #files} may be empty.
6 | * An instance of this class holds an array of {@link File} instances.
7 | * Specific {@link File} instances can be accessed via the corresponding methods, i.e getFile().
8 | * @author Trixt0r
9 | *
10 | */
11 | public class Folder {
12 |
13 | final File[] files;
14 | private int filePointer = 0;
15 | public final int id;
16 | public final String name;
17 |
18 | Folder(int id, String name, int files){
19 | this.id = id;
20 | this.name = name;
21 | this.files = new File[files];
22 | }
23 |
24 | /**
25 | * Adds a {@link File} instance to this folder.
26 | * @param file the file to add
27 | */
28 | void addFile(File file){
29 | this.files[filePointer++] = file;
30 | }
31 |
32 | /**
33 | * Returns a {@link File} instance with the given index.
34 | * @param index the index of the file
35 | * @return the file with the given name
36 | */
37 | public File getFile(int index){
38 | return files[index];
39 | }
40 |
41 | /**
42 | * Returns a {@link File} instance with the given name.
43 | * @param name the name of the file
44 | * @return the file with the given name or null if no file with the given name exists
45 | */
46 | public File getFile(String name){
47 | int index = getFileIndex(name);
48 | if(index >= 0) return getFile(index);
49 | else return null;
50 | }
51 |
52 | /**
53 | * Returns a file index with the given name.
54 | * @param name the name of the file
55 | * @return the file index with the given name or -1 if no file with the given name exists
56 | */
57 | int getFileIndex(String name){
58 | for(File file: this.files)
59 | if(file.name.equals(name)) return file.id;
60 | return -1;
61 | }
62 |
63 | public String toString(){
64 | String toReturn = getClass().getSimpleName()+"|[id: "+id+", name: "+name;
65 | for(File file: files)
66 | toReturn += "\n"+file;
67 | toReturn += "]";
68 | return toReturn;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/java/com/brashmokey/spriter/SCONReaderTest.java:
--------------------------------------------------------------------------------
1 | package com.brashmokey.spriter;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import com.brashmonkey.spriter.Data;
6 | import com.brashmonkey.spriter.Player;
7 | import com.brashmonkey.spriter.SCMLReader;
8 | import com.brashmonkey.spriter.SCONReader;
9 | import org.junit.Test;
10 |
11 | import java.io.*;
12 |
13 | public class SCONReaderTest {
14 |
15 | @Test
16 | public void should_load_scon_data_from_json_string () throws IOException {
17 | StringBuilder textBuilder = new StringBuilder();
18 | BufferedReader reader = new BufferedReader(new FileReader(new File("src/test/resources/test.scon")));
19 | int c = 0;
20 | while ((c = reader.read()) != -1) {
21 | textBuilder.append((char) c);
22 | }
23 | reader.close();
24 |
25 | SCONReader sconReader = new SCONReader(textBuilder.toString());
26 | Data data = sconReader.getData();
27 |
28 | assertTrue(data.scmlVersion.equals("1.0"));
29 | assertTrue(data.getEntity(0).name.equals("entity_000"));
30 | }
31 |
32 | @Test
33 | public void should_load_scon_data_from_input_stream () throws IOException {
34 | SCONReader sconReader = new SCONReader(new FileInputStream(new File("src/test/resources/test.scon")));
35 | Data data = sconReader.getData();
36 |
37 | assertTrue(data.scmlVersion.equals("1.0"));
38 | assertTrue(data.getEntity(0).name.equals("entity_000"));
39 | }
40 |
41 | @Test
42 | public void should_be_as_fast_as_scml_reader () throws IOException {
43 | long sconBegin = System.nanoTime();
44 | SCONReader sconReader = new SCONReader(new FileInputStream(new File("src/test/resources/Roy/roy.scon")));
45 | Data sconData = sconReader.getData();
46 | long sconEnd = System.nanoTime();
47 |
48 | long scmlBegin = System.nanoTime();
49 | SCMLReader scmlReader = new SCMLReader(new FileInputStream(new File("src/test/resources/Roy/roy.scml")));
50 | Data scmlData = scmlReader.getData();
51 | long scmlEnd = System.nanoTime();
52 |
53 | long sconTime = sconEnd - sconBegin;
54 | long scmlTime = scmlEnd - scmlBegin;
55 |
56 | assertTrue(sconTime <= scmlTime);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Interpolator.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 |
4 |
5 | /**
6 | * Utility class for various interpolation techniques, Spriter is using.
7 | * @author Trixt0r
8 | *
9 | */
10 | public class Interpolator {
11 |
12 | public static float linear(float a, float b, float t){
13 | return a+(b-a)*t;
14 | }
15 |
16 | public static float linearAngle(float a, float b, float t){
17 | return a + Calculator.angleDifference(b, a)*t;
18 | }
19 |
20 | public static float quadratic(float a, float b, float c, float t){
21 | return linear(linear(a, b, t), linear(b, c, t), t);
22 | }
23 |
24 | public static float quadraticAngle(float a, float b, float c, float t){
25 | return linearAngle(linearAngle(a, b, t), linearAngle(b, c, t), t);
26 | }
27 |
28 | public static float cubic(float a, float b, float c, float d, float t){
29 | return linear(quadratic(a, b, c, t), quadratic(b, c, d, t), t);
30 | }
31 |
32 | public static float cubicAngle(float a, float b, float c, float d, float t){
33 | return linearAngle(quadraticAngle(a, b, c, t), quadraticAngle(b, c, d, t), t);
34 | }
35 |
36 | public static float quartic(float a, float b, float c, float d, float e, float t){
37 | return linear(cubic(a, b, c, d, t), cubic(b, c, d, e, t), t);
38 | }
39 |
40 | public static float quarticAngle(float a, float b, float c, float d, float e, float t){
41 | return linearAngle(cubicAngle(a, b, c, d, t), cubicAngle(b, c, d, e, t), t);
42 | }
43 |
44 | public static float quintic(float a, float b, float c, float d, float e, float f, float t){
45 | return linear(quartic(a, b, c, d, e, t), quartic(b, c, d, e, f, t), t);
46 | }
47 |
48 | public static float quinticAngle(float a, float b, float c, float d, float e, float f, float t){
49 | return linearAngle(quarticAngle(a, b, c, d, e, t), quarticAngle(b, c, d, e, f, t), t);
50 | }
51 |
52 | public static float bezier(float t, float x1, float x2, float x3,float x4){
53 | return bezier0(t)*x1 + bezier1(t)*x2 + bezier2(t)*x3 + bezier3(t)*x4;
54 | }
55 |
56 | private static float bezier0(float t){
57 | float temp = t*t;
58 | return -temp*t + 3*temp - 3*t + 1;
59 | }
60 |
61 | private static float bezier1(float t){
62 | float temp = t*t;
63 | return 3*t*temp - 6*temp + 3*t;
64 | }
65 |
66 | private static float bezier2(float t){
67 | float temp = t*t;
68 | return -3*temp*t+3*temp;
69 | }
70 |
71 | private static float bezier3(float t){
72 | return t*t*t;
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if %ERRORLEVEL% equ 0 goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if %ERRORLEVEL% equ 0 goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | set EXIT_CODE=%ERRORLEVEL%
84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
86 | exit /b %EXIT_CODE%
87 |
88 | :mainEnd
89 | if "%OS%"=="Windows_NT" endlocal
90 |
91 | :omega
92 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/CCDResolver.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import com.brashmonkey.spriter.Mainline.Key.BoneRef;
4 | import com.brashmonkey.spriter.Timeline.Key.Bone;
5 |
6 | /**
7 | * An inverse kinematics resolver implementation.
8 | * An instance of this class uses the CCD (Cyclic Coordinate Descent) algorithm to resolve the constraints.
9 | * @see ccd-algorithm
10 | * and cyclic-coordinate-descent-in-2d .
11 | * @author Trixt0r
12 | *
13 | */
14 | public class CCDResolver extends IKResolver {
15 |
16 | public CCDResolver(Player player) {
17 | super(player);
18 | }
19 |
20 | @Override
21 | public void resolve(float x, float y, int chainLength, BoneRef effectorRef) {
22 | //player.unmapObjects(null);
23 | Timeline timeline = player.animation.getTimeline(effectorRef.timeline);
24 | Timeline.Key key = player.tweenedKeys[effectorRef.timeline];
25 | Timeline.Key unmappedKey = player.unmappedTweenedKeys[effectorRef.timeline];
26 | Bone effector = key.object();
27 | Bone unmappedffector = unmappedKey.object();
28 | float width = (timeline.objectInfo != null) ? timeline.objectInfo.size.width: 200;
29 | width *= unmappedffector.scale.x;
30 | float xx = unmappedffector.position.x+(float)Math.cos(Math.toRadians(unmappedffector.angle))*width,
31 | yy = unmappedffector.position.y+(float)Math.sin(Math.toRadians(unmappedffector.angle))*width;
32 | if(Calculator.distanceBetween(xx, yy, x, y) <= this.tolerance)
33 | return;
34 |
35 | effector.angle = Calculator.angleBetween(unmappedffector.position.x, unmappedffector.position.y, x, y);
36 | if(Math.signum(player.root.scale.x) == -1) effector.angle += 180f;
37 | BoneRef parentRef = effectorRef.parent;
38 | Bone parent = null, unmappedParent = null;
39 | if(parentRef != null){
40 | parent = player.tweenedKeys[parentRef.timeline].object();
41 | unmappedParent = player.unmappedTweenedKeys[parentRef.timeline].object();
42 | effector.angle -= unmappedParent.angle;
43 | }
44 | player.unmapObjects(null);
45 | for(int i = 0; i < chainLength && parentRef != null; i++){
46 | if(Calculator.distanceBetween(xx, yy, x, y) <= this.tolerance)
47 | return;
48 | parent.angle += Calculator.angleDifference(Calculator.angleBetween(unmappedParent.position.x, unmappedParent.position.y, x, y),
49 | Calculator.angleBetween(unmappedParent.position.x, unmappedParent.position.y, xx, yy));
50 | parentRef = parentRef.parent;
51 | if(parentRef != null && i < chainLength-1){
52 | parent = player.tweenedKeys[parentRef.timeline].object();
53 | unmappedParent = player.unmappedTweenedKeys[parentRef.timeline].object();
54 | parent.angle -= unmappedParent.angle;
55 | }
56 | else parent = null;
57 | player.unmapObjects(null);
58 | xx = unmappedffector.position.x+(float)Math.cos(Math.toRadians(unmappedffector.angle))*width;
59 | yy = unmappedffector.position.y+(float)Math.sin(Math.toRadians(unmappedffector.angle))*width;
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/resources/test.json:
--------------------------------------------------------------------------------
1 | {
2 | "frames": {
3 | "0.png": {
4 | "frame": {
5 | "h": 224,
6 | "w": 256,
7 | "x": 768,
8 | "y": 0
9 | },
10 | "rotated": "false",
11 | "sourceSize": {
12 | "h": 224,
13 | "w": 256
14 | },
15 | "spriteSourceSize": {
16 | "h": 224,
17 | "w": 256,
18 | "x": 0,
19 | "y": 0
20 | },
21 | "trimmed": "true"
22 | },
23 | "1.png": {
24 | "frame": {
25 | "h": 224,
26 | "w": 256,
27 | "x": 0,
28 | "y": 0
29 | },
30 | "rotated": "false",
31 | "sourceSize": {
32 | "h": 224,
33 | "w": 256
34 | },
35 | "spriteSourceSize": {
36 | "h": 224,
37 | "w": 256,
38 | "x": 0,
39 | "y": 0
40 | },
41 | "trimmed": "true"
42 | },
43 | "2.png": {
44 | "frame": {
45 | "h": 224,
46 | "w": 256,
47 | "x": 768,
48 | "y": 224
49 | },
50 | "rotated": "false",
51 | "sourceSize": {
52 | "h": 224,
53 | "w": 256
54 | },
55 | "spriteSourceSize": {
56 | "h": 224,
57 | "w": 256,
58 | "x": 0,
59 | "y": 0
60 | },
61 | "trimmed": "true"
62 | },
63 | "3.png": {
64 | "frame": {
65 | "h": 224,
66 | "w": 256,
67 | "x": 256,
68 | "y": 224
69 | },
70 | "rotated": "false",
71 | "sourceSize": {
72 | "h": 224,
73 | "w": 256
74 | },
75 | "spriteSourceSize": {
76 | "h": 224,
77 | "w": 256,
78 | "x": 0,
79 | "y": 0
80 | },
81 | "trimmed": "true"
82 | },
83 | "4.png": {
84 | "frame": {
85 | "h": 224,
86 | "w": 256,
87 | "x": 512,
88 | "y": 224
89 | },
90 | "rotated": "false",
91 | "sourceSize": {
92 | "h": 224,
93 | "w": 256
94 | },
95 | "spriteSourceSize": {
96 | "h": 224,
97 | "w": 256,
98 | "x": 0,
99 | "y": 0
100 | },
101 | "trimmed": "true"
102 | },
103 | "5.png": {
104 | "frame": {
105 | "h": 224,
106 | "w": 256,
107 | "x": 512,
108 | "y": 0
109 | },
110 | "rotated": "false",
111 | "sourceSize": {
112 | "h": 224,
113 | "w": 256
114 | },
115 | "spriteSourceSize": {
116 | "h": 224,
117 | "w": 256,
118 | "x": 0,
119 | "y": 0
120 | },
121 | "trimmed": "true"
122 | },
123 | "6.png": {
124 | "frame": {
125 | "h": 224,
126 | "w": 256,
127 | "x": 0,
128 | "y": 224
129 | },
130 | "rotated": "false",
131 | "sourceSize": {
132 | "h": 224,
133 | "w": 256
134 | },
135 | "spriteSourceSize": {
136 | "h": 224,
137 | "w": 256,
138 | "x": 0,
139 | "y": 0
140 | },
141 | "trimmed": "true"
142 | },
143 | "7.png": {
144 | "frame": {
145 | "h": 224,
146 | "w": 256,
147 | "x": 256,
148 | "y": 0
149 | },
150 | "rotated": "false",
151 | "sourceSize": {
152 | "h": 224,
153 | "w": 256
154 | },
155 | "spriteSourceSize": {
156 | "h": 224,
157 | "w": 256,
158 | "x": 0,
159 | "y": 0
160 | },
161 | "trimmed": "true"
162 | }
163 | },
164 | "meta": {
165 | "app": "Spriter",
166 | "format": "RGBA8888",
167 | "image": "test.png",
168 | "scale": 1,
169 | "size": {
170 | "h": 448,
171 | "w": 1024
172 | },
173 | "version": "r11"
174 | }
175 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | com.brashmonkey.spriter
4 | spriter
5 | 1.0.1
6 | jar
7 |
8 | Spriter
9 | Generic Java implementation for Spriter.
10 | https://brashmonkey.com/
11 |
12 |
13 |
14 | Apache License, Version 2.0
15 | http://www.apache.org/licenses/LICENSE-2.0.txt
16 | repo
17 |
18 |
19 |
20 |
21 |
22 | internal.repo
23 | Temporary Staging Repository
24 | file://${project.build.directory}/mvn-repo
25 |
26 |
27 |
28 |
29 |
30 | org.json
31 | json
32 | 20180130
33 |
34 |
35 | junit
36 | junit
37 | 4.12
38 | test
39 |
40 |
41 |
42 |
43 |
44 |
45 | maven-deploy-plugin
46 | 2.8.1
47 |
48 | internal.repo::default::file://${project.build.directory}/mvn
49 |
50 |
51 |
52 | com.github.github
53 | site-maven-plugin
54 | 0.12
55 |
56 | Maven artifacts for ${project.version}
57 | true
58 | ${project.build.directory}/mvn
59 | refs/heads/mvn
60 | **/*
61 | spriter
62 | Trixt0r
63 |
64 |
65 |
66 |
67 |
68 | site
69 |
70 | deploy
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | github
80 |
81 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Point.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * A utility class to keep the code short.
5 | * A point is essentially that what you would expect if you think about a point in a 2D space.
6 | * It holds an x and y value. You can {@link #translate(Point)}, {@link #scale(Point)}, {@link #rotate(float)} and {@link #set(Point)} a point.
7 | * @author Trixt0r
8 | *
9 | */
10 | public class Point {
11 |
12 | /**
13 | * The x coordinates of this point.
14 | */
15 | public float x;
16 | /**
17 | * The y coordinates of this point.
18 | */
19 | public float y;
20 |
21 | /**
22 | * Creates a point at (0,0).
23 | */
24 | public Point(){
25 | this(0,0);
26 | }
27 |
28 | /**
29 | * Creates a point at the position of the given point.
30 | * @param point the point to set this point at
31 | */
32 | public Point(Point point){
33 | this(point.x, point.y);
34 | }
35 |
36 | /**
37 | * Creates a point at (x, y).
38 | * @param x the x coordinate
39 | * @param y the y coordinate
40 | */
41 | public Point(float x, float y){
42 | this.set(x, y);
43 | }
44 |
45 | /**
46 | * Sets this point to the given coordinates.
47 | * @param x the x coordinate
48 | * @param y the y coordinate
49 | * @return this point for chained operations
50 | */
51 | public Point set(float x, float y){
52 | this.x = x;
53 | this.y = y;
54 | return this;
55 | }
56 |
57 | /**
58 | * Adds the given amount to this point.
59 | * @param x the amount in x direction to add
60 | * @param y the amount in y direction to add
61 | * @return this point for chained operations
62 | */
63 | public Point translate(float x, float y){
64 | return this.set(this.x+x, this.y+y);
65 | }
66 |
67 | /**
68 | * Scales this point by the given amount.
69 | * @param x the scale amount in x direction
70 | * @param y the scale amount in y direction
71 | * @return this point for chained operations
72 | */
73 | public Point scale(float x, float y){
74 | return this.set(this.x*x, this.y*y);
75 | }
76 |
77 | /**
78 | * Sets this point to the given point.
79 | * @param point the new coordinates
80 | * @return this point for chained operations
81 | */
82 | public Point set(Point point){
83 | return this.set(point.x, point.y);
84 | }
85 |
86 | /**
87 | * Adds the given amount to this point.
88 | * @param amount the amount to add
89 | * @return this point for chained operations
90 | */
91 | public Point translate(Point amount){
92 | return this.translate(amount.x, amount.y);
93 | }
94 |
95 | /**
96 | * Scales this point by the given amount.
97 | * @param amount the amount to scale
98 | * @return this point for chained operations
99 | */
100 | public Point scale(Point amount){
101 | return this.scale(amount.x, amount.y);
102 | }
103 |
104 | /**
105 | * Rotates this point around (0,0) by the given amount of degrees.
106 | * @param degrees the angle to rotate this point
107 | * @return this point for chained operations
108 | */
109 | public Point rotate(float degrees){
110 | if(x != 0 || y != 0){
111 | float cos = Calculator.cosDeg(degrees);
112 | float sin = Calculator.sinDeg(degrees);
113 |
114 | float xx = x*cos-y*sin;
115 | float yy = x*sin+y*cos;
116 |
117 | this.x = xx;
118 | this.y = yy;
119 | }
120 | return this;
121 | }
122 |
123 | /**
124 | * Returns a copy of this point with the current set values.
125 | * @return a copy of this point
126 | */
127 | public Point copy(){
128 | return new Point(x,y);
129 | }
130 |
131 | public String toString(){
132 | return "["+x+","+y+"]";
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/IKResolver.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map.Entry;
5 |
6 | import com.brashmonkey.spriter.Mainline.Key.BoneRef;
7 | import com.brashmonkey.spriter.Timeline.Key.Bone;
8 |
9 | /**
10 | * A IKResolver is responsible for resolving previously set constraints.
11 | * @see Inverse kinematics
12 | * @author Trixt0r
13 | *
14 | */
15 | public abstract class IKResolver {
16 |
17 | /**
18 | * Resolves the inverse kinematics constraint with a specific algtorithm
19 | * @param x the target x value
20 | * @param y the target y value
21 | * @param chainLength number of parents which are affected
22 | * @param effector the actual effector where the resolved information has to be stored in.
23 | */
24 | protected abstract void resolve(float x, float y, int chainLength, BoneRef effector);
25 |
26 | protected HashMap ikMap;
27 | protected float tolerance;
28 | protected Player player;
29 |
30 | /**
31 | * Creates a resolver with a default tolerance of 5f.
32 | */
33 | public IKResolver(Player player) {
34 | this.tolerance = 5f;
35 | this.ikMap = new HashMap();
36 | this.setPlayer(player);
37 | }
38 |
39 | /**
40 | * Sets the player for this resolver.
41 | * @param player the player which gets affected.
42 | * @throws SpriterException if player is null
43 | */
44 | public void setPlayer(Player player){
45 | if(player == null) throw new SpriterException("player cannot be null!");
46 | this.player = player;
47 | }
48 |
49 | /**
50 | * Returns the current set player.
51 | * @return the current player.
52 | */
53 | public Player getPlayer(){
54 | return this.player;
55 | }
56 |
57 | /**
58 | * Resolves the inverse kinematics constraints with the implemented algorithm in {@link #resolve(float, float, int, BoneRef)}.
59 | */
60 | public void resolve(){
61 | for(Entry entry: this.ikMap.entrySet()){
62 | for(int j = 0; j < entry.getKey().iterations; j++)
63 | this.resolve(entry.getKey().x, entry.getKey().y, entry.getKey().chainLength, entry.getValue());
64 | }
65 | }
66 |
67 | /**
68 | * Adds the given object to the internal IKObject - Bone map.
69 | * This means, the values of the given ik object affect the mapped bone.
70 | * @param ikObject the ik object
71 | * @param boneRef the bone reference which gets affected
72 | */
73 | public void mapIKObject(IKObject ikObject, BoneRef boneRef){
74 | this.ikMap.put(ikObject, boneRef);
75 | }
76 |
77 | /**
78 | * Adds the given object to the internal IKObject - Bone map.
79 | * This means, the values of the given ik object affect the mapped bone.
80 | * @param ikObject the ik object
81 | * @param bone the bone which gets affected
82 | */
83 | public void mapIKObject(IKObject ikObject, Bone bone){
84 | this.ikMap.put(ikObject, player.getBoneRef(bone));
85 | }
86 |
87 | /**
88 | * Removes the given object from the internal map.
89 | * @param ikObject the ik object to remove
90 | */
91 | public void unmapIKObject(IKObject ikObject){
92 | this.ikMap.remove(ikObject);
93 | }
94 |
95 | /**
96 | * Returns the tolerance of this resolver.
97 | * @return the tolerance
98 | */
99 | public float getTolerance() {
100 | return tolerance;
101 | }
102 |
103 | /**
104 | * Sets the tolerance distance of this resolver.
105 | * The resolver should stop the algorithm if the distance to the set ik object is less than the tolerance.
106 | * @param tolerance the tolerance
107 | */
108 | public void setTolerance(float tolerance) {
109 | this.tolerance = tolerance;
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Loader.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import java.util.HashMap;
4 |
5 | /**
6 | * A loader is responsible for loading all resources.
7 | * Since this library is meant to be as generic as possible, it cannot be assumed how to load a resource. Because of this this class has to be abstract.
8 | * This class takes care of loading all resources a {@link Data} instance contains.
9 | * To load all resources an instance relies on {@link #loadResource(FileReference)} which has to implemented with the backend specific methods.
10 | *
11 | * @author Trixt0r
12 | *
13 | * @param The backend specific resource. In general such a resource is called "sprite", "texture" or "image".
14 | */
15 | public abstract class Loader {
16 |
17 | /**
18 | * Contains all loaded resources if not {@link #isDisposed()}.
19 | */
20 | protected final HashMap resources;
21 |
22 | /**
23 | * The current set data containing {@link Folder}s and {@link File}s.
24 | */
25 | protected Data data;
26 |
27 | /**
28 | * The root path to the previous loaded Spriter SCML file.
29 | */
30 | protected String root = "";
31 |
32 | private boolean disposed;
33 |
34 | /**
35 | * Creates a loader with the given Spriter data.
36 | * @param data the generated Spriter data
37 | */
38 | public Loader(Data data){
39 | this.data = data;
40 | this.resources = new HashMap(100);
41 | }
42 |
43 | /**
44 | * Loads a resource.
45 | * The path to the file can be resolved with {@link #root} and {@link #data}.
46 | * I recommend using {@link Data#getFile(FileReference)}. Then the path to the resource is {@link File#name} relative to {@link #root}.
47 | * @param ref the reference to load
48 | * @return the loaded resource
49 | */
50 | protected abstract R loadResource(FileReference ref);
51 |
52 | /**
53 | * Called when all resources from {@link #data} have been loaded.
54 | */
55 | protected void finishLoading(){}
56 |
57 | /**
58 | * Called before all resources get loaded.
59 | */
60 | protected void beginLoading(){}
61 |
62 | /**
63 | * Loads all resources indicated by {@link #data}.
64 | * @param root the root folder of the previously loaded Spriter SCML file
65 | */
66 | public void load(String root){
67 | this.root = root;
68 | this.beginLoading();
69 | for(Folder folder: data.folders){
70 | for(File file: folder.files){
71 | //if(new java.io.File(root+"/"+file.name).exists()){
72 | FileReference ref = new FileReference(folder.id, file.id);
73 | this.resources.put(ref, this.loadResource(ref));
74 | //}
75 | }
76 | }
77 | this.disposed = false;
78 | this.finishLoading();
79 | }
80 |
81 | /**
82 | * Loads all resources indicated by {@link #data}.
83 | * @param file the previously loaded Spriter SCML file
84 | */
85 | public void load(java.io.File file){
86 | this.load(file.getParent());
87 | }
88 |
89 | /**
90 | * Returns a resource the given reference is pointing to.
91 | * @param ref the reference pointing to a resource
92 | * @return the resource or null if the resource is not loaded yet.
93 | */
94 | public R get(FileReference ref){
95 | return this.resources.get(ref);
96 | }
97 |
98 | /**
99 | * Removes all loaded resources from the internal reference-resource map.
100 | * Override this method and dispose all your resources. After that call {@link #dispose()} of the super class.
101 | */
102 | public void dispose(){
103 | resources.clear();
104 | data = null;
105 | root = "";
106 | disposed = true;
107 | }
108 |
109 | /**
110 | * Returns whether this loader has been disposed or not.
111 | * @return true if this loader is disposed
112 | */
113 | public boolean isDisposed(){
114 | return disposed;
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Rectangle.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * Represents a 2D rectangle with left, top, right and bottom bounds.
5 | * A rectangle is responsible for calculating its own size and checking if a point is inside it or if it is intersecting with another rectangle.
6 | * @author Trixt0r
7 | *
8 | */
9 | public class Rectangle {
10 |
11 | /**
12 | * Belongs to the bounds of this rectangle.
13 | */
14 | public float left, top, right, bottom;
15 | /**
16 | * The size of this rectangle.
17 | */
18 | public final Dimension size;
19 |
20 | /**
21 | * Creates a rectangle with the given bounds.
22 | * @param left left bounding
23 | * @param top top bounding
24 | * @param right right bounding
25 | * @param bottom bottom bounding
26 | */
27 | public Rectangle(float left, float top, float right, float bottom){
28 | this.set(left, top, right, bottom);
29 | this.size = new Dimension(0, 0);
30 | this.calculateSize();
31 | }
32 |
33 | /**
34 | * Creates a rectangle with the bounds of the given rectangle.
35 | * @param rect rectangle containing the bounds.
36 | */
37 | public Rectangle(Rectangle rect){
38 | this(rect.left, rect.top, rect.right, rect.bottom);
39 | }
40 |
41 | /**
42 | * Returns whether the given point (x,y) is inside this rectangle.
43 | * @param x the x coordinate
44 | * @param y the y coordinate
45 | * @return true if (x,y) is inside
46 | */
47 | public boolean isInside(float x, float y){
48 | return x >= this.left && x <= this.right && y <= this.top && y >= this.bottom;
49 | }
50 |
51 | /**
52 | * Returns whether the given point is inside this rectangle.
53 | * @param point the point
54 | * @return true if the point is inside
55 | */
56 | public boolean isInside(Point point){
57 | return isInside(point.x, point.y);
58 | }
59 |
60 | /**
61 | * Calculates the size of this rectangle.
62 | */
63 | public void calculateSize(){
64 | this.size.set(right-left, top-bottom);
65 | }
66 |
67 | /**
68 | * Sets the bounds of this rectangle to the bounds of the given rectangle.
69 | * @param rect rectangle containing the bounds.
70 | */
71 | public void set(Rectangle rect){
72 | if(rect == null) return;
73 | this.bottom = rect.bottom;
74 | this.left = rect.left;
75 | this.right = rect.right;
76 | this.top = rect.top;
77 | this.calculateSize();
78 | }
79 |
80 | /**
81 | * Sets the bounds of this rectangle to the given bounds.
82 | * @param left left bounding
83 | * @param top top bounding
84 | * @param right right bounding
85 | * @param bottom bottom bounding
86 | */
87 | public void set(float left, float top, float right, float bottom){
88 | this.left = left;
89 | this.top = top;
90 | this.right = right;
91 | this.bottom = bottom;
92 | }
93 |
94 | /**
95 | * Returns whether the given two rectangles are intersecting.
96 | * @param rect1 the first rectangle
97 | * @param rect2 the second rectangle
98 | * @return true if the rectangles are intersecting
99 | */
100 | public static boolean areIntersecting(Rectangle rect1, Rectangle rect2){
101 | return rect1.isInside(rect2.left, rect2.top) || rect1.isInside(rect2.right, rect2.top)
102 | || rect1.isInside(rect2.left, rect2.bottom) || rect1.isInside(rect2.right, rect2.bottom);
103 | }
104 |
105 | /**
106 | * Creates a bigger rectangle of the given two and saves it in the target.
107 | * @param rect1 the first rectangle
108 | * @param rect2 the second rectangle
109 | * @param target the target to save the new bounds.
110 | */
111 | public static void setBiggerRectangle(Rectangle rect1, Rectangle rect2, Rectangle target){
112 | target.left = Math.min(rect1.left, rect2.left);
113 | target.bottom = Math.min(rect1.bottom, rect2.bottom);
114 | target.right = Math.max(rect1.right, rect2.right);
115 | target.top = Math.max(rect1.top, rect2.top);
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Box.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import com.brashmonkey.spriter.Entity.ObjectInfo;
4 |
5 | /**
6 | * Represents a box, which consists of four points: top-left, top-right, bottom-left and bottom-right.
7 | * A box is responsible for checking collisions and calculating a bounding box for a {@link Timeline.Key.Bone}.
8 | * @author Trixt0r
9 | *
10 | */
11 | public class Box {
12 | public final Point[] points;
13 | private Rectangle rect;
14 |
15 | /**
16 | * Creates a new box with no witdh and height.
17 | */
18 | public Box(){
19 | this.points = new Point[4];
20 | //this.temp = new Point[4];
21 | for(int i = 0; i < 4; i++){
22 | this.points[i] = new Point(0,0);
23 | //this.temp[i] = new Point(0,0);
24 | }
25 | this.rect = new Rectangle(0,0,0,0);
26 | }
27 |
28 | /**
29 | * Calculates its four points for the given bone or object with the given info.
30 | * @param boneOrObject the bone or object
31 | * @param info the info
32 | * @throws NullPointerException if info or boneOrObject is null
33 | */
34 | public void calcFor(Timeline.Key.Bone boneOrObject, ObjectInfo info){
35 | float width = info.size.width*boneOrObject.scale.x;
36 | float height = info.size.height*boneOrObject.scale.y;
37 |
38 | float pivotX = width*boneOrObject.pivot.x;
39 | float pivotY = height*boneOrObject.pivot.y;
40 |
41 | this.points[0].set(-pivotX,-pivotY);
42 | this.points[1].set(width-pivotX, -pivotY);
43 | this.points[2].set(-pivotX,height-pivotY);
44 | this.points[3].set(width-pivotX,height-pivotY);
45 |
46 | for(int i = 0; i < 4; i++)
47 | this.points[i].rotate(boneOrObject.angle);
48 | for(int i = 0; i < 4; i++)
49 | this.points[i].translate(boneOrObject.position);
50 | }
51 |
52 | /**
53 | * Returns whether the given coordinates lie inside the box of the given bone or object.
54 | * @param boneOrObject the bone or object
55 | * @param info the object info of the given bone or object
56 | * @param x the x coordinate
57 | * @param y the y coordinate
58 | * @return true if the given point lies in the box
59 | * @throws NullPointerException if info or boneOrObject is null
60 | */
61 | public boolean collides(Timeline.Key.Bone boneOrObject, ObjectInfo info, float x, float y){
62 | float width = info.size.width*boneOrObject.scale.x;
63 | float height = info.size.height*boneOrObject.scale.y;
64 |
65 | float pivotX = width*boneOrObject.pivot.x;
66 | float pivotY = height*boneOrObject.pivot.y;
67 |
68 | Point point = new Point(x-boneOrObject.position.x,y-boneOrObject.position.y);
69 | point.rotate(-boneOrObject.angle);
70 |
71 | return point.x >= -pivotX && point.x <= width-pivotX && point.y >= -pivotY && point.y <= height-pivotY;
72 | }
73 |
74 | /**
75 | * Returns whether this box is inside the given rectangle.
76 | * @param rect the rectangle
77 | * @return true if one of the four points is inside the rectangle
78 | */
79 | public boolean isInside(Rectangle rect){
80 | boolean inside = false;
81 | for(Point p: points)
82 | inside |= rect.isInside(p);
83 | return inside;
84 | }
85 |
86 | /**
87 | * Returns a bounding box for this box.
88 | * @return the bounding box
89 | */
90 | public Rectangle getBoundingRect(){
91 | this.rect.set(points[0].x,points[0].y,points[0].x,points[0].y);
92 | this.rect.left = Math.min(Math.min(Math.min(Math.min(points[0].x, points[1].x),points[2].x),points[3].x), this.rect.left);
93 | this.rect.right = Math.max(Math.max(Math.max(Math.max(points[0].x, points[1].x),points[2].x),points[3].x), this.rect.right);
94 | this.rect.top = Math.max(Math.max(Math.max(Math.max(points[0].y, points[1].y),points[2].y),points[3].y), this.rect.top);
95 | this.rect.bottom = Math.min(Math.min(Math.min(Math.min(points[0].y, points[1].y),points[2].y),points[3].y), this.rect.bottom);
96 | return this.rect;
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/PlayerTweener.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * A player tweener is responsible for tweening to {@link Player} instances.
5 | * Such a
6 | * @author Trixt0r
7 | *
8 | */
9 | public class PlayerTweener extends Player{
10 |
11 | private TweenedAnimation anim;
12 | private Player player1, player2;
13 | /**
14 | * Indicates whether to update the {@link Player} instances this instance is holding.
15 | * If this variable is set to false, you will have to call {@link Player#update()} on your own.
16 | */
17 | public boolean updatePlayers = true;
18 |
19 | /**
20 | * The name of root bone to start the tweening at.
21 | * Set it to null to tween the whole hierarchy.
22 | */
23 | public String baseBoneName = null;
24 |
25 | /**
26 | * Creates a player tweener which will tween the given two players.
27 | * @param player1 the first player
28 | * @param player2 the second player
29 | */
30 | public PlayerTweener(Player player1, Player player2){
31 | super(player1.getEntity());
32 | this.setPlayers(player1, player2);
33 | }
34 |
35 | /**
36 | * Creates a player tweener based on the entity.
37 | * The players to tween will be created by this instance.
38 | * @param entity the entity the players will animate
39 | */
40 | public PlayerTweener(Entity entity){
41 | this(new Player(entity), new Player(entity));
42 | }
43 |
44 | /**
45 | * Tweens the current set players.
46 | * This method will update the set players if {@link #updatePlayers} is true.
47 | * @throws SpriterException if no bone with {@link #baseBoneName} exists
48 | */
49 | @Override
50 | public void update(){
51 | if(updatePlayers){
52 | player1.update();
53 | player2.update();
54 | }
55 | anim.setAnimations(player1.animation, player2.animation);
56 | super.update();
57 | if(baseBoneName != null){
58 | int index = anim.onFirstMainLine()? player1.getBoneIndex(baseBoneName) : player2.getBoneIndex(baseBoneName);
59 | if(index == -1) throw new SpriterException("A bone with name \""+baseBoneName+"\" does no exist!");
60 | anim.base = anim.getCurrentKey().getBoneRef(index);
61 | super.update();
62 | }
63 | }
64 |
65 | /**
66 | * Sets the players for this tweener.
67 | * Both players have to hold the same {@link Entity}
68 | * @param player1 the first player
69 | * @param player2 the second player
70 | */
71 | public void setPlayers(Player player1, Player player2){
72 | if(player1.entity != player2.entity)
73 | throw new SpriterException("player1 and player2 have to hold the same entity!");
74 | this.player1 = player1;
75 | this.player2 = player2;
76 | if(player1.entity == entity) return;
77 | this.anim = new TweenedAnimation(player1.getEntity());
78 | anim.setAnimations(player1.animation, player2.animation);
79 | super.setEntity(player1.getEntity());
80 | super.setAnimation(anim);
81 | }
82 |
83 | /**
84 | * Returns the first set player.
85 | * @return the first player
86 | */
87 | public Player getFirstPlayer(){
88 | return this.player1;
89 | }
90 |
91 | /**
92 | * Returns the second set player.
93 | * @return the second player
94 | */
95 | public Player getSecondPlayer(){
96 | return this.player2;
97 | }
98 |
99 | /**
100 | * Sets the interpolation weight of this tweener.
101 | * @param weight the interpolation weight between 0.0f and 1.0f
102 | */
103 | public void setWeight(float weight){
104 | this.anim.weight = weight;
105 | }
106 |
107 | /**
108 | * Returns the interpolation weight.
109 | * @return the interpolation weight between 0.0f and 1.0f
110 | */
111 | public float getWeight(){
112 | return this.anim.weight;
113 | }
114 |
115 |
116 | /**
117 | * Sets the base animation of this tweener.
118 | * Has only an effect if {@link #baseBoneName} is not null.
119 | * @param anim the base animation
120 | */
121 | public void setBaseAnimation(Animation anim){
122 | this.anim.baseAnimation = anim;
123 | }
124 |
125 | /**
126 | * Sets the base animation of this tweener by the given animation index.
127 | * Has only an effect if {@link #baseBoneName} is not null.
128 | * @param index the index of the base animation
129 | */
130 | public void setBaseAnimation(int index){
131 | this.setBaseAnimation(entity.getAnimation(index));
132 | }
133 |
134 | /**
135 | * Sets the base animation of this tweener by the given name.
136 | * Has only an effect if {@link #baseBoneName} is not null.
137 | * @param name the name of the base animation
138 | */
139 | public void setBaseAnimation(String name){
140 | this.setBaseAnimation(entity.getAnimation(name));
141 | }
142 |
143 | /**
144 | * Returns the base animation if this tweener.
145 | * @return the base animation
146 | */
147 | public Animation getBaseAnimation(){
148 | return this.anim.baseAnimation;
149 | }
150 |
151 | /**
152 | * Not supported by this class.
153 | */
154 | @Override
155 | public void setAnimation(Animation anim){}
156 |
157 | /**
158 | * Not supported by this class.
159 | */
160 | @Override
161 | public void setEntity(Entity entity){}
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Data.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 |
4 | /**
5 | * Represents all the data which necessary to animate a Spriter generated SCML file.
6 | * An instance of this class holds {@link Folder}s and {@link Entity} instances.
7 | * Specific {@link Folder} and {@link Entity} instances can be accessed via the corresponding methods, i.e. getEntity()
8 | * and getFolder().
9 | * @author Trixt0r
10 | *
11 | */
12 | public class Data {
13 |
14 | /**
15 | * Represents the rendering mode stored in the spriter data root.
16 | */
17 | public enum PixelMode {
18 | NONE, PIXEL_ART;
19 |
20 | /**
21 | * @param mode
22 | * @return The pixel mode for the given int value. Default is {@link PixelMode#NONE}.
23 | */
24 | public static PixelMode get(int mode) {
25 | switch (mode) {
26 | case 1: return PIXEL_ART;
27 | default: return NONE;
28 | }
29 | }
30 | }
31 |
32 | final Folder[] folders;
33 | final Entity[] entities;
34 | private int folderPointer = 0, entityPointer = 0;
35 | public final String scmlVersion, generator, generatorVersion;
36 | public final PixelMode pixelMode;
37 |
38 |
39 | Data(String scmlVersion, String generator, String generatorVersion, PixelMode pixelMode, int folders, int entities){
40 | this.scmlVersion = scmlVersion;
41 | this.generator = generator;
42 | this.generatorVersion = generatorVersion;
43 | this.pixelMode = pixelMode;
44 | this.folders = new Folder[folders];
45 | this.entities = new Entity[entities];
46 | }
47 |
48 | /**
49 | * Adds a folder to this data.
50 | * @param folder the folder to add
51 | */
52 | void addFolder(Folder folder){
53 | this.folders[folderPointer++] = folder;
54 | }
55 |
56 | /**
57 | * Adds an entity to this data.
58 | * @param entity the entity to add
59 | */
60 | void addEntity(Entity entity){
61 | this.entities[entityPointer++] = entity;
62 | }
63 |
64 | /**
65 | * Returns a {@link Folder} instance with the given name.
66 | * @param name the name of the folder
67 | * @return the folder with the given name or null if no folder with the given name exists
68 | */
69 | public Folder getFolder(String name){
70 | int index = getFolderIndex(name);
71 | if(index >= 0) return getFolder(index);
72 | else return null;
73 | }
74 |
75 | /**
76 | * Returns a folder index with the given name.
77 | * @param name name of the folder
78 | * @return the folder index of the Folder with the given name or -1 if no folder with the given name exists
79 | */
80 | int getFolderIndex(String name){
81 | for(Folder folder: this.folders)
82 | if(folder.name.equals(name)) return folder.id;
83 | return -1;
84 | }
85 |
86 | /**
87 | * Returns a {@link Folder} instance at the given index.
88 | * @param index the index of the folder
89 | * @return the {@link Folder} instance at the given index
90 | */
91 | Folder getFolder(int index){
92 | return this.folders[index];
93 | }
94 |
95 | /**
96 | * Returns an {@link Entity} instance with the given index.
97 | * @param index index of the entity to return.
98 | * @return the entity with the given index
99 | */
100 | public Entity getEntity(int index){
101 | return this.entities[index];
102 | }
103 |
104 | /**
105 | * Returns an {@link Entity} instance with the given name.
106 | * @param name the name of the entity
107 | * @return the entity with the given name or null if no entity with the given name exists
108 | */
109 | public Entity getEntity(String name){
110 | int index = getEntityIndex(name);
111 | if(index >= 0) return getEntity(index);
112 | else return null;
113 | }
114 |
115 | /**
116 | * Returns an entity index with the given name.
117 | * @param name name of the entity
118 | * @return the entity index of the entity with the given name or -1 if no entity with the given name exists
119 | */
120 | int getEntityIndex(String name){
121 | for(Entity entity: this.entities)
122 | if(entity.name.equals(name)) return entity.id;
123 | return -1;
124 | }
125 |
126 | /**
127 | * Returns a {@link File} instance in the given {@link Folder} instance at the given file index.
128 | * @param folder {@link Folder} instance to search in.
129 | * @param file index of the file
130 | * @return the {@link File} instance in the given folder at the given file index
131 | */
132 | public File getFile(Folder folder, int file){
133 | return folder.getFile(file);
134 | }
135 |
136 | /**
137 | * Returns a {@link File} instance in the given folder at the given file index.
138 | * @param folder index of the folder
139 | * @param file index of the file
140 | * @return the {@link File} instance in the given folder at the given file index
141 | */
142 | public File getFile(int folder, int file){
143 | return getFile(this.getFolder(folder), file);
144 | }
145 |
146 | /**
147 | * Returns a {@link File} instance for the given {@link FileReference} instance.
148 | * @param ref reference to the file
149 | * @return the {@link File} instance for the given reference
150 | */
151 | public File getFile(FileReference ref){
152 | return this.getFile(ref.folder, ref.file);
153 | }
154 |
155 | /**
156 | * @return The string representation of this spriter data
157 | */
158 | public String toString(){
159 | String toReturn = getClass().getSimpleName() +
160 | "|[Version: " + scmlVersion +
161 | ", Generator: " + generator +
162 | " (" + generatorVersion + ")]";
163 | for(Folder folder: folders)
164 | toReturn += "\n"+folder;
165 | for(Entity entity: entities)
166 | toReturn += "\n"+entity;
167 | toReturn+="]";
168 | return toReturn;
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Curve.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import static com.brashmonkey.spriter.Calculator.*;
4 | import static com.brashmonkey.spriter.Interpolator.*;
5 |
6 | /**
7 | * Represents a curve in a Spriter SCML file.
8 | * An instance of this class is responsible for tweening given data.
9 | * The most important method of this class is {@link #tween(float, float, float)}.
10 | * Curves can be changed with sub curves {@link Curve#subCurve}.
11 | * @author Trixt0r
12 | *
13 | */
14 | public class Curve {
15 |
16 | /**
17 | * Represents a curve type in a Spriter SCML file.
18 | * @author Trixt0r
19 | *
20 | */
21 | public static enum Type {
22 | Instant, Linear, Quadratic, Cubic, Quartic, Quintic, Bezier;
23 | }
24 |
25 | /**
26 | * Returns a curve type based on the given curve name.
27 | * @param name the name of the curve
28 | * @return the curve type. {@link Type#Linear} is returned as a default type.
29 | */
30 | public static Type getType(String name){
31 | if(name.equals("instant")) return Type.Instant;
32 | else if(name.equals("quadratic")) return Type.Quadratic;
33 | else if(name.equals("cubic")) return Type.Cubic;
34 | else if(name.equals("quartic")) return Type.Quartic;
35 | else if(name.equals("quintic")) return Type.Quintic;
36 | else if(name.equals("bezier")) return Type.Bezier;
37 | else return Type.Linear;
38 | }
39 |
40 | private Type type;
41 | /**
42 | * The sub curve of this curve, which can be null.
43 | */
44 | public Curve subCurve;
45 | /**
46 | * The constraints of a curve which will affect a curve of the types different from {@link Type#Linear} and {@link Type#Instant}.
47 | */
48 | public final Constraints constraints = new Constraints(0, 0, 0, 0);
49 |
50 | /**
51 | * Creates a new linear curve.
52 | */
53 | public Curve(){
54 | this(Type.Linear);
55 | }
56 |
57 | /**
58 | * Creates a new curve with the given type.
59 | * @param type the curve type
60 | */
61 | public Curve(Type type){
62 | this(type, null);
63 | }
64 |
65 | /**
66 | * Creates a new curve with the given type and sub cuve.
67 | * @param type the curve type
68 | * @param subCurve the sub curve. Can be null
69 | */
70 | public Curve(Type type, Curve subCurve){
71 | this.setType(type);
72 | this.subCurve = subCurve;
73 | }
74 |
75 | /**
76 | * Sets the type of this curve.
77 | * @param type the curve type.
78 | * @throws SpriterException if the type is null
79 | */
80 | public void setType(Type type){
81 | if(type == null) throw new SpriterException("The type of a curve cannot be null!");
82 | this.type = type;
83 | }
84 |
85 | /**
86 | * Returns the type of this curve.
87 | * @return the curve type
88 | */
89 | public Type getType(){
90 | return this.type;
91 | }
92 |
93 |
94 | private float lastCubicSolution = 0f;
95 | /**
96 | * Returns a new value based on the given values.
97 | * Tweens the weight with the set sub curve.
98 | * @param a the start value
99 | * @param b the end value
100 | * @param t the weight which lies between 0.0 and 1.0
101 | * @return tweened value
102 | */
103 | public float tween(float a, float b, float t){
104 | t = tweenSub(0f,1f,t);
105 | switch(type){
106 | case Instant: return a;
107 | case Linear: return linear(a, b, t);
108 | case Quadratic: return quadratic(a, linear(a, b, constraints.c1), b, t);
109 | case Cubic: return cubic(a, linear(a, b, constraints.c1), linear(a, b, constraints.c2), b, t);
110 | case Quartic: return quartic(a, linear(a, b, constraints.c1), linear(a, b, constraints.c2), linear(a, b, constraints.c3), b, t);
111 | case Quintic: return quintic(a, linear(a, b, constraints.c1), linear(a, b, constraints.c2), linear(a, b, constraints.c3), linear(a, b, constraints.c4), b, t);
112 | case Bezier: float cubicSolution = solveCubic(3f*(constraints.c1-constraints.c3) + 1f, 3f*(constraints.c3-2f*constraints.c1), 3f*constraints.c1, -t);
113 | if(cubicSolution == NO_SOLUTION) cubicSolution = lastCubicSolution;
114 | else lastCubicSolution = cubicSolution;
115 | return linear(a, b, bezier(cubicSolution, 0f, constraints.c2, constraints.c4, 1f));
116 | default: return linear(a, b, t);
117 | }
118 | }
119 |
120 | /**
121 | * Interpolates the given two points with the given weight and saves the result in the target point.
122 | * @param a the start point
123 | * @param b the end point
124 | * @param t the weight which lies between 0.0 and 1.0
125 | * @param target the target point to save the result in
126 | */
127 | public void tweenPoint(Point a, Point b, float t, Point target){
128 | target.set(this.tween(a.x, b.x, t), this.tween(a.y, b.y, t));
129 | }
130 |
131 | private float tweenSub(float a, float b, float t){
132 | if(this.subCurve != null) return subCurve.tween(a, b, t);
133 | else return t;
134 | }
135 |
136 | /**
137 | * Returns a tweened angle based on the given angles, weight and the spin.
138 | * @param a the start angle
139 | * @param b the end angle
140 | * @param t the weight which lies between 0.0 and 1.0
141 | * @param spin the spin, which is either 0, 1 or -1
142 | * @return tweened angle
143 | */
144 | public float tweenAngle(float a, float b, float t, int spin){
145 | if(spin>0){
146 | if(b-a < 0)
147 | b+=360;
148 | }
149 | else if(spin < 0){
150 | if(b-a > 0)
151 | b-=360;
152 | }
153 | else return a;
154 |
155 | return tween(a, b, t);
156 | }
157 |
158 | /**
159 | * @see #tween(float, float, float)
160 | */
161 | public float tweenAngle(float a, float b, float t){
162 | t = tweenSub(0f,1f,t);
163 | switch(type){
164 | case Instant: return a;
165 | case Linear: return linearAngle(a, b, t);
166 | case Quadratic: return quadraticAngle(a, linearAngle(a, b, constraints.c1), b, t);
167 | case Cubic: return cubicAngle(a, linearAngle(a, b, constraints.c1), linearAngle(a, b, constraints.c2), b, t);
168 | case Quartic: return quarticAngle(a, linearAngle(a, b, constraints.c1), linearAngle(a, b, constraints.c2), linearAngle(a, b, constraints.c3), b, t);
169 | case Quintic: return quinticAngle(a, linearAngle(a, b, constraints.c1), linearAngle(a, b, constraints.c2), linearAngle(a, b, constraints.c3), linearAngle(a, b, constraints.c4), b, t);
170 | case Bezier: float cubicSolution = solveCubic(3f*(constraints.c1-constraints.c3) + 1f, 3f*(constraints.c3-2f*constraints.c1), 3f*constraints.c1, -t);
171 | if(cubicSolution == NO_SOLUTION) cubicSolution = lastCubicSolution;
172 | else lastCubicSolution = cubicSolution;
173 | return linearAngle(a, b, bezier(cubicSolution, 0f, constraints.c2, constraints.c4, 1f));
174 | default: return linearAngle(a, b, t);
175 | }
176 | }
177 |
178 | public String toString(){
179 | return getClass().getSimpleName()+"|["+type+":"+constraints+", subCurve: "+subCurve+"]";
180 | }
181 |
182 | /**
183 | * Represents constraints for a curve.
184 | * Constraints are important for curves which have a order higher than 1.
185 | * @author Trixt0r
186 | *
187 | */
188 | public static class Constraints{
189 | public float c1, c2, c3, c4;
190 |
191 | public Constraints(float c1, float c2, float c3, float c4){
192 | this.set(c1, c2, c3, c4);
193 | }
194 |
195 | public void set(float c1, float c2, float c3, float c4){
196 | this.c1 = c1;
197 | this.c2 = c2;
198 | this.c3 = c3;
199 | this.c4 = c4;
200 | }
201 |
202 | public String toString(){
203 | return getClass().getSimpleName()+"| [c1:"+c1+", c2:"+c2+", c3:"+c3+", c4:"+c4+"]";
204 | }
205 | }
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Calculator.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import static java.lang.Math.*;
4 |
5 | /**
6 | * A utility class which provides methods to calculate Spriter specific issues,
7 | * like linear interpolation and rotation around a parent object.
8 | * Other interpolation types are coming with the next releases of Spriter.
9 | *
10 | * @author Trixt0r
11 | *
12 | */
13 |
14 | public class Calculator {
15 |
16 | public final static float PI = (float)Math.PI;
17 | public final static float NO_SOLUTION = -1;
18 |
19 | /**
20 | * Calculates the smallest difference between angle a and b.
21 | * @param a first angle (in degrees)
22 | * @param b second angle (in degrees)
23 | * @return Smallest difference between a and b (between 180� and -180�).
24 | */
25 | public static float angleDifference(float a, float b){
26 | return ((((a - b) % 360) + 540) % 360) - 180;
27 | }
28 |
29 | /**
30 | * @param x1 x coordinate of first point.
31 | * @param y1 y coordinate of first point.
32 | * @param x2 x coordinate of second point.
33 | * @param y2 y coordinate of second point.
34 | * @return Angle between the two given points.
35 | */
36 | public static float angleBetween(float x1, float y1, float x2, float y2){
37 | return (float)toDegrees(atan2(y2-y1,x2-x1));
38 | }
39 |
40 | /**
41 | * @param x1 x coordinate of first point.
42 | * @param y1 y coordinate of first point.
43 | * @param x2 x coordinate of second point.
44 | * @param y2 y coordinate of second point.
45 | * @return Distance between the two given points.
46 | */
47 | public static float distanceBetween(float x1, float y1, float x2, float y2){
48 | float xDiff = x2-x1;
49 | float yDiff = y2-y1;
50 | return (float)sqrt(xDiff*xDiff+yDiff*yDiff);
51 | }
52 |
53 | /**
54 | * Solves the equation a*x^3 + b*x^2 + c*x +d = 0.
55 | * @param a
56 | * @param b
57 | * @param c
58 | * @param d
59 | * @return the solution of the cubic function if it belongs [0, 1], {@link #NO_SOLUTION} otherwise.
60 | */
61 | public static float solveCubic(float a, float b, float c, float d) {
62 | if (a == 0) return solveQuadratic(b, c, d);
63 | if (d == 0) return 0f;
64 |
65 | b /= a;
66 | c /= a;
67 | d /= a;
68 | float squaredB = squared(b);
69 | float q = (3f * c - squaredB) / 9f;
70 | float r = (-27f * d + b * (9f * c - 2f * squaredB)) / 54f;
71 | float disc = cubed(q) + squared(r);
72 | float term1 = b / 3f;
73 |
74 | if (disc > 0) {
75 | float sqrtDisc = sqrt(disc);
76 | float s = r + sqrtDisc;
77 | s = (s < 0) ? -cubicRoot(-s) : cubicRoot(s);
78 | float t = r - sqrtDisc;
79 | t = (t < 0) ? -cubicRoot(-t) : cubicRoot(t);
80 |
81 | float result = -term1 + s + t;
82 | if (result >= 0 && result <= 1) return result;
83 | } else if (disc == 0) {
84 | float r13 = (r < 0) ? -cubicRoot(-r) : cubicRoot(r);
85 |
86 | float result = -term1 + 2f * r13;
87 | if (result >= 0 && result <= 1) return result;
88 |
89 | result = -(r13 + term1);
90 | if (result >= 0 && result <= 1) return result;
91 | } else {
92 | q = -q;
93 | float dum1 = q * q * q;
94 | dum1 = acos(r / sqrt(dum1));
95 | float r13 = 2f * sqrt(q);
96 |
97 | float result = -term1 + r13 * cos(dum1 / 3f);
98 | if (result >= 0 && result <= 1) return result;
99 |
100 | result = -term1 + r13 * cos((dum1 + 2f * PI) / 3f);
101 | if (result >= 0 && result <= 1) return result;
102 |
103 | result = -term1 + r13 * cos((dum1 + 4f * PI) / 3f);
104 | if (result >= 0 && result <= 1) return result;
105 | }
106 |
107 | return NO_SOLUTION;
108 | }
109 |
110 | /**
111 | * Solves the equation a*x^2 + b*x + c = 0
112 | * @param a
113 | * @param b
114 | * @param c
115 | * @return the solution for the quadratic function if it belongs [0, 1], {@link #NO_SOLUTION} otherwise.
116 | */
117 | public static float solveQuadratic(float a, float b, float c) {
118 | float squaredB = squared(b);
119 | float twoA = 2 * a;
120 | float fourAC = 4 * a * c;
121 | float sqrt = sqrt(squaredB - fourAC);
122 | float result = (-b + sqrt) / twoA;
123 | if (result >= 0 && result <= 1) return result;
124 |
125 | result = (-b - sqrt) / twoA;
126 | if (result >= 0 && result <= 1) return result;
127 |
128 | return NO_SOLUTION;
129 | }
130 |
131 | /**
132 | * Returns the square of the given value.
133 | * @param f the value
134 | * @return the square of the value
135 | */
136 | public static float squared(float f) { return f * f; }
137 |
138 | /**
139 | * Returns the cubed value of the given one.
140 | * @param f the value
141 | * @return the cubed value
142 | */
143 | public static float cubed(float f) { return f * f * f; }
144 |
145 | /**
146 | * Returns the cubic root of the given value.
147 | * @param f the value
148 | * @return the cubic root
149 | */
150 | public static float cubicRoot(float f) { return (float) pow(f, 1f / 3f); }
151 |
152 | /**
153 | * Returns the square root of the given value.
154 | * @param x the value
155 | * @return the square root
156 | */
157 | public static float sqrt(float x){ return (float)Math.sqrt(x); }
158 |
159 | /**
160 | * Returns the arc cosine at the given value.
161 | * @param x the value
162 | * @return the arc cosine
163 | */
164 | public static float acos(float x){ return (float)Math.acos(x); }
165 |
166 | static private final int SIN_BITS = 14; // 16KB. Adjust for accuracy.
167 | static private final int SIN_MASK = ~(-1 << SIN_BITS);
168 | static private final int SIN_COUNT = SIN_MASK + 1;
169 |
170 | static private final float radFull = PI * 2;
171 | static private final float degFull = 360;
172 | static private final float radToIndex = SIN_COUNT / radFull;
173 | static private final float degToIndex = SIN_COUNT / degFull;
174 |
175 | /** multiply by this to convert from radians to degrees */
176 | static public final float radiansToDegrees = 180f / PI;
177 | static public final float radDeg = radiansToDegrees;
178 | /** multiply by this to convert from degrees to radians */
179 | static public final float degreesToRadians = PI / 180;
180 | static public final float degRad = degreesToRadians;
181 |
182 | static private class Sin {
183 | static final float[] table = new float[SIN_COUNT];
184 | static {
185 | for (int i = 0; i < SIN_COUNT; i++)
186 | table[i] = (float)Math.sin((i + 0.5f) / SIN_COUNT * radFull);
187 | for (int i = 0; i < 360; i += 90)
188 | table[(int)(i * degToIndex) & SIN_MASK] = (float)Math.sin(i * degreesToRadians);
189 | }
190 | }
191 |
192 | /** Returns the sine in radians from a lookup table. */
193 | static public final float sin (float radians) {
194 | return Sin.table[(int)(radians * radToIndex) & SIN_MASK];
195 | }
196 |
197 | /** Returns the cosine in radians from a lookup table. */
198 | static public final float cos (float radians) {
199 | return Sin.table[(int)((radians + PI / 2) * radToIndex) & SIN_MASK];
200 | }
201 |
202 | /** Returns the sine in radians from a lookup table. */
203 | static public final float sinDeg (float degrees) {
204 | return Sin.table[(int)(degrees * degToIndex) & SIN_MASK];
205 | }
206 |
207 | /** Returns the cosine in radians from a lookup table. */
208 | static public final float cosDeg (float degrees) {
209 | return Sin.table[(int)((degrees + 90) * degToIndex) & SIN_MASK];
210 | }
211 |
212 | }
213 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Mainline.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | /**
4 | * Represents a mainline in a Spriter SCML file.
5 | * A mainline holds only keys and occurs only once in an animation.
6 | * The mainline is responsible for telling which draw order the sprites have
7 | * and how the objects are related to each other, i.e. which bone is the root and which objects are the children.
8 | * @author Trixt0r
9 | *
10 | */
11 | public class Mainline {
12 |
13 | final Key[] keys;
14 | private int keyPointer = 0;
15 |
16 | public Mainline(int keys){
17 | this.keys = new Key[keys];
18 | }
19 |
20 | public String toString(){
21 | String toReturn = getClass().getSimpleName()+"|";
22 | for(Key key: keys)
23 | toReturn += "\n"+key;
24 | toReturn+="]";
25 | return toReturn;
26 | }
27 |
28 | public void addKey(Key key){
29 | this.keys[keyPointer++] = key;
30 | }
31 |
32 | /**
33 | * Returns a {@link Key} at the given index.
34 | * @param index the index of the key
35 | * @return the key with the given index
36 | * @throws IndexOutOfBoundsException if index is out of range
37 | */
38 | public Key getKey(int index){
39 | return this.keys[index];
40 | }
41 |
42 | /**
43 | * Returns a {@link Key} before the given time.
44 | * @param time the time a key has to be before
45 | * @return a key which has a time value before the given one.
46 | * The first key is returned if no key was found.
47 | */
48 | public Key getKeyBeforeTime(int time){
49 | Key found = this.keys[0];
50 | for(Key key: this.keys){
51 | if(key.time <= time) found = key;
52 | else break;
53 | }
54 | return found;
55 | }
56 |
57 | /**
58 | * Represents a mainline key in a Spriter SCML file.
59 | * A mainline key holds an {@link #id}, a {@link #time}, a {@link #curve}
60 | * and lists of bone and object references which build a tree hierarchy.
61 | * @author Trixt0r
62 | *
63 | */
64 | public static class Key{
65 |
66 | public final int id, time;
67 | final BoneRef[] boneRefs;
68 | final ObjectRef[] objectRefs;
69 | private int bonePointer = 0, objectPointer = 0;
70 | public final Curve curve;
71 |
72 | public Key(int id, int time, Curve curve, int boneRefs, int objectRefs){
73 | this.id = id;
74 | this.time = time;
75 | this.curve = curve;
76 | this.boneRefs = new BoneRef[boneRefs];
77 | this.objectRefs = new ObjectRef[objectRefs];
78 | }
79 |
80 | /**
81 | * Adds a bone reference to this key.
82 | * @param ref the reference to add
83 | */
84 | public void addBoneRef(BoneRef ref){
85 | this.boneRefs[bonePointer++] = ref;
86 | }
87 |
88 | /**
89 | * Adds a object reference to this key.
90 | * @param ref the reference to add
91 | */
92 | public void addObjectRef(ObjectRef ref){
93 | this.objectRefs[objectPointer++] = ref;
94 | }
95 |
96 | /**
97 | * Returns a {@link BoneRef} with the given index.
98 | * @param index the index of the bone reference
99 | * @return the bone reference or null if no reference exists with the given index
100 | */
101 | public BoneRef getBoneRef(int index){
102 | if(index < 0 || index >= this.boneRefs.length) return null;
103 | else return this.boneRefs[index];
104 | }
105 |
106 | /**
107 | * Returns a {@link ObjectRef} with the given index.
108 | * @param index the index of the object reference
109 | * @return the object reference or null if no reference exists with the given index
110 | */
111 | public ObjectRef getObjectRef(int index){
112 | if(index < 0 || index >= this.objectRefs.length) return null;
113 | else return this.objectRefs[index];
114 | }
115 |
116 | /**
117 | * Returns a {@link BoneRef} for the given reference.
118 | * @param ref the reference to the reference in this key
119 | * @return a bone reference with the same time line as the given one
120 | */
121 | public BoneRef getBoneRef(BoneRef ref){
122 | return getBoneRefTimeline(ref.timeline);
123 | }
124 |
125 | /**
126 | * Returns a {@link BoneRef} with the given time line index.
127 | * @param timeline the time line index
128 | * @return the bone reference with the given time line index or null if no reference exists with the given time line index
129 | */
130 | public BoneRef getBoneRefTimeline(int timeline){
131 | for(BoneRef boneRef: this.boneRefs)
132 | if(boneRef.timeline == timeline) return boneRef;
133 | return null;
134 | }
135 |
136 | /**
137 | * Returns an {@link ObjectRef} for the given reference.
138 | * @param ref the reference to the reference in this key
139 | * @return an object reference with the same time line as the given one
140 | */
141 | public ObjectRef getObjectRef(ObjectRef ref){
142 | return getObjectRefTimeline(ref.timeline);
143 | }
144 |
145 | /**
146 | * Returns a {@link ObjectRef} with the given time line index.
147 | * @param timeline the time line index
148 | * @return the object reference with the given time line index or null if no reference exists with the given time line index
149 | */
150 | public ObjectRef getObjectRefTimeline(int timeline){
151 | for(ObjectRef objRef: this.objectRefs)
152 | if(objRef.timeline == timeline) return objRef;
153 | return null;
154 | }
155 |
156 | public String toString(){
157 | String toReturn = getClass().getSimpleName()+"|[id:"+id+", time: "+time+", curve: ["+curve+"]";
158 | for(BoneRef ref: boneRefs)
159 | toReturn += "\n"+ref;
160 | for(ObjectRef ref: objectRefs)
161 | toReturn += "\n"+ref;
162 | toReturn+="]";
163 | return toReturn;
164 | }
165 |
166 | /**
167 | * Represents a bone reference in a Spriter SCML file.
168 | * A bone reference holds an {@link #id}, a {@link #timeline} and a {@link #key}.
169 | * A bone reference may have a parent reference.
170 | * @author Trixt0r
171 | *
172 | */
173 | public static class BoneRef{
174 | public final int id, key, timeline;
175 | public final BoneRef parent;
176 |
177 | public BoneRef(int id, int timeline, int key, BoneRef parent){
178 | this.id = id;
179 | this.timeline = timeline;
180 | this.key = key;
181 | this.parent = parent;
182 | }
183 |
184 | public String toString(){
185 | int parentId = (parent != null) ? parent.id:-1;
186 | return getClass().getSimpleName()+"|id: "+id+", parent:"+parentId+", timeline: "+timeline+", key: "+key;
187 | }
188 | }
189 |
190 | /**
191 | * Represents an object reference in a Spriter SCML file.
192 | * An object reference extends a {@link BoneRef} with a {@link #zIndex},
193 | * which indicates when the object has to be drawn.
194 | * @author Trixt0r
195 | *
196 | */
197 | public static class ObjectRef extends BoneRef implements Comparable{
198 | public final int zIndex;
199 |
200 | public ObjectRef(int id, int timeline, int key, BoneRef parent, int zIndex){
201 | super(id, timeline, key, parent);
202 | this.zIndex = zIndex;
203 | }
204 |
205 | public String toString(){
206 | return super.toString()+", z_index: "+zIndex;
207 | }
208 |
209 | public int compareTo(ObjectRef o) {
210 | return (int)Math.signum(zIndex-o.zIndex);
211 | }
212 | }
213 | }
214 |
215 | }
216 |
--------------------------------------------------------------------------------
/src/test/resources/test.scml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Entity.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 |
7 | /**
8 | * Represents an entity of a Spriter SCML file.
9 | * An entity holds {@link Animation}s, an {@link #id}, a {@link #name}.
10 | * {@link #characterMaps} and {@link #objectInfos} may be empty.
11 | * @author Trixt0r
12 | *
13 | */
14 | public class Entity {
15 |
16 | public final int id;
17 | public final String name;
18 | private final Animation[] animations;
19 | private int animationPointer = 0;
20 | private final HashMap namedAnimations;
21 | private final CharacterMap[] characterMaps;
22 | private int charMapPointer = 0;
23 | private final ObjectInfo[] objectInfos;
24 | private int objInfoPointer = 0;
25 |
26 | Entity(int id, String name, int animations, int characterMaps, int objectInfos){
27 | this.id = id;
28 | this.name = name;
29 | this.animations = new Animation[animations];
30 | this.characterMaps = new CharacterMap[characterMaps];
31 | this.objectInfos = new ObjectInfo[objectInfos];
32 | this.namedAnimations = new HashMap();
33 | }
34 |
35 | void addAnimation(Animation anim){
36 | this.animations[animationPointer++] = anim;
37 | this.namedAnimations.put(anim.name, anim);
38 | }
39 |
40 | /**
41 | * Returns an {@link Animation} with the given index.
42 | * @param index the index of the animation
43 | * @return the animation with the given index
44 | * @throws IndexOutOfBoundsException if the index is out of range
45 | */
46 | public Animation getAnimation(int index){
47 | return this.animations[index];
48 | }
49 |
50 | /**
51 | * Returns an {@link Animation} with the given name.
52 | * @param name the name of the animation
53 | * @return the animation with the given name or null if no animation exists with the given name
54 | */
55 | public Animation getAnimation(String name){
56 | return this.namedAnimations.get(name);
57 | }
58 |
59 | /**
60 | * Returns the number of animations this entity holds.
61 | * @return the number of animations
62 | */
63 | public int animations(){
64 | return this.animations.length;
65 | }
66 |
67 | /**
68 | * Returns whether this entity contains the given animation.
69 | * @param anim the animation to check
70 | * @return true if the given animation is in this entity, false otherwise.
71 | */
72 | public boolean containsAnimation(Animation anim){
73 | for(Animation a: this.animations)
74 | if(a == anim) return true;
75 | return false;
76 | }
77 |
78 | /**
79 | * Returns the animation with the most number of time lines in this entity.
80 | * @return animation with the maximum amount of time lines.
81 | */
82 | public Animation getAnimationWithMostTimelines(){
83 | Animation maxAnim = getAnimation(0);
84 | for(Animation anim: this.animations){
85 | if(maxAnim.timelines() < anim.timelines()) maxAnim = anim;
86 | }
87 | return maxAnim;
88 | }
89 |
90 | /**
91 | * Returns a {@link CharacterMap} with the given name.
92 | * @param name name of the character map
93 | * @return the character map or null if no character map exists with the given name
94 | */
95 | public CharacterMap getCharacterMap(String name){
96 | for(CharacterMap map: this.characterMaps)
97 | if(map.name.equals(name)) return map;
98 | return null;
99 | }
100 |
101 | void addCharacterMap(CharacterMap map){
102 | this.characterMaps[charMapPointer++] = map;
103 | }
104 |
105 | void addInfo(ObjectInfo info){
106 | this.objectInfos[objInfoPointer++] = info;
107 | }
108 |
109 | /**
110 | * Returns an {@link ObjectInfo} with the given index.
111 | * @param index the index of the object info
112 | * @return the object info
113 | * @throws IndexOutOfBoundsException if index is out of range
114 | */
115 | public ObjectInfo getInfo(int index){
116 | return this.objectInfos[index];
117 | }
118 |
119 | /**
120 | * Returns an {@link ObjectInfo} with the given name.
121 | * @param name name of the object info
122 | * @return object info or null if no object info exists with the given name
123 | */
124 | public ObjectInfo getInfo(String name){
125 | for(ObjectInfo info: this.objectInfos)
126 | if(info.name.equals(name)) return info;
127 | return null;
128 | }
129 |
130 | /**
131 | * Returns an {@link ObjectInfo} with the given name and the given {@link ObjectType} type.
132 | * @param name the name of the object info
133 | * @param type the type if the object info
134 | * @return the object info or null if no object info exists with the given name and type
135 | */
136 | public ObjectInfo getInfo(String name, ObjectType type){
137 | ObjectInfo info = this.getInfo(name);
138 | if(info != null && info.type == type) return info;
139 | else return null;
140 | }
141 |
142 | /**
143 | * Represents the object types Spriter supports.
144 | * @author Trixt0r
145 | *
146 | */
147 | public static enum ObjectType{
148 | Sprite, Bone, Box, Point, Skin;
149 |
150 | /**
151 | * Returns the object type for the given name
152 | * @param name the name of the type
153 | * @return the object type, Sprite is the default value
154 | */
155 | public static ObjectType getObjectInfoFor(String name){
156 | if(name.equals("bone")) return Bone;
157 | else if(name.equals("skin")) return Skin;
158 | else if(name.equals("box")) return Box;
159 | else if(name.equals("point")) return Point;
160 | else return Sprite;
161 | }
162 | }
163 |
164 | /**
165 | * Represents the object info in a Spriter SCML file.
166 | * An object info holds a {@link #type} and a {@link #name}.
167 | * If the type is a Sprite it holds a list of frames. Otherwise it has a {@link #size} for debug drawing purposes.
168 | * @author Trixt0r
169 | *
170 | */
171 | public static class ObjectInfo{
172 | public final ObjectType type;
173 | public final List frames;
174 | public final String name;
175 | public final Dimension size;
176 |
177 | ObjectInfo(String name, ObjectType type, Dimension size, List frames){
178 | this.type = type;
179 | this.frames = frames;
180 | this.name = name;
181 | this.size = size;
182 | }
183 |
184 | ObjectInfo(String name, ObjectType type, Dimension size){
185 | this(name, type, size, new ArrayList());
186 | }
187 |
188 | ObjectInfo(String name, ObjectType type, List frames){
189 | this(name, type, new Dimension(0,0), frames);
190 | }
191 |
192 | public String toString(){
193 | return name + ": "+ type + ", size: "+size+"|frames:\n"+frames;
194 | }
195 | }
196 |
197 | /**
198 | * Represents a Spriter SCML character map.
199 | * A character map maps {@link FileReference}s to {@link FileReference}s.
200 | * It holds an {@link CharacterMap#id} and a {@link CharacterMap#name}.
201 | * @author Trixt0r
202 | *
203 | */
204 | public static class CharacterMap extends HashMap{
205 | private static final long serialVersionUID = 6062776450159802283L;
206 |
207 | public final int id;
208 | public final String name;
209 |
210 | public CharacterMap(int id, String name){
211 | this.id = id;
212 | this.name = name;
213 | }
214 |
215 | /**
216 | * Returns the mapped reference for the given key.
217 | * @param key the key of the reference
218 | * @return The mapped reference if the key is in this map, otherwise the given key itself is returned.
219 | */
220 | public FileReference get(FileReference key){
221 | if(!super.containsKey(key)) return key;
222 | else return super.get(key);
223 | }
224 | }
225 |
226 | public String toString(){
227 | String toReturn = getClass().getSimpleName()+"|[id: "+id+", name: "+name+"]";
228 | toReturn +="Object infos:\n";
229 | for(ObjectInfo info: this.objectInfos)
230 | toReturn += "\n"+info;
231 | toReturn +="Character maps:\n";
232 | for(CharacterMap map: this.characterMaps)
233 | toReturn += "\n"+map;
234 | toReturn +="Animations:\n";
235 | for(Animation animaton: this.animations)
236 | toReturn += "\n"+animaton;
237 | toReturn+="]";
238 | return toReturn;
239 | }
240 |
241 | }
242 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Animation.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import java.util.HashMap;
4 |
5 | import com.brashmonkey.spriter.Mainline.Key;
6 | import com.brashmonkey.spriter.Mainline.Key.BoneRef;
7 | import com.brashmonkey.spriter.Mainline.Key.ObjectRef;
8 | import com.brashmonkey.spriter.Timeline.Key.Bone;
9 | import com.brashmonkey.spriter.Timeline.Key.Object;
10 | /**
11 | * Represents an animation of a Spriter SCML file.
12 | * An animation holds {@link Timeline}s and a {@link Mainline} to animate objects.
13 | * Furthermore it holds an {@link #id}, a {@link #length}, a {@link #name} and whether it is {@link #looping} or not.
14 | * @author Trixt0r
15 | *
16 | */
17 | public class Animation {
18 |
19 | public final Mainline mainline;
20 | private final Timeline[] timelines;
21 | private int timelinePointer = 0;
22 | private final HashMap nameToTimeline;
23 | public final int id, length;
24 | public final String name;
25 | public final boolean looping;
26 | Key currentKey;
27 | Timeline.Key[] tweenedKeys, unmappedTweenedKeys;
28 | private boolean prepared;
29 |
30 | public Animation(Mainline mainline, int id, String name, int length, boolean looping, int timelines){
31 | this.mainline = mainline;
32 | this.id = id;
33 | this.name = name;
34 | this.length = length;
35 | this.looping = looping;
36 | this.timelines = new Timeline[timelines];
37 | this.prepared = false;
38 | this.nameToTimeline = new HashMap();
39 | //this.currentKey = mainline.getKey(0);
40 | }
41 |
42 | /**
43 | * Returns a {@link Timeline} with the given index.
44 | * @param index the index of the timeline
45 | * @return the timeline with the given index
46 | * @throws IndexOutOfBoundsException if the index is out of range
47 | */
48 | public Timeline getTimeline(int index){
49 | return this.timelines[index];
50 | }
51 |
52 | /**
53 | * Returns a {@link Timeline} with the given name.
54 | * @param name the name of the time line
55 | * @return the time line with the given name or null if no time line exists with the given name.
56 | */
57 | public Timeline getTimeline(String name){
58 | return this.nameToTimeline.get(name);
59 | }
60 |
61 | void addTimeline(Timeline timeline){
62 | this.timelines[timelinePointer++] = timeline;
63 | this.nameToTimeline.put(timeline.name, timeline);
64 | }
65 |
66 | /**
67 | * Returns the number of time lines this animation holds.
68 | * @return the number of time lines
69 | */
70 | public int timelines(){
71 | return timelines.length;
72 | }
73 |
74 | public String toString(){
75 | String toReturn = getClass().getSimpleName()+"|[id: "+id+", "+name+", duration: "+length+", is looping: "+looping;
76 | toReturn +="Mainline:\n";
77 | toReturn += mainline;
78 | toReturn += "Timelines\n";
79 | for(Timeline timeline: this.timelines)
80 | toReturn += timeline;
81 | toReturn+="]";
82 | return toReturn;
83 | }
84 |
85 | /**
86 | * Updates the bone and object structure with the given time to the given root bone.
87 | * @param time The time which has to be between 0 and {@link #length} to work properly.
88 | * @param root The root bone which is not allowed to be null. The whole animation runs relative to the root bone.
89 | */
90 | public void update(int time, Bone root){
91 | if(!this.prepared) throw new SpriterException("This animation is not ready yet to animate itself. Please call prepare()!");
92 | if(root == null) throw new SpriterException("The root can not be null! Set a root bone to apply this animation relative to the root bone.");
93 | this.currentKey = mainline.getKeyBeforeTime(time);
94 |
95 | for(Timeline.Key timelineKey: this.unmappedTweenedKeys)
96 | timelineKey.active = false;
97 | for(BoneRef ref: currentKey.boneRefs)
98 | this.update(ref, root, time);
99 | for(ObjectRef ref: currentKey.objectRefs)
100 | this.update(ref, root, time);
101 | }
102 |
103 | protected void update(BoneRef ref, Bone root, int time){
104 | boolean isObject = ref instanceof ObjectRef;
105 | //Get the timelines, the refs pointing to
106 | Timeline timeline = getTimeline(ref.timeline);
107 | Timeline.Key key = timeline.getKey(ref.key);
108 | Timeline.Key nextKey = timeline.getKey((ref.key+1)%timeline.keys.length);
109 | int currentTime = key.time;
110 | int nextTime = nextKey.time;
111 | if(nextTime < currentTime){
112 | if(!looping) nextKey = key;
113 | else nextTime = length;
114 | }
115 | //Normalize the time
116 | float t = (float)(time - currentTime)/(float)(nextTime - currentTime);
117 | if(Float.isNaN(t) || Float.isInfinite(t)) t = 1f;
118 | if(currentKey.time > currentTime){
119 | float tMid = (float)(currentKey.time - currentTime)/(float)(nextTime - currentTime);
120 | if(Float.isNaN(tMid) || Float.isInfinite(tMid)) tMid = 0f;
121 | t = (float)(time - currentKey.time)/(float)(nextTime - currentKey.time);
122 | if(Float.isNaN(t) || Float.isInfinite(t)) t = 1f;
123 | t = currentKey.curve.tween(tMid, 1f, t);
124 | }
125 | else
126 | t = currentKey.curve.tween(0f, 1f, t);
127 | //Tween bone/object
128 | Bone bone1 = key.object();
129 | Bone bone2 = nextKey.object();
130 | Bone tweenTarget = this.tweenedKeys[ref.timeline].object();
131 | if(isObject) this.tweenObject((Object)bone1, (Object)bone2, (Object)tweenTarget, t, key.curve, key.spin);
132 | else this.tweenBone(bone1, bone2, tweenTarget, t, key.curve, key.spin);
133 | this.unmappedTweenedKeys[ref.timeline].active = true;
134 | this.unmapTimelineObject(ref.timeline, isObject,(ref.parent != null) ?
135 | this.unmappedTweenedKeys[ref.parent.timeline].object(): root);
136 | }
137 |
138 | void unmapTimelineObject(int timeline, boolean isObject, Bone root){
139 | Bone tweenTarget = this.tweenedKeys[timeline].object();
140 | Bone mapTarget = this.unmappedTweenedKeys[timeline].object();
141 | if(isObject) ((Object)mapTarget).set((Object)tweenTarget);
142 | else mapTarget.set(tweenTarget);
143 | mapTarget.unmap(root);
144 | }
145 |
146 | protected void tweenBone(Bone bone1, Bone bone2, Bone target, float t, Curve curve, int spin){
147 | target.angle = curve.tweenAngle(bone1.angle, bone2.angle, t, spin);
148 | curve.tweenPoint(bone1.position, bone2.position, t, target.position);
149 | curve.tweenPoint(bone1.scale, bone2.scale, t, target.scale);
150 | curve.tweenPoint(bone1.pivot, bone2.pivot, t, target.pivot);
151 | }
152 |
153 | protected void tweenObject(Object object1, Object object2, Object target, float t, Curve curve, int spin){
154 | this.tweenBone(object1, object2, target, t, curve, spin);
155 | target.alpha = curve.tweenAngle(object1.alpha, object2.alpha, t);
156 | target.ref.set(object1.ref);
157 | }
158 |
159 | Timeline getSimilarTimeline(Timeline t){
160 | Timeline found = getTimeline(t.name);
161 | if(found == null && t.id < this.timelines()) found = this.getTimeline(t.id);
162 | return found;
163 | }
164 |
165 | /*Timeline getSimilarTimeline(BoneRef ref, Collection coveredTimelines){
166 | if(ref.parent == null) return null;
167 | for(BoneRef boneRef: this.currentKey.objectRefs){
168 | Timeline t = this.getTimeline(boneRef.timeline);
169 | if(boneRef.parent != null && boneRef.parent.id == ref.parent.id && !coveredTimelines.contains(t))
170 | return t;
171 | }
172 | return null;
173 | }
174 |
175 | Timeline getSimilarTimeline(ObjectRef ref, Collection coveredTimelines){
176 | if(ref.parent == null) return null;
177 | for(ObjectRef objRef: this.currentKey.objectRefs){
178 | Timeline t = this.getTimeline(objRef.timeline);
179 | if(objRef.parent != null && objRef.parent.id == ref.parent.id && !coveredTimelines.contains(t))
180 | return t;
181 | }
182 | return null;
183 | }*/
184 |
185 | /**
186 | * Prepares this animation to set this animation in any time state.
187 | * This method has to be called before {@link #update(int, Bone)}.
188 | */
189 | public void prepare(){
190 | if(this.prepared) return;
191 | this.tweenedKeys = new Timeline.Key[timelines.length];
192 | this.unmappedTweenedKeys = new Timeline.Key[timelines.length];
193 |
194 | for(int i = 0; i < this.tweenedKeys.length; i++){
195 | this.tweenedKeys[i] = new Timeline.Key(i);
196 | this.unmappedTweenedKeys[i] = new Timeline.Key(i);
197 | this.tweenedKeys[i].setObject(new Timeline.Key.Object(new Point(0,0)));
198 | this.unmappedTweenedKeys[i].setObject(new Timeline.Key.Object(new Point(0,0)));
199 | }
200 | if(mainline.keys.length > 0) currentKey = mainline.getKey(0);
201 | this.prepared = true;
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Stop when "xargs" is not available.
209 | if ! command -v xargs >/dev/null 2>&1
210 | then
211 | die "xargs is not available"
212 | fi
213 |
214 | # Use "xargs" to parse quoted args.
215 | #
216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
217 | #
218 | # In Bash we could simply go:
219 | #
220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
221 | # set -- "${ARGS[@]}" "$@"
222 | #
223 | # but POSIX shell has neither arrays nor command substitution, so instead we
224 | # post-process each arg (as a line of input to sed) to backslash-escape any
225 | # character that might be a shell metacharacter, then use eval to reverse
226 | # that process (while maintaining the separation between arguments), and wrap
227 | # the whole thing up as a single "set" statement.
228 | #
229 | # This will of course break if any of these variables contains a newline or
230 | # an unmatched quote.
231 | #
232 |
233 | eval "set -- $(
234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
235 | xargs -n1 |
236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
237 | tr '\n' ' '
238 | )" '"$@"'
239 |
240 | exec "$JAVACMD" "$@"
241 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/TweenedAnimation.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import com.brashmonkey.spriter.Mainline.Key.BoneRef;
4 | import com.brashmonkey.spriter.Mainline.Key.ObjectRef;
5 | import com.brashmonkey.spriter.Timeline.Key.Bone;
6 | import com.brashmonkey.spriter.Timeline.Key.Object;
7 |
8 | /**
9 | * A tweened animation is responsible for updating itself based on two given animations.
10 | * The values of the two given animations will get interpolated and save in this animation.
11 | * When tweening two animations, you have to make sure that they have the same structure.
12 | * The best result is achieved if bones of two different animations are named in the same way.
13 | * There are still issues with sprites, which are hard to resolve since Spriter does not save them in a useful order or naming convention.
14 | * @author Trixt0r
15 | *
16 | */
17 | public class TweenedAnimation extends Animation{
18 |
19 | /**
20 | * The weight of the interpolation. 0.5f is the default value.
21 | * Values closer to 0.0f mean the first animation will have more influence.
22 | */
23 | public float weight = .5f;
24 |
25 | /**
26 | * Indicates when a sprite should be switched form the first animation object to the second one.
27 | * A value closer to 0.0f means that the sprites of the second animation will be drawn.
28 | */
29 | public float spriteThreshold = .5f;
30 |
31 | /**
32 | * The curve which will tween the animations.
33 | * The default type of the curve is {@link Curve.Type#Linear}.
34 | */
35 | public final Curve curve;
36 |
37 | /**
38 | * The entity the animations have be part of.
39 | * Animations of two different entities can not be tweened.
40 | */
41 | public final Entity entity;
42 | private Animation anim1, anim2;
43 |
44 | /**
45 | * The base animation an object or bone will get if it will not be tweened.
46 | */
47 | public Animation baseAnimation;
48 | BoneRef base = null;
49 |
50 | /**
51 | * Indicates whether to tween sprites or not. Default value is false.
52 | * Tweening sprites should be only enabled if they have exactly the same structure.
53 | * If all animations are bone based and sprites only change their references it is not recommended to tween sprites.
54 | */
55 | public boolean tweenSprites = false;
56 |
57 | /**
58 | * Creates a tweened animation based on the given entity.
59 | * @param entity the entity animations have to be part of
60 | */
61 | public TweenedAnimation(Entity entity) {
62 | super(new Mainline(0), -1, "__interpolatedAnimation__", 0, true, entity.getAnimationWithMostTimelines().timelines());
63 | this.entity = entity;
64 | this.curve = new Curve();
65 | this.setUpTimelines();
66 | }
67 |
68 | /**
69 | * Returns the current mainline key.
70 | * @return the mainline key
71 | */
72 | public Mainline.Key getCurrentKey(){
73 | return this.currentKey;
74 | }
75 |
76 | @Override
77 | public void update(int time, Bone root){
78 | super.currentKey = onFirstMainLine() ? anim1.currentKey: anim2.currentKey;
79 | for(Timeline.Key timelineKey: this.unmappedTweenedKeys)
80 | timelineKey.active = false;
81 | if(base != null){//TODO: Sprites not working properly because of different timeline naming
82 | Animation currentAnim = onFirstMainLine() ? anim1: anim2;
83 | Animation baseAnim = baseAnimation == null ? (onFirstMainLine() ? anim1:anim2) : baseAnimation;
84 | for(BoneRef ref: currentKey.boneRefs){
85 | Timeline timeline = baseAnim.getSimilarTimeline(currentAnim.getTimeline(ref.timeline));
86 | if(timeline == null) continue;
87 | Timeline.Key key, mappedKey;
88 | key = baseAnim.tweenedKeys[timeline.id];
89 | mappedKey = baseAnim.unmappedTweenedKeys[timeline.id];
90 | this.tweenedKeys[ref.timeline].active = key.active;
91 | this.tweenedKeys[ref.timeline].object().set(key.object());
92 | this.unmappedTweenedKeys[ref.timeline].active = mappedKey.active;
93 | this.unmapTimelineObject(ref.timeline, false,(ref.parent != null) ?
94 | this.unmappedTweenedKeys[ref.parent.timeline].object(): root);
95 | }
96 | /*for(ObjectRef ref: baseAnim.currentKey.objectRefs){
97 | Timeline timeline = baseAnim.getTimeline(ref.timeline);//getSimilarTimeline(ref, tempTimelines);
98 | if(timeline != null){
99 | //tempTimelines.addLast(timeline);
100 | Timeline.Key key = baseAnim.tweenedKeys[timeline.id];
101 | Timeline.Key mappedKey = baseAnim.mappedTweenedKeys[timeline.id];
102 | Object obj = (Object) key.object();
103 |
104 | this.tweenedKeys[ref.timeline].active = key.active;
105 | ((Object)this.tweenedKeys[ref.timeline].object()).set(obj);
106 | this.mappedTweenedKeys[ref.timeline].active = mappedKey.active;
107 | this.unmapTimelineObject(ref.timeline, true,(ref.parent != null) ?
108 | this.mappedTweenedKeys[ref.parent.timeline].object(): root);
109 | }
110 | }*/
111 | //tempTimelines.clear();
112 | }
113 |
114 | this.tweenBoneRefs(base, root);
115 | for(ObjectRef ref: super.currentKey.objectRefs){
116 | //if(ref.parent == base)
117 | this.update(ref, root, 0);
118 | }
119 | }
120 |
121 | private void tweenBoneRefs(BoneRef base, Bone root){
122 | int startIndex = base == null ? -1 : base.id-1;
123 | int length = super.currentKey.boneRefs.length;
124 | for(int i = startIndex+1; i < length; i++){
125 | BoneRef ref = currentKey.boneRefs[i];
126 | if(base == ref || ref.parent == base) this.update(ref, root, 0);
127 | if(base == ref.parent) this.tweenBoneRefs(ref, root);
128 | }
129 | }
130 |
131 | @Override
132 | protected void update(BoneRef ref, Bone root, int time){
133 | boolean isObject = ref instanceof ObjectRef;
134 | //Tween bone/object
135 | Bone bone1 = null, bone2 = null, tweenTarget = null;
136 | Timeline t1 = onFirstMainLine() ? anim1.getTimeline(ref.timeline) : anim1.getSimilarTimeline(anim2.getTimeline(ref.timeline));
137 | Timeline t2 = onFirstMainLine() ? anim2.getSimilarTimeline(t1) : anim2.getTimeline(ref.timeline);
138 | Timeline targetTimeline = super.getTimeline(onFirstMainLine() ? t1.id:t2.id);
139 | if(t1 != null) bone1 = anim1.tweenedKeys[t1.id].object();
140 | if(t2 != null) bone2 = anim2.tweenedKeys[t2.id].object();
141 | if(targetTimeline != null) tweenTarget = this.tweenedKeys[targetTimeline.id].object();
142 | if(isObject && (t2 == null || !tweenSprites)){
143 | if(!onFirstMainLine()) bone1 = bone2;
144 | else bone2 = bone1;
145 | }
146 | if(bone2 != null && tweenTarget != null && bone1 != null){
147 | if(isObject) this.tweenObject((Object)bone1, (Object)bone2, (Object)tweenTarget, this.weight, this.curve);
148 | else this.tweenBone(bone1, bone2, tweenTarget, this.weight, this.curve);
149 | this.unmappedTweenedKeys[targetTimeline.id].active = true;
150 | }
151 | //Transform the bone relative to the parent bone or the root
152 | if(this.unmappedTweenedKeys[ref.timeline].active){
153 | this.unmapTimelineObject(targetTimeline.id, isObject,(ref.parent != null) ?
154 | this.unmappedTweenedKeys[ref.parent.timeline].object(): root);
155 | }
156 | }
157 |
158 | private void tweenBone(Bone bone1, Bone bone2, Bone target, float t, Curve curve){
159 | target.angle = curve.tweenAngle(bone1.angle, bone2.angle, t);
160 | curve.tweenPoint(bone1.position, bone2.position, t, target.position);
161 | curve.tweenPoint(bone1.scale, bone2.scale, t, target.scale);
162 | curve.tweenPoint(bone1.pivot, bone2.pivot, t, target.pivot);
163 | }
164 |
165 | private void tweenObject(Object object1, Object object2, Object target, float t, Curve curve){
166 | this.tweenBone(object1, object2, target, t, curve);
167 | target.alpha = curve.tweenAngle(object1.alpha, object2.alpha, t);
168 | target.ref.set(object1.ref);
169 | }
170 |
171 | /**
172 | * Returns whether the current mainline key is the one from the first animation or from the second one.
173 | * @return true if the mainline key is the one from the first animation
174 | */
175 | public boolean onFirstMainLine(){
176 | return this.weight < this.spriteThreshold;
177 | }
178 |
179 | private void setUpTimelines(){
180 | Animation maxAnim = this.entity.getAnimationWithMostTimelines();
181 | int max = maxAnim.timelines();
182 | for(int i = 0; i < max; i++){
183 | Timeline t = new Timeline(i, maxAnim.getTimeline(i).name, maxAnim.getTimeline(i).objectInfo, 1);
184 | addTimeline(t);
185 | }
186 | prepare();
187 | }
188 |
189 | /**
190 | * Sets the animations to tween.
191 | * @param animation1 the first animation
192 | * @param animation2 the second animation
193 | * @throws SpriterException if {@link #entity} does not contain one of the given animations.
194 | */
195 | public void setAnimations(Animation animation1, Animation animation2){
196 | boolean areInterpolated = animation1 instanceof TweenedAnimation || animation2 instanceof TweenedAnimation;
197 | if(animation1 == anim1 && animation2 == anim2) return;
198 | if((!this.entity.containsAnimation(animation1) || !this.entity.containsAnimation(animation2)) && !areInterpolated)
199 | throw new SpriterException("Both animations have to be part of the same entity!");
200 | this.anim1 = animation1;
201 | this.anim2 = animation2;
202 | }
203 |
204 | /**
205 | * Returns the first animation.
206 | * @return the first animation
207 | */
208 | public Animation getFirstAnimation(){
209 | return this.anim1;
210 | }
211 |
212 | /**
213 | * Returns the second animation.
214 | * @return the second animation
215 | */
216 | public Animation getSecondAnimation(){
217 | return this.anim2;
218 | }
219 |
220 | }
221 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Timeline.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import com.brashmonkey.spriter.Entity.ObjectInfo;
4 |
5 | /**
6 | * Represents a time line in a Spriter SCML file.
7 | * A time line holds an {@link #id}, a {@link #name} and at least one {@link Key}.
8 | * @author Trixt0r
9 | *
10 | */
11 | public class Timeline {
12 |
13 | public final Key[] keys;
14 | private int keyPointer = 0;
15 | public final int id;
16 | public final String name;
17 | public final ObjectInfo objectInfo;
18 |
19 | Timeline(int id, String name, ObjectInfo objectInfo, int keys){
20 | this.id = id;
21 | this.name = name;
22 | this.objectInfo = objectInfo;
23 | this.keys = new Key[keys];
24 | }
25 |
26 | void addKey(Key key){
27 | this.keys[keyPointer++] = key;
28 | }
29 |
30 | /**
31 | * Returns a {@link Key} at the given index
32 | * @param index the index of the key.
33 | * @return the key with the given index.
34 | * @throws IndexOutOfBoundsException if the index is out of range
35 | */
36 | public Key getKey(int index){
37 | return this.keys[index];
38 | }
39 |
40 | public String toString(){
41 | String toReturn = getClass().getSimpleName()+"|[id:"+id+", name: "+name+", object_info: "+objectInfo;
42 | for(Key key: keys)
43 | toReturn += "\n"+key;
44 | toReturn+="]";
45 | return toReturn;
46 | }
47 |
48 | /**
49 | * Represents a time line key in a Spriter SCML file.
50 | * A key holds an {@link #id}, a {@link #time}, a {@link #spin}, an {@link #object()} and a {@link #curve}.
51 | * @author Trixt0r
52 | *
53 | */
54 | public static class Key{
55 |
56 | public final int id, spin;
57 | public int time;
58 | public final Curve curve;
59 | public boolean active;
60 | private Object object;
61 |
62 | public Key(int id, int time, int spin, Curve curve){
63 | this.id = id;
64 | this.time = time;
65 | this.spin = spin;
66 | this.curve = curve;
67 | }
68 |
69 | public Key(int id,int time, int spin){
70 | this(id, time, 1, new Curve());
71 | }
72 |
73 | public Key(int id, int time){
74 | this(id, time, 1);
75 | }
76 |
77 | public Key(int id){
78 | this(id, 0);
79 | }
80 |
81 | public void setObject(Object object){
82 | if(object == null) throw new IllegalArgumentException("object can not be null!");
83 | this.object = object;
84 | }
85 |
86 | public Object object(){
87 | return this.object;
88 | }
89 |
90 | public String toString(){
91 | return getClass().getSimpleName()+"|[id: "+id+", time: "+time+", spin: "+spin+"\ncurve: "+curve+"\nobject:"+object+"]";
92 | }
93 |
94 | /**
95 | * Represents a bone in a Spriter SCML file.
96 | * A bone holds a {@link #position}, {@link #scale}, an {@link #angle} and a {@link #pivot}.
97 | * Bones are the only objects which can be used as a parent for other tweenable objects.
98 | * @author Trixt0r
99 | *
100 | */
101 | public static class Bone{
102 | public final Point position, scale, pivot;
103 | public float angle;
104 |
105 | public Bone(Point position, Point scale, Point pivot, float angle){
106 | this.position = new Point(position);
107 | this.scale = new Point(scale);
108 | this.angle = angle;
109 | this.pivot = new Point(pivot);
110 | }
111 |
112 | public Bone(Bone bone){
113 | this(bone.position, bone.scale, bone.pivot, bone.angle);
114 | }
115 |
116 | public Bone(Point position){
117 | this(position, new Point(1f,1f), new Point(0f, 1f), 0f);
118 | }
119 |
120 | public Bone(){
121 | this(new Point());
122 | }
123 |
124 | /**
125 | * Returns whether this instance is a Spriter object or a bone.
126 | * @return true if this instance is a Spriter bone
127 | */
128 | public boolean isBone(){
129 | return !(this instanceof Object);
130 | }
131 |
132 | /**
133 | * Sets the values of this bone to the values of the given bone
134 | * @param bone the bone
135 | */
136 | public void set(Bone bone){
137 | this.set(bone.position, bone.angle, bone.scale, bone.pivot);
138 | }
139 |
140 | /**
141 | * Sets the given values for this bone.
142 | * @param x the new position in x direction
143 | * @param y the new position in y direction
144 | * @param angle the new angle
145 | * @param scaleX the new scale in x direction
146 | * @param scaleY the new scale in y direction
147 | * @param pivotX the new pivot in x direction
148 | * @param pivotY the new pivot in y direction
149 | */
150 | public void set(float x, float y, float angle, float scaleX, float scaleY, float pivotX, float pivotY){
151 | this.angle = angle;
152 | this.position.set(x, y);
153 | this.scale.set(scaleX, scaleY);
154 | this.pivot.set(pivotX, pivotY);
155 | }
156 |
157 | /**
158 | * Sets the given values for this bone.
159 | * @param position the new position
160 | * @param angle the new angle
161 | * @param scale the new scale
162 | * @param pivot the new pivot
163 | */
164 | public void set(Point position, float angle, Point scale, Point pivot){
165 | this.set(position.x, position.y, angle, scale.x, scale.y, pivot.x, pivot.y);
166 | }
167 |
168 | /**
169 | * Maps this bone from it's parent's coordinate system to a global one.
170 | * @param parent the parent bone of this bone
171 | */
172 | public void unmap(Bone parent){
173 | this.angle *= Math.signum(parent.scale.x)*Math.signum(parent.scale.y);
174 | this.angle += parent.angle;
175 | this.scale.scale(parent.scale);
176 | this.position.scale(parent.scale);
177 | this.position.rotate(parent.angle);
178 | this.position.translate(parent.position);
179 | }
180 |
181 | /**
182 | * Maps this from it's global coordinate system to the parent's one.
183 | * @param parent the parent bone of this bone
184 | */
185 | public void map(Bone parent){
186 | this.position.translate(-parent.position.x, -parent.position.y);
187 | this.position.rotate(-parent.angle);
188 | this.position.scale(1f/parent.scale.x, 1f/parent.scale.y);
189 | this.scale.scale(1f/parent.scale.x, 1f/parent.scale.y);
190 | this.angle -=parent.angle;
191 | this.angle *= Math.signum(parent.scale.x)*Math.signum(parent.scale.y);
192 | }
193 |
194 | public String toString(){
195 | return getClass().getSimpleName()+"|position: "+position+", scale: "+scale+", angle: "+angle;
196 | }
197 | }
198 |
199 |
200 | /**
201 | * Represents an object in a Spriter SCML file.
202 | * A file has the same properties as a bone with an alpha and file extension.
203 | * @author Trixt0r
204 | *
205 | */
206 | public static class Object extends Bone{
207 |
208 | public float alpha;
209 | public final FileReference ref;
210 |
211 | public Object(Point position, Point scale, Point pivot, float angle, float alpha, FileReference ref) {
212 | super(position, scale, pivot, angle);
213 | this.alpha = alpha;
214 | this.ref = ref;
215 | }
216 |
217 | public Object(Point position) {
218 | this(position, new Point(1f,1f), new Point(0f,1f), 0f, 1f, new FileReference(-1,-1));
219 | }
220 |
221 | public Object(Object object){
222 | this(object.position.copy(), object.scale.copy(),object.pivot.copy(),object.angle,object.alpha,object.ref);
223 | }
224 |
225 | public Object(){
226 | this(new Point());
227 | }
228 |
229 | /**
230 | * Sets the values of this object to the values of the given object.
231 | * @param object the object
232 | */
233 | public void set(Object object){
234 | this.set(object.position, object.angle, object.scale, object.pivot, object.alpha, object.ref);
235 | }
236 |
237 | /**
238 | * Sets the given values for this object.
239 | * @param x the new position in x direction
240 | * @param y the new position in y direction
241 | * @param angle the new angle
242 | * @param scaleX the new scale in x direction
243 | * @param scaleY the new scale in y direction
244 | * @param pivotX the new pivot in x direction
245 | * @param pivotY the new pivot in y direction
246 | * @param alpha the new alpha value
247 | * @param folder the new folder index
248 | * @param file the new file index
249 | */
250 | public void set(float x, float y, float angle, float scaleX, float scaleY, float pivotX, float pivotY, float alpha, int folder, int file){
251 | super.set(x, y, angle, scaleX, scaleY, pivotX, pivotY);
252 | this.alpha = alpha;
253 | this.ref.folder = folder;
254 | this.ref.file = file;
255 | }
256 |
257 | /**
258 | * Sets the given values for this object.
259 | * @param position the new position
260 | * @param angle the new angle
261 | * @param scale the new scale
262 | * @param pivot the new pivot
263 | * @param alpha the new alpha value
264 | * @param fileRef the new file reference
265 | */
266 | public void set(Point position, float angle, Point scale, Point pivot, float alpha, FileReference fileRef){
267 | this.set(position.x, position.y, angle, scale.x, scale.y, pivot.x, pivot.y, alpha , fileRef.folder, fileRef.file);
268 | }
269 |
270 | public String toString(){
271 | return super.toString()+", pivot: "+pivot+", alpha: "+alpha+", reference: "+ref;
272 | }
273 |
274 | }
275 | }
276 |
277 | }
278 |
--------------------------------------------------------------------------------
/src/main/java/com/brashmonkey/spriter/Drawer.java:
--------------------------------------------------------------------------------
1 | package com.brashmonkey.spriter;
2 |
3 | import java.util.Iterator;
4 |
5 | import com.brashmonkey.spriter.Entity.CharacterMap;
6 | import com.brashmonkey.spriter.Entity.ObjectInfo;
7 | import com.brashmonkey.spriter.Entity.ObjectType;
8 | import com.brashmonkey.spriter.Timeline.Key.Bone;
9 | import com.brashmonkey.spriter.Timeline.Key.Object;
10 |
11 | /**
12 | * A Drawer is responsible for drawing a {@link Player}.
13 | * Since this library is meant to be as generic as possible this class has to be abstract, because it cannot be assumed how to draw a resource.
14 | * Anyone who wants to draw a {@link Player} has to know how to draw a resource. A resource can be e.g. a sprite, a texture or a texture region.
15 | * To draw a {@link Player} call {@link #draw(Player)}. This method relies on {@link #draw(Object)}, which has to be implemented with the chosen backend.
16 | * To debug draw a {@link Player} call {@link #drawBones(Player)}, {@link #drawBoxes(Player)} and {@link #drawPoints(Player)},
17 | * which rely on {@link #rectangle(float, float, float, float)}, {@link #circle(float, float, float)}, {@link #line(float, float, float, float)} and {@link #setColor(float, float, float, float)}.
18 | * @author Trixt0r
19 | *
20 | * @param The backend specific resource. In general such a resource is called "sprite", "texture" or "image".
21 | */
22 | public abstract class Drawer {
23 |
24 | /**
25 | * The radius of a point for debug drawing purposes.
26 | */
27 | public float pointRadius = 5f;
28 | protected Loader loader;
29 |
30 | /**
31 | * Creates a new drawer based on the given loader.
32 | * @param loader the loader containing resources
33 | */
34 | public Drawer(Loader loader){
35 | this.loader = loader;
36 | }
37 |
38 | /**
39 | * Sets the loader of this drawer.
40 | * @param loader the loader containing resources
41 | * @throws SpriterException if the loader is null
42 | */
43 | public void setLoader(Loader loader){
44 | if(loader == null) throw new SpriterException("The loader instance can not be null!");
45 | this.loader = loader;
46 | }
47 |
48 | /**
49 | * Draws the bones of the given player composed of lines.
50 | * @param player the player to draw
51 | */
52 | public void drawBones(Player player){
53 | this.setColor(1, 0, 0, 1);
54 | Iterator it = player.boneIterator();
55 | while(it.hasNext()){
56 | Timeline.Key.Bone bone = it.next();
57 | Timeline.Key key = player.getKeyFor(bone);
58 | if(!key.active) continue;
59 | ObjectInfo info = player.getObjectInfoFor(bone);
60 | Dimension size = info.size;
61 | drawBone(bone, size);
62 | }
63 | /*for(Mainline.Key.BoneRef ref: player.getCurrentKey().boneRefs){
64 | Timeline.Key key = player.unmappedTweenedKeys[ref.timeline];
65 | Timeline.Key.Bone bone = key.object();
66 | if(player.animation.getTimeline(ref.timeline).objectInfo.type != ObjectType.Bone || !key.active) continue;
67 | ObjectInfo info = player.animation.getTimeline(ref.timeline).objectInfo;
68 | if(info == null) continue;
69 | Dimension size = info.size;
70 | drawBone(bone, size);
71 | }*/
72 | }
73 |
74 | /**
75 | * Draws the given bone composed of lines with the given size.
76 | * @param bone the bone to draw
77 | * @param size the size of the bone
78 | */
79 | public void drawBone(Bone bone, Dimension size){
80 | float halfHeight = size.height/2;
81 | float xx = bone.position.x+(float)Math.cos(Math.toRadians(bone.angle))*size.height;
82 | float yy = bone.position.y+(float)Math.sin(Math.toRadians(bone.angle))*size.height;
83 | float x2 = (float)Math.cos(Math.toRadians(bone.angle+90))*halfHeight*bone.scale.y;
84 | float y2 = (float)Math.sin(Math.toRadians(bone.angle+90))*halfHeight*bone.scale.y;
85 |
86 | float targetX = bone.position.x+(float)Math.cos(Math.toRadians(bone.angle))*size.width*bone.scale.x,
87 | targetY = bone.position.y+(float)Math.sin(Math.toRadians(bone.angle))*size.width*bone.scale.x;
88 | float upperPointX = xx+x2, upperPointY = yy+y2;
89 | this.line(bone.position.x, bone.position.y, upperPointX, upperPointY);
90 | this.line(upperPointX, upperPointY, targetX, targetY);
91 |
92 | float lowerPointX = xx-x2, lowerPointY = yy-y2;
93 | this.line(bone.position.x, bone.position.y, lowerPointX, lowerPointY);
94 | this.line(lowerPointX, lowerPointY, targetX, targetY);
95 | this.line(bone.position.x, bone.position.y, targetX, targetY);
96 | }
97 |
98 | /**
99 | * Draws the boxes of the player.
100 | * @param player the player to draw the boxes from
101 | */
102 | public void drawBoxes(Player player){
103 | this.setColor(0f, 1f, 0f, 1f);
104 | this.drawBoneBoxes(player);
105 | this.drawObjectBoxes(player);
106 | this.drawPoints(player);
107 | }
108 |
109 | /**
110 | * Draws the boxes of all bones of the given player.
111 | * @param player the player to draw the bone boxes of
112 | */
113 | public void drawBoneBoxes(Player player){
114 | drawBoneBoxes(player, player.boneIterator());
115 | }
116 |
117 | /**
118 | * Draws the boxes of all bones of the given player based on the given iterator.
119 | * @param player the player to draw the bone boxes of
120 | * @param it the iterator iterating over the bones to draw
121 | */
122 | public void drawBoneBoxes(Player player, Iterator it){
123 | while(it.hasNext()){
124 | Bone bone = it.next();
125 | this.drawBox(player.getBox(bone));
126 | }
127 | }
128 |
129 | /**
130 | * Draws the boxes of the player objects, i.e. sprites and objects.
131 | * @param player the player to draw the object boxes of
132 | */
133 | public void drawObjectBoxes(Player player){
134 | drawObjectBoxes(player, player.objectIterator());
135 | }
136 |
137 | /**
138 | * Draws the boxes of sprites and boxes of the given player based on the given iterator.
139 | * @param player player the player to draw the object boxes of
140 | * @param it the iterator iterating over the object to draw
141 | */
142 | public void drawObjectBoxes(Player player, Iterator