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 org
.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
;
40 import java
.util
.Arrays
;
41 import java
.util
.Iterator
;
42 import org
.sourceforge
.desert
.ImageFilter
;
43 import org
.sourceforge
.desert
.ImageFilterClearImplementation
;
44 import org
.sourceforge
.desert
.Particle
;
45 import org
.sourceforge
.desert
.ParticleEngine
;
46 import org
.sourceforge
.desert
.ParticleEngineCustomImplementation
;
47 import org
.sourceforge
.desert
.ParticleRenderer
;
48 import org
.sourceforge
.desert
.Utilities
;
51 * The drawing board, extending the graphics Canvas.
52 * @author codistmonk (creation 2010-04-13)
54 @SuppressWarnings("serial")
55 public class DrawingBoard
extends Canvas
implements ActionListener
{
57 private final ParticleRenderer particleRenderer
;
59 private ParticleEngine particleEngine
;
61 private Particle
.Type particleType
;
63 private BufferedImage buffer
;
65 private ImageFilter imageFilter
;
67 private boolean paused
;
70 * This mask covers the board and is used to tell where particles can be added.
71 * <br>It should be updated at the same time as the drawing buffer.
73 private boolean[][] particleMask
;
75 private boolean[][] brush
;
77 private BrushMode brushMode
;
79 public DrawingBoard() {
80 this.particleRenderer
= new ParticleRenderer();
81 this.particleEngine
= new ParticleEngineCustomImplementation();
82 this.particleType
= Particle
.Type
.values()[0];
83 this.imageFilter
= new ImageFilterClearImplementation();
84 this.brush
= Utilities
.getBrush(Utilities
.listBrushPaths().get(0));
85 this.brushMode
= BrushMode
.ADD
;
87 this.addComponentListener(this.new ResizeHandler());
89 final MouseHandler mouseHandler
= this.new MouseHandler();
91 this.addMouseListener(mouseHandler
);
92 this.addMouseMotionListener(mouseHandler
);
94 // This doesn't seem of much use in applets
95 this.setPreferredSize(new Dimension(PREFERRED_WIDTH
, PREFERRED_HEIGHT
));
98 public static final File
[] getBrushFiles() {
100 Utilities
.debugPrint(Utilities
.listSubresources(Utilities
.RESOURCE_BASE
+ "images/brush_"));
101 } catch (final Exception exception
) {
102 exception
.printStackTrace();
109 public final void actionPerformed(final ActionEvent event
) {
110 if (!this.isPaused()) {
111 this.getParticleEngine().update(((Timer
.Event
) event
).getDeltaTime());
118 public final void paint(final Graphics g
) {
119 if (this.buffer
!= null) {
122 this.drawParticlesToBuffer();
131 * <br>A non-null value
134 public final boolean[][] getBrush() {
141 * <br>Should not be null
142 * <br>Reference parameter
144 public final void setBrush(final boolean[][] brush
) {
151 * <br>A non-null value
154 public final BrushMode
getBrushMode() {
155 return this.brushMode
;
161 * <br>Should not be null
162 * <br>Reference parameter
164 public final void setBrushMode(final BrushMode brushMode
) {
165 this.brushMode
= brushMode
;
172 public boolean isPaused() {
180 public void setPaused(final boolean paused
) {
181 this.paused
= paused
;
187 * <br>A non-null value
190 public final ImageFilter
getImageFilter() {
191 return this.imageFilter
;
197 * <br>Should not be null
198 * <br>Reference parameter
200 public final void setImageFilter(final ImageFilter filter
) {
201 this.imageFilter
= filter
;
207 * <br>Range: <code>[0 .. Integer.MAX_VALUE]</code>
209 public final int getParticleCount() {
210 return this.getParticleEngine().getParticleCount();
216 * <br>A non-null value
219 public final ParticleEngine
getParticleEngine() {
220 return this.particleEngine
;
225 * @param particleEngine
226 * <br>Should not be null
227 * <br>Reference parameter
229 public final void setParticleEngine(final ParticleEngine particleEngine
) {
230 if (particleEngine
!= this.getParticleEngine()) {
231 for (final Particle particle
: this.getParticleEngine()) {
232 particleEngine
.addParticle(particle
.getType(), particle
.getX(), particle
.getY(), particle
.getSpeedX(), particle
.getSpeedY());
235 this.particleEngine
= particleEngine
;
237 this.getParticleEngine().setBoardSize(this.getSize());
244 * <br>A non-null value
246 public final Particle
.Type
getParticleType() {
247 return this.particleType
;
253 * <br>Should not be null
255 public final void setParticleType(final Particle
.Type type
) {
256 this.particleType
= type
;
262 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
264 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
266 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
268 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
270 final void addParticle(final float x
, final float y
, final float speedX
, final float speedY
) {
271 this.getParticleEngine().addParticle(this.getParticleType(), x
, y
, speedX
, speedY
);
274 final void resizeBuffer() {
275 if (this.getWidth() >0 && this.getHeight() > 0) {
276 this.buffer
= new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage
.TYPE_3BYTE_BGR
);
277 this.particleMask
= new boolean[this.getHeight()][this.getWidth()];
278 this.getParticleEngine().setBoardSize(this.getSize());
279 this.particleRenderer
.setBuffer(this.buffer
);
289 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
291 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
292 * @return <code>true</code> if the specified location is outside the board or contain no particle
294 final boolean canAddParticle(final int x
, final int y
) {
295 return this.isInsideBoard(x
, y
) && !this.particleMask
[y
][x
];
298 private final void clearBuffer() {
299 this.buffer
= this.imageFilter
.filter(this.buffer
);
301 for (int i
= 0; i
< this.particleMask
.length
; ++i
) {
302 Arrays
.fill(this.particleMask
[i
], false);
305 for (final Particle particle
: this.getParticleEngine()) {
306 if (this.isInsideBoard((int) particle
.getX(), (int) particle
.getY())) {
307 this.particleMask
[(int) particle
.getY()][(int) particle
.getX()] = true;
311 this.particleRenderer
.setBuffer(this.buffer
);
314 private final void drawParticlesToBuffer() {
315 for (final Particle particle
: this.getParticleEngine()) {
316 this.particleRenderer
.draw(particle
);
318 if (this.isInsideBoard((int) particle
.getX(), (int) particle
.getY())) {
319 this.particleMask
[(int) particle
.getY()][(int) particle
.getX()] = true;
327 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
329 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
332 private final boolean isInsideBoard(final int x
, final int y
) {
333 return 0 <= x
&& x
< this.getWidth() && 0 <= y
&& y
< this.getHeight();
339 * <br>Should not be null
341 private final void drawBuffer(final Graphics graphics
) {
342 graphics
.drawImage(this.buffer
, 0, 0, null);
347 * @author codistmonk (creation 2010-04-13)
349 private final class ResizeHandler
extends ComponentAdapter
{
352 public final void componentResized(final ComponentEvent event
) {
353 DrawingBoard
.this.resizeBuffer();
359 * The mouse handler adds particles to the board when the user presses a mouse button or performs a drag gesture.
360 * @author codistmonk (creation 2010-04-13)
362 private final class MouseHandler
extends MouseAdapter
{
364 // TODO: Use triggers set off by classes called Desert*Element
366 private Point lastPoint
;
369 public final void mouseDragged(final MouseEvent event
) {
370 if (this.lastPoint
!= null) {
371 this.addOrRemoveParticleStreak(this.lastPoint
, event
.getPoint());
374 this.addOrRemoveParticles(event
.getX(), event
.getY());
377 this.lastPoint
= event
.getPoint();
381 public final void mousePressed(final MouseEvent event
) {
382 this.addOrRemoveParticles(event
.getX(), event
.getY());
384 this.lastPoint
= null;
390 * <br>Should not be null
392 * <br>Should not be null
394 private final void addOrRemoveParticleStreak(final Point origin
, final Point destination
) {
395 float deltaX
= destination
.x
- origin
.x
;
396 float deltaY
= destination
.y
- origin
.y
;
397 final float pixelCount
= Math
.max(Math
.abs(deltaX
), Math
.abs(deltaY
));
399 if (pixelCount
> 0) {
400 deltaX
/= pixelCount
;
401 deltaY
/= pixelCount
;
406 for (int i
= 0; i
< pixelCount
; ++i
) {
407 this.addOrRemoveParticles((int) x
, (int) y
);
416 * There is no real limit on <code>x</code> and <code>y</code> because
417 * particles can be created outside the board and moved inside by the engines.
419 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
421 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
423 private final void addOrRemoveParticles(final int x
, final int y
) {
424 switch (DrawingBoard
.this.getBrushMode()) {
426 this.addParticles(x
, y
);
429 this.removeParticles(x
, y
);
437 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
439 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
441 private final void addParticles(final int x
, final int y
) {
442 final boolean[][] brush
= DrawingBoard
.this.getBrush();
443 final int halfWidth
= brush
[0].length
/ 2;
444 final int halfHeight
= brush
.length
/ 2;
446 for (int i
= 0; i
< brush
.length
; ++i
) {
447 for (int j
= 0; j
< brush
[0].length
; ++j
) {
448 final int x2
= x
- halfWidth
+ j
;
449 final int y2
= DrawingBoard
.this.getHeight() - y
+ halfHeight
- i
;
451 if (brush
[i
][j
] && DrawingBoard
.this.canAddParticle(x2
, y2
)) {
452 DrawingBoard
.this.particleMask
[y2
][x2
] = true;
453 DrawingBoard
.this.addParticle(jitter(x2
), jitter(y2
), 0F
, 0F
);
462 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
464 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
466 private final void removeParticles(final int x
, final int y
) {
467 final boolean[][] brush
= DrawingBoard
.this.getBrush();
468 final int halfWidth
= brush
[0].length
/ 2;
469 final int halfHeight
= brush
.length
/ 2;
471 for (int i
= 0; i
< brush
.length
; ++i
) {
472 for (int j
= 0; j
< brush
[0].length
; ++j
) {
474 final int x2
= x
- halfWidth
+ j
;
475 final int y2
= DrawingBoard
.this.getHeight() - y
+ halfHeight
- i
;
477 for (final Iterator
<Particle
> iterator
= DrawingBoard
.this.getParticleEngine().iterator(); iterator
.hasNext();) {
478 final Particle particle
= iterator
.next();
480 if (x2
== (int) particle
.getX() && y2
==(int) particle
.getY()) {
491 public static final int PREFERRED_WIDTH
= 400;
493 public static final int PREFERRED_HEIGHT
= 300;
496 * Use this function so that particle coordinates aren't perfect integers.
497 * <br>having perfect integers can restrict the particles to vertical motion if
498 * the only applied force is gravity.
500 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
502 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
504 private static final float jitter(final float value
) {
505 return value
+ (float) Math
.random();
510 * @author codistmonk (creation 2010-04-28)
512 public static enum BrushMode
{