Removed ability for the user to create particles outside the drawing board.
[desert.git] / src / org / sourceforge / desert / ui / DrawingBoard.java
blob4612318c46f04bf01991e4edfe89fbd893e1ebe0
1 /*
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
11 * conditions:
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;
39 import java.io.File;
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;
50 /**
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;
69 /**
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() {
99 try {
100 Utilities.debugPrint(Utilities.listSubresources(Utilities.RESOURCE_BASE + "images/brush_"));
101 } catch (final Exception exception) {
102 exception.printStackTrace();
104 System.exit(0);
105 return null;
108 @Override
109 public final void actionPerformed(final ActionEvent event) {
110 if (!this.isPaused()) {
111 this.getParticleEngine().update(((Timer.Event) event).getDeltaTime());
114 this.repaint();
117 @Override
118 public final void paint(final Graphics g) {
119 if (this.buffer != null) {
120 this.clearBuffer();
122 this.drawParticlesToBuffer();
124 this.drawBuffer(g);
130 * @return
131 * <br>A non-null value
132 * <br>A reference
134 public final boolean[][] getBrush() {
135 return brush;
140 * @param brush
141 * <br>Should not be null
142 * <br>Reference parameter
144 public final void setBrush(final boolean[][] brush) {
145 this.brush = brush;
150 * @return
151 * <br>A non-null value
152 * <br>A reference
154 public final BrushMode getBrushMode() {
155 return this.brushMode;
160 * @param brushMode
161 * <br>Should not be null
162 * <br>Reference parameter
164 public final void setBrushMode(final BrushMode brushMode) {
165 this.brushMode = brushMode;
170 * @return
172 public boolean isPaused() {
173 return this.paused;
178 * @param paused
180 public void setPaused(final boolean paused) {
181 this.paused = paused;
186 * @return
187 * <br>A non-null value
188 * <br>A reference
190 public final ImageFilter getImageFilter() {
191 return this.imageFilter;
196 * @param filter
197 * <br>Should not be null
198 * <br>Reference parameter
200 public final void setImageFilter(final ImageFilter filter) {
201 this.imageFilter = filter;
206 * @return
207 * <br>Range: <code>[0 .. Integer.MAX_VALUE]</code>
209 public final int getParticleCount() {
210 return this.getParticleEngine().getParticleCount();
215 * @return
216 * <br>A non-null value
217 * <br>A reference
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());
243 * @return
244 * <br>A non-null value
246 public final Particle.Type getParticleType() {
247 return this.particleType;
252 * @param type
253 * <br>Should not be null
255 public final void setParticleType(final Particle.Type type) {
256 this.particleType = type;
260 * Add a new particle
261 * @param x
262 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
263 * @param y
264 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
265 * @param speedX
266 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
267 * @param speedY
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);
281 else {
282 this.buffer = null;
288 * @param x
289 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
290 * @param y
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;
326 * @param x
327 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
328 * @param y
329 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
330 * @return
332 private final boolean isInsideBoard(final int x, final int y) {
333 return 0 <= x && x < this.getWidth() && 0 <= y && y < this.getHeight();
338 * @param graphics
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 {
351 @Override
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;
368 @Override
369 public final void mouseDragged(final MouseEvent event) {
370 if (this.lastPoint != null) {
371 this.addOrRemoveParticleStreak(this.lastPoint, event.getPoint());
373 else {
374 this.addOrRemoveParticles(event.getX(), event.getY());
377 this.lastPoint = event.getPoint();
380 @Override
381 public final void mousePressed(final MouseEvent event) {
382 this.addOrRemoveParticles(event.getX(), event.getY());
384 this.lastPoint = null;
389 * @param origin
390 * <br>Should not be null
391 * @param destination
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;
403 float x = origin.x;
404 float y = origin.y;
406 for (int i = 0; i < pixelCount; ++i) {
407 this.addOrRemoveParticles((int) x, (int) y);
409 x += deltaX;
410 y += deltaY;
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.
418 * @param x
419 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
420 * @param y
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()) {
425 case ADD:
426 this.addParticles(x, y);
427 break;
428 case REMOVE:
429 this.removeParticles(x, y);
430 break;
436 * @param x
437 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
438 * @param y
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);
461 * @param x
462 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
463 * @param y
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) {
473 if (brush[i][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()) {
481 iterator.remove();
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.
499 * @param value
500 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
501 * @return
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 {
514 ADD, REMOVE;