2 * Copyright (c) 2010 The Desert team
4 * Permission is hereby granted, free of charge, to any person
5 * obtaining a copy of this software and associated documentation
6 * files (the "Software"), to deal in the Software without
7 * restriction, including without limitation the rights to use,
8 * copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 * OTHER DEALINGS IN THE SOFTWARE.
26 package net
.sourceforge
.desert
.ui
;
28 import java
.awt
.Canvas
;
29 import java
.awt
.Dimension
;
30 import java
.awt
.Graphics
;
31 import java
.awt
.Point
;
32 import java
.awt
.event
.ActionEvent
;
33 import java
.awt
.event
.ActionListener
;
34 import java
.awt
.event
.ComponentAdapter
;
35 import java
.awt
.event
.ComponentEvent
;
36 import java
.awt
.event
.MouseAdapter
;
37 import java
.awt
.event
.MouseEvent
;
38 import java
.awt
.image
.BufferedImage
;
39 import java
.util
.Arrays
;
40 import java
.util
.Iterator
;
42 import net
.sourceforge
.desert
.ImageFilter
;
43 import net
.sourceforge
.desert
.ImageFilterClearImplementation
;
44 import net
.sourceforge
.desert
.Particle
;
45 import net
.sourceforge
.desert
.ParticleEngine
;
46 import net
.sourceforge
.desert
.ParticleRenderer
;
47 import net
.sourceforge
.desert
.Utilities
;
50 * The drawing board, extending the graphics Canvas.
51 * @author codistmonk (creation 2010-04-13)
53 @SuppressWarnings("serial")
54 public class DrawingBoard
extends Canvas
implements ActionListener
{
56 private final ParticleRenderer particleRenderer
;
58 private ParticleEngine particleEngine
;
60 private Particle
.Type particleType
;
62 private BufferedImage buffer
;
64 private ImageFilter imageFilter
;
66 private boolean paused
;
69 * This mask covers the board and is used to tell where particles can be added.
70 * <br>It should be updated at the same time as the drawing buffer.
72 private boolean[][] particleMask
;
74 private boolean[][] brush
;
76 private BrushMode brushMode
;
78 public DrawingBoard() {
79 this.particleRenderer
= new ParticleRenderer();
80 this.particleEngine
= new ParticleEngine();
81 this.particleType
= Particle
.Type
.values()[0];
82 this.imageFilter
= new ImageFilterClearImplementation();
83 this.brush
= Utilities
.getBrush(Utilities
.listBrushPaths().get(0));
84 this.brushMode
= BrushMode
.ADD
;
86 this.addComponentListener(this.new ResizeHandler());
88 final MouseHandler mouseHandler
= this.new MouseHandler();
90 this.addMouseListener(mouseHandler
);
91 this.addMouseMotionListener(mouseHandler
);
93 // This doesn't seem of much use in applets
94 this.setPreferredSize(new Dimension(PREFERRED_WIDTH
, PREFERRED_HEIGHT
));
98 public final void actionPerformed(final ActionEvent event
) {
99 if (!this.isPaused()) {
100 this.getParticleEngine().update(((Timer
.Event
) event
).getDeltaTime());
107 public final void paint(final Graphics g
) {
108 if (this.buffer
!= null) {
111 this.drawParticlesToBuffer();
120 * <br>A non-null value
123 public final boolean[][] getBrush() {
130 * <br>Should not be null
131 * <br>Reference parameter
133 public final void setBrush(final boolean[][] brush
) {
140 * <br>A non-null value
143 public final BrushMode
getBrushMode() {
144 return this.brushMode
;
150 * <br>Should not be null
151 * <br>Reference parameter
153 public final void setBrushMode(final BrushMode brushMode
) {
154 this.brushMode
= brushMode
;
161 public boolean isPaused() {
169 public void setPaused(final boolean paused
) {
170 this.paused
= paused
;
176 * <br>A non-null value
179 public final ImageFilter
getImageFilter() {
180 return this.imageFilter
;
186 * <br>Should not be null
187 * <br>Reference parameter
189 public final void setImageFilter(final ImageFilter filter
) {
190 this.imageFilter
= filter
;
196 * <br>Range: <code>[0 .. Integer.MAX_VALUE]</code>
198 public final int getParticleCount() {
199 return this.getParticleEngine().getParticleCount();
205 * <br>A non-null value
208 public final ParticleEngine
getParticleEngine() {
209 return this.particleEngine
;
214 * @param particleEngine
215 * <br>Should not be null
216 * <br>Reference parameter
218 public final void setParticleEngine(final ParticleEngine particleEngine
) {
219 if (particleEngine
!= this.getParticleEngine()) {
220 for (final Particle particle
: this.getParticleEngine()) {
221 particleEngine
.addParticle(particle
.getType(), particle
.getX(), particle
.getY(), particle
.getSpeedX(), particle
.getSpeedY(), particle
.getMass(), particle
.getTemp());
224 this.particleEngine
= particleEngine
;
226 this.getParticleEngine().setBoardSize(this.getSize());
233 * <br>A non-null value
235 public final Particle
.Type
getParticleType() {
236 return this.particleType
;
242 * <br>Should not be null
244 public final void setParticleType(final Particle
.Type type
) {
245 this.particleType
= type
;
251 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
253 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
255 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
257 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
259 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
261 final void addParticle(final float x
, final float y
, final float speedX
, final float speedY
, final float mass
, final Integer temp
) {
262 this.getParticleEngine().addParticle(this.getParticleType(), x
, y
, speedX
, speedY
, mass
, temp
);
265 final void resizeBuffer() {
266 if (this.getWidth() >0 && this.getHeight() > 0) {
267 this.buffer
= new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage
.TYPE_3BYTE_BGR
);
268 this.particleMask
= new boolean[this.getHeight()][this.getWidth()];
269 this.getParticleEngine().setBoardSize(this.getSize());
270 this.particleRenderer
.setBuffer(this.buffer
);
280 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
282 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
283 * @return <code>true</code> if the specified location is outside the board or contain no particle
285 final boolean canAddParticle(final int x
, final int y
) {
286 return this.isInsideBoard(x
, y
) && !this.particleMask
[y
][x
];
289 private final void clearBuffer() {
290 this.buffer
= this.imageFilter
.filter(this.buffer
);
292 for (int i
= 0; i
< this.particleMask
.length
; ++i
) {
293 Arrays
.fill(this.particleMask
[i
], false);
296 for (final Particle particle
: this.getParticleEngine()) {
297 if (this.isInsideBoard((int) particle
.getX(), (int) particle
.getY())) {
298 this.particleMask
[(int) particle
.getY()][(int) particle
.getX()] = true;
302 this.particleRenderer
.setBuffer(this.buffer
);
305 private final void drawParticlesToBuffer() {
306 for (final Particle particle
: this.getParticleEngine()) {
307 this.particleRenderer
.draw(particle
);
309 if (this.isInsideBoard((int) particle
.getX(), (int) particle
.getY())) {
310 this.particleMask
[(int) particle
.getY()][(int) particle
.getX()] = true;
318 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
320 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
323 private final boolean isInsideBoard(final int x
, final int y
) {
324 return 0 <= x
&& x
< this.getWidth() && 0 <= y
&& y
< this.getHeight();
330 * <br>Should not be null
332 private final void drawBuffer(final Graphics graphics
) {
333 graphics
.drawImage(this.buffer
, 0, 0, null);
338 * @author codistmonk (creation 2010-04-13)
340 private final class ResizeHandler
extends ComponentAdapter
{
343 public final void componentResized(final ComponentEvent event
) {
344 DrawingBoard
.this.resizeBuffer();
350 * The mouse handler adds particles to the board when the user presses a mouse button or performs a drag gesture.
351 * @author codistmonk (creation 2010-04-13)
353 private final class MouseHandler
extends MouseAdapter
{
355 // TODO: Use triggers set off by classes called Desert*Element
357 private Point lastPoint
;
360 public final void mouseDragged(final MouseEvent event
) {
361 if (this.lastPoint
!= null) {
362 this.addOrRemoveParticleStreak(this.lastPoint
, event
.getPoint());
365 this.addOrRemoveParticles(event
.getX(), event
.getY());
368 this.lastPoint
= event
.getPoint();
372 public final void mousePressed(final MouseEvent event
) {
373 this.addOrRemoveParticles(event
.getX(), event
.getY());
375 this.lastPoint
= null;
381 * <br>Should not be null
383 * <br>Should not be null
385 private final void addOrRemoveParticleStreak(final Point origin
, final Point destination
) {
386 float deltaX
= destination
.x
- origin
.x
;
387 float deltaY
= destination
.y
- origin
.y
;
388 final float pixelCount
= Math
.max(Math
.abs(deltaX
), Math
.abs(deltaY
));
390 if (pixelCount
> 0) {
391 deltaX
/= pixelCount
;
392 deltaY
/= pixelCount
;
397 for (int i
= 0; i
< pixelCount
; ++i
) {
398 this.addOrRemoveParticles((int) x
, (int) y
);
407 * There is no real limit on <code>x</code> and <code>y</code> because
408 * particles can be created outside the board and moved inside by the engines.
410 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
412 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
414 private final void addOrRemoveParticles(final int x
, final int y
) {
415 switch (DrawingBoard
.this.getBrushMode()) {
417 this.addParticles(x
, y
);
420 this.removeParticles(x
, y
);
428 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
430 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
432 private final void addParticles(final int x
, final int y
) {
433 final boolean[][] brush
= DrawingBoard
.this.getBrush();
434 final int halfWidth
= brush
[0].length
/ 2;
435 final int halfHeight
= brush
.length
/ 2;
437 for (int i
= 0; i
< brush
.length
; ++i
) {
438 for (int j
= 0; j
< brush
[0].length
; ++j
) {
439 final int x2
= x
- halfWidth
+ j
;
440 final int y2
= DrawingBoard
.this.getHeight() - y
+ halfHeight
- i
;
442 if (brush
[i
][j
] && DrawingBoard
.this.canAddParticle(x2
, y2
)) {
443 DrawingBoard
.this.particleMask
[y2
][x2
] = true;
444 DrawingBoard
.this.addParticle(jitter(x2
), jitter(y2
), 0F
, 0F
, jitter(DrawingBoard
.this.getParticleType().getMass()), DrawingBoard
.this.getParticleType().getTemp());
453 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
455 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
457 private final void removeParticles(final int x
, final int y
) {
458 final boolean[][] brush
= DrawingBoard
.this.getBrush();
459 final int halfWidth
= brush
[0].length
/ 2;
460 final int halfHeight
= brush
.length
/ 2;
462 for (int i
= 0; i
< brush
.length
; ++i
) {
463 for (int j
= 0; j
< brush
[0].length
; ++j
) {
465 final int x2
= x
- halfWidth
+ j
;
466 final int y2
= DrawingBoard
.this.getHeight() - y
+ halfHeight
- i
;
468 for (final Iterator
<Particle
> iterator
= DrawingBoard
.this.getParticleEngine().iterator(); iterator
.hasNext();) {
469 final Particle particle
= iterator
.next();
471 if (x2
== (int) particle
.getX() && y2
==(int) particle
.getY()) {
482 public static final int PREFERRED_WIDTH
= 400;
484 public static final int PREFERRED_HEIGHT
= 300;
487 * Use this function so that particle coordinates aren't perfect integers.
488 * <br>having perfect integers can restrict the particles to vertical motion if
489 * the only applied force is gravity.
491 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
493 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
495 private static final float jitter(final float value
) {
496 return value
+ (float) Math
.random();
501 * @author codistmonk (creation 2010-04-28)
503 public static enum BrushMode
{