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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
package ch.epfl.maze.graphics;
import ch.epfl.maze.util.Action;
import ch.epfl.maze.util.Direction;
import ch.epfl.maze.util.Vector2D;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
/**
* Graphic component of an animal that will be drawn by an {@link Animation}.
*
* @author EPFL
*/
public final class GraphicComponent {
/* constants */
public static final int MAXIMUM_FRAMES = 4;
public static final int SQUARE_SIZE = Display.SQUARE_SIZE;
/* drawing variables */
private final Vector2D mPosition;
private final BufferedImage mImage;
private Action mAction;
private boolean mRotate;
/**
* Constructs a graphic component with the image of the animal, the position
* at which the component needs to be drawn, and the corresponding action
* that it needs to perform.
*
* @param image Image of animal
* @param position Position at which the image will be drawn
* @param action Action that the component needs to perform
*/
public GraphicComponent(BufferedImage image, Vector2D position, Action action) {
// sanity checks
if (image == null) {
throw new IllegalArgumentException("BufferedImage cannot be null.");
}
if (position == null) {
throw new IllegalArgumentException("Position cannot be null.");
}
if (action == null) {
action = new Action(Direction.NONE, false);
}
// default values
mImage = image;
mPosition = position;
mRotate = true;
mAction = action;
}
/**
* Notifies the component that it will die between two squares.
*/
public void willDieMoving() {
mAction = new Action(mAction.getDirection(), mAction.isSuccessful(), true);
}
/**
* Asks the graphic component to paint itself on graphic environment, by
* performing a ratio of its action.
*
* @param ratio Ratio of the action to be performed
* @param g Graphic environment
* @param targetWindow Window on which the graphic is being drawn
*/
public void paint(float ratio, Graphics2D g, ImageObserver targetWindow) {
if (mAction.getDirection() == Direction.NONE) {
renderStuck(g, targetWindow);
} else {
if (ratio > 0.5) {
if (!mAction.diesBetweenSquares() && !mAction.isSuccessful()) {
renderMove(1 - ratio, g, targetWindow, true);
} else if (!mAction.diesBetweenSquares()) {
renderMove(ratio, g, targetWindow, false);
}
} else {
renderMove(ratio, g, targetWindow, false);
}
}
}
/**
* Draws the moving component on graphics environment and target window.
* <p>
* The function draws the animal at {@code (position + ratio*heading)}.
*
* @param ratio Ratio of action performed by animation
* @param g Graphic environment
* @param targetWindow Frame display
* @param buzz Buzzes the animal, used when he has just hit a wall
*/
private void renderMove(float ratio, Graphics2D g, ImageObserver targetWindow, boolean buzz) {
// transforms direction into vector
Vector2D heading = mAction.getDirection().toVector().mul(SQUARE_SIZE);
Vector2D normalized = heading.normalize();
// loads the correct frame
BufferedImage img = cropImage(ratio, mAction.getDirection());
AffineTransform reset = new AffineTransform();
// applies translation
double newX = (mPosition.getX() + ratio * heading.getX());
double newY = (mPosition.getY() + ratio * heading.getY());
reset.translate(newX, newY);
// applies rotation
double rotation = 0;
if (buzz) {
rotation = -(Math.PI / 6.0) * Math.sin((60 * ratio) / Math.PI);
}
if (mRotate) {
rotation += Math.atan2(normalized.getY(), normalized.getX()) - Math.PI / 2;
}
reset.rotate(rotation, SQUARE_SIZE / 2, SQUARE_SIZE / 2);
// transforms and draws image
g.setTransform(reset);
g.drawImage(img, 0, 0, targetWindow);
// inverts transformations
reset.rotate(-rotation, SQUARE_SIZE / 2, SQUARE_SIZE / 2);
reset.translate(-newX, -newY);
g.setTransform(reset);
}
/**
* Draws the still component on graphics environment and target window.
* <p>
* Draws a question mark if {@code mAction} is not successful
*
* @param g Graphic environment
* @param targetWindow Frame display
*/
private void renderStuck(Graphics2D g, ImageObserver targetWindow) {
// loads default frame of image with default direction
BufferedImage img = cropImage(-1, Direction.NONE);
AffineTransform reset = new AffineTransform();
// applies translation
double newX = mPosition.getX();
double newY = mPosition.getY();
reset.translate(newX, newY);
// transforms and draws image
g.setTransform(reset);
g.drawImage(img, 0, 0, targetWindow);
// draws interrogation mark
if (!mAction.isSuccessful()) {
ImageIcon icon = new ImageIcon("img/unknown.png");
g.drawImage(icon.getImage(), SQUARE_SIZE - icon.getIconWidth() - 2, 2, targetWindow);
}
// inverts translation
reset.translate(-newX, -newY);
g.setTransform(reset);
}
/**
* Transforms the image according to frame and movement.
*
* @param ratio The ratio of the action to perform
* @param dir The direction towards which the component faces
* @return The correct frame that faces towards the direction specified
*/
private BufferedImage cropImage(float ratio, Direction dir) {
int width = mImage.getWidth();
int height = mImage.getHeight();
int frames = width / SQUARE_SIZE;
int moves = height / SQUARE_SIZE;
// sanity checks
if (width % SQUARE_SIZE != 0 || height % SQUARE_SIZE != 0) {
throw new UnsupportedOperationException(
"Image size is not a multiple of " + SQUARE_SIZE + " pixels, but " + width + "x" + height);
}
if (moves > Direction.values().length) {
throw new UnsupportedOperationException(
"Image height has more than " + Direction.values().length + " moves (" + height + ")");
}
if (frames > MAXIMUM_FRAMES) {
throw new UnsupportedOperationException(
"Image width has more than " + MAXIMUM_FRAMES + " frames (" + frames + ")");
}
// selects frame
int frame = ((int) (ratio * 2 * frames)) % frames;
if (frame >= frames) {
frame = 0;
} else if (ratio < 0 || ratio > 1) { // handles bad ratio
frame = (int) (frames / 2);
}
// selects move
int move = dir.intValue();
mRotate = false;
if (move >= moves) {
mRotate = true;
move = 0;
}
// selects subimage according to frame and move
BufferedImage img = mImage.getSubimage(frame * SQUARE_SIZE, move * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);
return img;
}
}
|