Skip to content

Commit 5a4ab3d

Browse files
committed
Fixes #29. Crash when obj have only vertex info
1 parent 1d5a3df commit 5a4ab3d

21 files changed

+1171
-40
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The purpose of this application is to learn and share how to draw using OpenGL l
1212
* STereoLithography format (STL): https://en.wikipedia.org/wiki/STL_(file_format)
1313

1414

15-
News (19/11/2017)
15+
News (21/11/2017)
1616
=================
1717

1818
* Fixed #28: Load texture feature is now available
@@ -127,11 +127,14 @@ ChangeLog
127127

128128
(f) fixed, (i) improved, (n) new feature
129129

130-
- 1.3.2 (19/11/2017)
131-
- (f) #28: Load texture available for any model having texture coordinates
130+
- 1.4.1 (21/11/2017)
131+
- (f) #29: Crash loading obj with only vertex info
132+
133+
- 1.4.0 (19/11/2017)
134+
- (f) #28: Load texture available for any model having texture coordinates
132135

133136
- 1.3.1 (23/04/2017)
134-
- (f) #18: Removed asReadOnlyBuffer() because it is causing IndexOutOfBounds on Android 7
137+
- (f) #18: Removed asReadOnlyBuffer() because it is causing IndexOutOfBounds on Android 7
135138

136139
- 1.3.0 (17/04/2017)
137140
- (n) #17: Added support for STL files

app/build/outputs/apk/app-release.apk

6.32 KB
Binary file not shown.

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="org.andresoviedo.dddmodel2"
4-
android:versionCode="12"
5-
android:versionName="1.3.2" >
4+
android:versionCode="13"
5+
android:versionName="1.4.1" >
66

77
<uses-sdk
88
android:minSdkVersion="8"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.andresoviedo.app.model3D.animation;
2+
3+
4+
/**
5+
*
6+
* Represents an animation that can applied to an {@link org.andresoviedo.app.model3D.model.AnimatedModel} . It
7+
* contains the length of the animation in seconds, and a list of
8+
* {@link KeyFrame}s.
9+
*
10+
* @author Karl
11+
*
12+
*
13+
*/
14+
public class Animation {
15+
16+
private final float length;//in seconds
17+
private final KeyFrame[] keyFrames;
18+
19+
/**
20+
* @param lengthInSeconds
21+
* - the total length of the animation in seconds.
22+
* @param frames
23+
* - all the keyframes for the animation, ordered by time of
24+
* appearance in the animation.
25+
*/
26+
public Animation(float lengthInSeconds, KeyFrame[] frames) {
27+
this.keyFrames = frames;
28+
this.length = lengthInSeconds;
29+
}
30+
31+
/**
32+
* @return The length of the animation in seconds.
33+
*/
34+
public float getLength() {
35+
return length;
36+
}
37+
38+
/**
39+
* @return An array of the animation's keyframes. The array is ordered based
40+
* on the order of the keyframes in the animation (first keyframe of
41+
* the animation in array position 0).
42+
*/
43+
public KeyFrame[] getKeyFrames() {
44+
return keyFrames;
45+
}
46+
47+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package org.andresoviedo.app.model3D.animation;
2+
3+
import android.opengl.Matrix;
4+
import android.os.SystemClock;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
import org.andresoviedo.app.model3D.model.AnimatedModel;
10+
import org.andresoviedo.app.model3D.model.Object3DData;
11+
import org.andresoviedo.app.model3D.services.collada.entities.Joint;
12+
13+
14+
/**
15+
*
16+
* This class contains all the functionality to apply an animation to an
17+
* animated entity. An Animator instance is associated with just one
18+
* {@link AnimatedModel}. It also keeps track of the running time (in seconds)
19+
* of the current animation, along with a reference to the currently playing
20+
* animation for the corresponding entity.
21+
*
22+
* An Animator instance needs to be updated every frame, in order for it to keep
23+
* updating the animation pose of the associated entity. The currently playing
24+
* animation can be changed at any time using the doAnimation() method. The
25+
* Animator will keep looping the current animation until a new animation is
26+
* chosen.
27+
*
28+
* The Animator calculates the desired current animation pose by interpolating
29+
* between the previous and next keyframes of the animation (based on the
30+
* current animation time). The Animator then updates the transforms all of the
31+
* joints each frame to match the current desired animation pose.
32+
*
33+
* @author Karl
34+
*
35+
*/
36+
public class Animator {
37+
38+
private float animationTime = 0;
39+
40+
public Animator() {
41+
}
42+
43+
/**
44+
* This method should be called each frame to update the animation currently
45+
* being played. This increases the animation time (and loops it back to
46+
* zero if necessary), finds the pose that the entity should be in at that
47+
* time of the animation, and then applies that pose to all the model's
48+
* joints by setting the joint transforms.
49+
*/
50+
public void update(Object3DData obj) {
51+
if (!(obj instanceof AnimatedModel)) {
52+
return;
53+
}
54+
// if (true) return;
55+
AnimatedModel animatedModel = (AnimatedModel)obj;
56+
if (animatedModel.getAnimation() == null) return;
57+
58+
increaseAnimationTime((AnimatedModel)obj);
59+
Map<String, float[]> currentPose = calculateCurrentAnimationPose(animatedModel);
60+
float parentTransform[] = new float[16];
61+
Matrix.setIdentityM(parentTransform,0);
62+
applyPoseToJoints(currentPose, (animatedModel).getRootJoint(), parentTransform);
63+
}
64+
65+
/**
66+
* Increases the current animation time which allows the animation to
67+
* progress. If the current animation has reached the end then the timer is
68+
* reset, causing the animation to loop.
69+
*/
70+
private void increaseAnimationTime(AnimatedModel obj) {
71+
animationTime += SystemClock.uptimeMillis();
72+
if (animationTime > obj.getAnimation().getLength()) {
73+
this.animationTime %= obj.getAnimation().getLength();
74+
}
75+
}
76+
77+
/**
78+
* This method returns the current animation pose of the entity. It returns
79+
* the desired local-space transforms for all the joints in a map, indexed
80+
* by the name of the joint that they correspond to.
81+
*
82+
* The pose is calculated based on the previous and next keyframes in the
83+
* current animation. Each keyframe provides the desired pose at a certain
84+
* time in the animation, so the animated pose for the current time can be
85+
* calculated by interpolating between the previous and next keyframe.
86+
*
87+
* This method first finds the preious and next keyframe, calculates how far
88+
* between the two the current animation is, and then calculated the pose
89+
* for the current animation time by interpolating between the transforms at
90+
* those keyframes.
91+
*
92+
* @return The current pose as a map of the desired local-space transforms
93+
* for all the joints. The transforms are indexed by the name ID of
94+
* the joint that they should be applied to.
95+
*/
96+
private Map<String, float[]> calculateCurrentAnimationPose(AnimatedModel obj) {
97+
KeyFrame[] frames = getPreviousAndNextFrames(obj);
98+
float progression = calculateProgression(frames[0], frames[1]);
99+
return interpolatePoses(frames[0], frames[1], progression);
100+
}
101+
102+
/**
103+
* This is the method where the animator calculates and sets those all-
104+
* important "joint transforms" that I talked about so much in the tutorial.
105+
*
106+
* This method applies the current pose to a given joint, and all of its
107+
* descendants. It does this by getting the desired local-transform for the
108+
* current joint, before applying it to the joint. Before applying the
109+
* transformations it needs to be converted from local-space to model-space
110+
* (so that they are relative to the model's origin, rather than relative to
111+
* the parent joint). This can be done by multiplying the local-transform of
112+
* the joint with the model-space transform of the parent joint.
113+
*
114+
* The same thing is then done to all the child joints.
115+
*
116+
* Finally the inverse of the joint's bind transform is multiplied with the
117+
* model-space transform of the joint. This basically "subtracts" the
118+
* joint's original bind (no animation applied) transform from the desired
119+
* pose transform. The result of this is then the transform required to move
120+
* the joint from its original model-space transform to it's desired
121+
* model-space posed transform. This is the transform that needs to be
122+
* loaded up to the vertex shader and used to transform the vertices into
123+
* the current pose.
124+
*
125+
* @param currentPose
126+
* - a map of the local-space transforms for all the joints for
127+
* the desired pose. The map is indexed by the name of the joint
128+
* which the transform corresponds to.
129+
* @param joint
130+
* - the current joint which the pose should be applied to.
131+
* @param parentTransform
132+
* - the desired model-space transform of the parent joint for
133+
* the pose.
134+
*/
135+
private void applyPoseToJoints(Map<String, float[]> currentPose, Joint joint, float[] parentTransform) {
136+
float[] currentLocalTransform = currentPose.get(joint.name);
137+
float[] currentTransform = new float[16];
138+
Matrix.multiplyMM(currentTransform,0,parentTransform,0,currentLocalTransform,0);
139+
for (Joint childJoint : joint.children) {
140+
applyPoseToJoints(currentPose, childJoint, currentTransform);
141+
}
142+
Matrix.multiplyMM(currentTransform,0,joint.getInverseBindTransform(), 0, currentTransform, 0);
143+
joint.setAnimationTransform(currentTransform);
144+
}
145+
146+
/**
147+
* Finds the previous keyframe in the animation and the next keyframe in the
148+
* animation, and returns them in an array of length 2. If there is no
149+
* previous frame (perhaps current animation time is 0.5 and the first
150+
* keyframe is at time 1.5) then the first keyframe is used as both the
151+
* previous and next keyframe. The last keyframe is used for both next and
152+
* previous if there is no next keyframe.
153+
*
154+
* @return The previous and next keyframes, in an array which therefore will
155+
* always have a length of 2.
156+
*/
157+
private KeyFrame[] getPreviousAndNextFrames(AnimatedModel obj) {
158+
KeyFrame[] allFrames = obj.getAnimation().getKeyFrames();
159+
KeyFrame previousFrame = allFrames[0];
160+
KeyFrame nextFrame = allFrames[0];
161+
for (int i = 1; i < allFrames.length; i++) {
162+
nextFrame = allFrames[i];
163+
if (nextFrame.getTimeStamp() > animationTime) {
164+
break;
165+
}
166+
previousFrame = allFrames[i];
167+
}
168+
return new KeyFrame[] { previousFrame, nextFrame };
169+
}
170+
171+
/**
172+
* Calculates how far between the previous and next keyframe the current
173+
* animation time is, and returns it as a value between 0 and 1.
174+
*
175+
* @param previousFrame
176+
* - the previous keyframe in the animation.
177+
* @param nextFrame
178+
* - the next keyframe in the animation.
179+
* @return A number between 0 and 1 indicating how far between the two
180+
* keyframes the current animation time is.
181+
*/
182+
private float calculateProgression(KeyFrame previousFrame, KeyFrame nextFrame) {
183+
float totalTime = nextFrame.getTimeStamp() - previousFrame.getTimeStamp();
184+
float currentTime = animationTime - previousFrame.getTimeStamp();
185+
return currentTime / totalTime;
186+
}
187+
188+
/**
189+
* Calculates all the local-space joint transforms for the desired current
190+
* pose by interpolating between the transforms at the previous and next
191+
* keyframes.
192+
*
193+
* @param previousFrame
194+
* - the previous keyframe in the animation.
195+
* @param nextFrame
196+
* - the next keyframe in the animation.
197+
* @param progression
198+
* - a number between 0 and 1 indicating how far between the
199+
* previous and next keyframes the current animation time is.
200+
* @return The local-space transforms for all the joints for the desired
201+
* current pose. They are returned in a map, indexed by the name of
202+
* the joint to which they should be applied.
203+
*/
204+
private Map<String, float[]> interpolatePoses(KeyFrame previousFrame, KeyFrame nextFrame, float progression) {
205+
Map<String, float[]> currentPose = new HashMap<String, float[]>();
206+
for (String jointName : previousFrame.getJointKeyFrames().keySet()) {
207+
JointTransform previousTransform = previousFrame.getJointKeyFrames().get(jointName);
208+
JointTransform nextTransform = nextFrame.getJointKeyFrames().get(jointName);
209+
JointTransform currentTransform = JointTransform.interpolate(previousTransform, nextTransform, progression);
210+
currentPose.put(jointName, currentTransform.getLocalTransform());
211+
}
212+
return currentPose;
213+
}
214+
215+
}

0 commit comments

Comments
 (0)