Initial temp support. (Thanks, Jack)
[desert.git] / src / net / sourceforge / desert / ui / DrawingBoard.java
blob0d7aa6be5fcfe58c3c40986fba321fee96154241
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 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;
49 /**
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;
68 /**
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));
97 @Override
98 public final void actionPerformed(final ActionEvent event) {
99 if (!this.isPaused()) {
100 this.getParticleEngine().update(((Timer.Event) event).getDeltaTime());
103 this.repaint();
106 @Override
107 public final void paint(final Graphics g) {
108 if (this.buffer != null) {
109 this.clearBuffer();
111 this.drawParticlesToBuffer();
113 this.drawBuffer(g);
119 * @return
120 * <br>A non-null value
121 * <br>A reference
123 public final boolean[][] getBrush() {
124 return brush;
129 * @param brush
130 * <br>Should not be null
131 * <br>Reference parameter
133 public final void setBrush(final boolean[][] brush) {
134 this.brush = brush;
139 * @return
140 * <br>A non-null value
141 * <br>A reference
143 public final BrushMode getBrushMode() {
144 return this.brushMode;
149 * @param brushMode
150 * <br>Should not be null
151 * <br>Reference parameter
153 public final void setBrushMode(final BrushMode brushMode) {
154 this.brushMode = brushMode;
159 * @return
161 public boolean isPaused() {
162 return this.paused;
167 * @param paused
169 public void setPaused(final boolean paused) {
170 this.paused = paused;
175 * @return
176 * <br>A non-null value
177 * <br>A reference
179 public final ImageFilter getImageFilter() {
180 return this.imageFilter;
185 * @param filter
186 * <br>Should not be null
187 * <br>Reference parameter
189 public final void setImageFilter(final ImageFilter filter) {
190 this.imageFilter = filter;
195 * @return
196 * <br>Range: <code>[0 .. Integer.MAX_VALUE]</code>
198 public final int getParticleCount() {
199 return this.getParticleEngine().getParticleCount();
204 * @return
205 * <br>A non-null value
206 * <br>A reference
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());
232 * @return
233 * <br>A non-null value
235 public final Particle.Type getParticleType() {
236 return this.particleType;
241 * @param type
242 * <br>Should not be null
244 public final void setParticleType(final Particle.Type type) {
245 this.particleType = type;
249 * Add a new particle
250 * @param x
251 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
252 * @param y
253 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
254 * @param speedX
255 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
256 * @param speedY
257 * <br>Range: <code>[Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY]</code>
258 * @param mass
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);
272 else {
273 this.buffer = null;
279 * @param x
280 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
281 * @param y
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;
317 * @param x
318 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
319 * @param y
320 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]</code>
321 * @return
323 private final boolean isInsideBoard(final int x, final int y) {
324 return 0 <= x && x < this.getWidth() && 0 <= y && y < this.getHeight();
329 * @param graphics
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 {
342 @Override
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;
359 @Override
360 public final void mouseDragged(final MouseEvent event) {
361 if (this.lastPoint != null) {
362 this.addOrRemoveParticleStreak(this.lastPoint, event.getPoint());
364 else {
365 this.addOrRemoveParticles(event.getX(), event.getY());
368 this.lastPoint = event.getPoint();
371 @Override
372 public final void mousePressed(final MouseEvent event) {
373 this.addOrRemoveParticles(event.getX(), event.getY());
375 this.lastPoint = null;
380 * @param origin
381 * <br>Should not be null
382 * @param destination
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;
394 float x = origin.x;
395 float y = origin.y;
397 for (int i = 0; i < pixelCount; ++i) {
398 this.addOrRemoveParticles((int) x, (int) y);
400 x += deltaX;
401 y += deltaY;
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.
409 * @param x
410 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
411 * @param y
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()) {
416 case ADD:
417 this.addParticles(x, y);
418 break;
419 case REMOVE:
420 this.removeParticles(x, y);
421 break;
427 * @param x
428 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
429 * @param y
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());
452 * @param x
453 * <br>Range: <code>[Integer.MIN_VALUE .. Integer.MAX_VALUE]]</code>
454 * @param y
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) {
464 if (brush[i][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()) {
472 iterator.remove();
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.
490 * @param value
491 * <br>Range: <code>]Float.NEGATIVE_INFINITY .. Float.POSITIVE_INFINITY[</code>
492 * @return
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 {
505 ADD, REMOVE;