Files
narya/src/java/com/threerings/miso/client/IsoSceneView.java
T
Walter Korman 6e7c8e29c5 Implemented tile deletion from the scene.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@205 542714f4-19e9-0310-aa3c-eee0fc999fb1
2001-08-09 05:44:07 +00:00

559 lines
15 KiB
Java

//
// $Id: IsoSceneView.java,v 1.32 2001/08/09 05:44:07 shaper Exp $
package com.threerings.miso.scene;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.ArrayList;
import com.threerings.miso.Log;
import com.threerings.miso.sprite.*;
import com.threerings.miso.tile.Tile;
import com.threerings.miso.tile.TileManager;
/**
* The <code>IsoSceneView</code> provides an isometric view of a
* particular scene.
*/
public class IsoSceneView implements EditableSceneView
{
/**
* Construct an <code>IsoSceneView</code> object.
*
* @param tilemgr the tile manager.
* @param spritemgr the sprite manager.
* @param model the data model.
*/
public IsoSceneView (TileManager tilemgr, SpriteManager spritemgr,
IsoSceneModel model)
{
_tilemgr = tilemgr;
_spritemgr = spritemgr;
setModel(model);
// initialize the highlighted objects
_htile = new Point(-1, -1);
_hfull = new Point(-1, -1);
// get the font used to render tile coordinates
_font = new Font("Arial", Font.PLAIN, 7);
// create the array used to mark dirty tiles
_dirty = new boolean[Scene.TILE_WIDTH][Scene.TILE_HEIGHT];
// create the list of dirty rectangles
_dirtyRects = new ArrayList();
clearDirtyRegions();
}
/**
* Paint the scene view and any highlighted tiles to the given
* graphics context.
*
* @param g the graphics context.
*/
public void paint (Graphics g)
{
if (_scene == null) return;
Graphics2D gfx = (Graphics2D)g;
// clip the drawing region to our desired bounds since we
// currently draw tiles willy-nilly in undesirable areas.
Shape oldclip = gfx.getClip();
gfx.setClip(0, 0, _model.bounds.width, _model.bounds.height);
if (_numDirty == 0) {
// render the full scene
renderScene(gfx);
} else {
// render only dirty tiles
renderSceneInvalid(gfx);
// draw frames of dirty tiles and rectangles
//drawDirtyRegions(gfx);
// clear out the dirty tiles and rectangles
clearDirtyRegions();
}
// draw an outline around the highlighted tile
paintHighlightedTile(gfx, _htile.x, _htile.y);
// draw an outline around the highlighted full coordinate
paintHighlightedFull(gfx, _hfull.x, _hfull.y);
// draw lines illustrating tracking of the mouse position
//paintMouseLines(gfx);
// restore the original clipping region
gfx.setClip(oldclip);
}
protected void clearDirtyRegions ()
{
_dirtyRects.clear();
_numDirty = 0;
for (int xx = 0; xx < Scene.TILE_WIDTH; xx++) {
for (int yy = 0; yy < Scene.TILE_HEIGHT; yy++) {
_dirty[xx][yy] = false;
}
}
}
protected void drawDirtyRegions (Graphics2D gfx)
{
// draw the dirty tiles
gfx.setColor(Color.cyan);
for (int xx = 0; xx < Scene.TILE_WIDTH; xx++) {
for (int yy = 0; yy < Scene.TILE_HEIGHT; yy++) {
if (_dirty[xx][yy]) {
gfx.draw(IsoUtil.getTilePolygon(_model, xx, yy));
}
}
}
// draw the dirty rectangles
gfx.setColor(Color.red);
int size = _dirtyRects.size();
for (int ii = 0; ii < size; ii++) {
Rectangle rect = (Rectangle)_dirtyRects.get(ii);
gfx.draw(rect);
}
}
/**
* Render the scene to the given graphics context.
*
* @param gfx the graphics context.
*/
protected void renderSceneInvalid (Graphics2D gfx)
{
int numDrawn = 0;
for (int yy = 0; yy < Scene.TILE_HEIGHT; yy++) {
for (int xx = 0; xx < Scene.TILE_WIDTH; xx++) {
// skip this tile if it's not marked dirty
if (!_dirty[xx][yy]) continue;
// get the tile's screen position
Polygon poly = IsoUtil.getTilePolygon(_model, xx, yy);
// draw all layers at this tile position
for (int kk = 0; kk < Scene.NUM_LAYERS; kk++) {
// get the tile at these coordinates and layer
Tile tile = _scene.tiles[xx][yy][kk];
if (tile == null) continue;
// offset the image y-position by the tile-specific height
int ypos = poly.ypoints[0] - _model.tilehhei -
(tile.height - _model.tilehei);
// draw the tile image
gfx.drawImage(tile.img, poly.xpoints[0], ypos, null);
}
// draw all sprites residing in the current tile
_spritemgr.renderSprites(gfx, poly);
// bail early if we know we've drawn all dirty tiles
if (++numDrawn == _numDirty) break;
}
}
}
/**
* Render the scene to the given graphics context.
*
* @param gfx the graphics context.
*/
protected void renderScene (Graphics2D gfx)
{
int mx = 1;
int my = 0;
int screenY = _model.origin.y;
for (int ii = 0; ii < _model.tilerows; ii++) {
// determine starting tile coordinates
int tx = (ii < Scene.TILE_HEIGHT) ? 0 : mx++;
int ty = my;
// determine number of tiles in this row
int length = (ty - tx) + 1;
// determine starting screen x-position
int screenX = _model.origin.x - ((length) * _model.tilehwid);
for (int jj = 0; jj < length; jj++) {
for (int kk = 0; kk < Scene.NUM_LAYERS; kk++) {
// grab the tile we're rendering
Tile tile = _scene.tiles[tx][ty][kk];
if (tile == null) continue;
// determine screen y-position, accounting for
// tile image height
int ypos = screenY - (tile.height - _model.tilehei);
// draw the tile image at the appropriate screen position
gfx.drawImage(tile.img, screenX, ypos, null);
// draw all sprites residing in the current tile
// TODO: simplify other tile positioning here to use poly
_spritemgr.renderSprites(
gfx, IsoUtil.getTilePolygon(_model, tx, ty));
}
// draw tile coordinates in each tile
if (_model.showCoords) {
paintCoords(gfx, tx, ty, screenX, screenY);
}
// each tile is one tile-width to the right of the previous
screenX += _model.tilewid;
// advance tile x and decrement tile y as we move to
// the right drawing the row
tx++;
ty--;
}
// each row is a half-tile-height away from the previous row
screenY += _model.tilehhei;
// advance starting y-axis coordinate unless we've hit bottom
if ((++my) > Scene.TILE_HEIGHT - 1) my = Scene.TILE_HEIGHT - 1;
}
}
/**
* Paint lines showing the most recently calculated x- and y-axis
* mouse position tracking lines, and the mouse position itself.
*
* @param gfx the graphics context.
*/
protected void paintMouseLines (Graphics2D gfx)
{
Point[] lx = _model.lineX;
// draw the baseline x-axis line
gfx.setColor(Color.red);
gfx.drawLine(lx[0].x, lx[0].y, lx[1].x, lx[1].y);
/*
// draw line from last mouse pos to baseline
gfx.setColor(Color.yellow);
gfx.drawLine(ly[0].x, ly[0].y, ly[1].x, ly[1].y);
// draw the most recent mouse cursor position
gfx.setColor(Color.green);
gfx.fillRect(ly[0].x, ly[0].y, 2, 2);
gfx.setColor(Color.red);
gfx.drawRect(ly[0].x - 1, ly[0].y - 1, 3, 3);
*/
}
/**
* Paint the tile coordinate numbers in tile (x, y) whose top-left
* corner is at screen pixel coordinates (sx, sy).
*
* @param gfx the graphics context.
* @param x the tile x-position coordinate.
* @param y the tile y-position coordinate.
* @param sx the screen x-position pixel coordinate.
* @param sy the screen y-position pixel coordinate.
*/
protected void paintCoords (Graphics2D gfx, int x, int y, int sx, int sy)
{
FontMetrics fm = gfx.getFontMetrics(_font);
gfx.setFont(_font);
gfx.setColor(Color.white);
int cx = _model.tilehwid, cy = _model.tilehhei;
int fhei = fm.getAscent();
// draw x-coordinate
String str = "" + x;
gfx.drawString(str, sx + cx - (fm.stringWidth(str)/2), sy + cy);
// draw y-coordinate
str = "" + y;
gfx.drawString(str, sx + cx - (fm.stringWidth(str)/2), sy + cy + fhei);
}
/**
* Paint a highlight around the specified tile.
*
* @param gfx the graphics context.
* @param x the tile x-position coordinate.
* @param y the tile y-position coordinate.
*/
protected void paintHighlightedTile (Graphics2D gfx, int x, int y)
{
if (x == -1 || y == -1) return;
// set the desired stroke and color
Stroke ostroke = gfx.getStroke();
gfx.setStroke(HLT_STROKE);
gfx.setColor(HLT_COLOR);
// draw the tile outline
gfx.draw(IsoUtil.getTilePolygon(_model, x, y));
// restore the original stroke
gfx.setStroke(ostroke);
}
/**
* Paint a highlight around the specified full coordinate.
*
* @param gfx the graphics context.
* @param x the full x-position coordinate.
* @param y the full y-position coordinate.
*/
protected void paintHighlightedFull (Graphics2D gfx, int x, int y)
{
if (x == -1 || y == -1) return;
Point spos = new Point();
IsoUtil.fullToScreen(_model, x, y, spos);
gfx.setColor(Color.red);
gfx.draw(new Ellipse2D.Float(spos.x, spos.y, 3, 3));
}
public void setHighlightedTile (int sx, int sy)
{
IsoUtil.screenToTile(_model, sx, sy, _htile);
}
public void setHighlightedFull (int x, int y)
{
IsoUtil.screenToFull(_model, x, y, _hfull);
}
/**
* Invalidate a list of rectangles in the view for later repainting.
*
* @param rects the list of Rectangle objects.
*/
public void invalidateRects (ArrayList rects)
{
int size = rects.size();
for (int ii = 0; ii < size; ii++) {
Rectangle r = (Rectangle)rects.get(ii);
// dirty the tiles impacted by this rectangle
invalidateScreenRect(r.x, r.y, r.width, r.height);
// save the rectangle for potential display later
_dirtyRects.add(r);
}
}
/**
* Invalidate the specified rectangle in screen pixel coordinates
* in the view.
*
* @param x the rectangle x-position.
* @param y the rectangle y-position.
* @param width the rectangle width.
* @param height the rectangle height.
*/
public void invalidateScreenRect (int x, int y, int width, int height)
{
// TODO: fix boundary conditions, store contiguous dirty tile
// row segments rather than individual tiles, use specialized
// data structure rather than allocating int[] every time.
// note that corner tiles may be included unnecessarily, but
// checking to determine whether they're actually needed
// complicates the code with likely-insufficient benefit
// determine the top-left tile impacted by this rect
Point tpos = new Point();
IsoUtil.screenToTile(_model, x, y, tpos);
// determine screen coordinates for top-left tile
Point topleft = new Point();
IsoUtil.tileToScreen(_model, tpos.x, tpos.y, topleft);
// determine number of horizontal and vertical tiles for rect
int numh = (int)Math.ceil((float)width / (float)_model.tilewid);
int numv = (int)Math.ceil((float)height / (float)_model.tilehhei);
// set up iterating variables
int tx = tpos.x, ty = tpos.y, mx = tpos.x, my = tpos.y;;
// set the starting screen y-position
int screenY = topleft.y;
// add top row if rect may overlap
if (y < (screenY + _model.tilehhei)) {
ty--;
for (int ii = 0; ii < numh; ii++) {
if (!_dirty[tx][ty]) _numDirty++;
_dirty[tx++][ty--] = true;
}
}
// add rows to the bottom if rect may overlap
int ypos = screenY + (numv * _model.tilehhei);
if ((y + height) > ypos) {
numv += ((y + height) > (ypos + _model.tilehhei)) ? 2 : 1;
}
// add dirty tiles from each affected row
boolean isodd = false;
for (int ii = 0; ii < numv; ii++) {
// set up iterating variables for this row
tx = mx;
ty = my;
int length = numh;
// set the starting screen x-position
int screenX = topleft.x;
if (isodd) {
screenX -= _model.tilehwid;
}
// skip leftmost tile if rect doesn't overlap
if (x > screenX + _model.tilewid) {
tx++;
ty--;
screenX += _model.tilewid;
}
// add to the right edge if rect may overlap
if (x + width > (screenX + (length * _model.tilewid))) {
length++;
}
// add all tiles in the row to the dirty set
for (int jj = 0; jj < length; jj++) {
if (!_dirty[tx][ty]) _numDirty++;
_dirty[tx++][ty--] = true;
}
// step along the x- or y-axis appropriately
if (isodd) {
mx++;
} else {
my++;
}
// increment the screen y-position
screenY += _model.tilehhei;
// toggle whether we're drawing an odd-numbered row
isodd = !isodd;
}
}
public void setScene (Scene scene)
{
_scene = scene;
}
public boolean getShowCoordinates ()
{
return _model.showCoords;
}
public void setShowCoordinates (boolean show)
{
_model.showCoords = show;
}
public void setTile (int x, int y, int lnum, Tile tile)
{
Point tpos = new Point();
IsoUtil.screenToTile(_model, x, y, tpos);
_scene.tiles[tpos.x][tpos.y][lnum] = tile;
}
public void deleteTile (int x, int y, int lnum)
{
Point tpos = new Point();
IsoUtil.screenToTile(_model, x, y, tpos);
Tile tile = (lnum == 0) ? _scene.getDefaultTile() : null;
_scene.tiles[tpos.x][tpos.y][lnum] = tile;
}
public void setModel (IsoSceneModel model)
{
_model = model;
_model.precalculate();
}
public Path getPath (Sprite sprite, int x, int y)
{
// make sure the destination point is within our bounds
if (x < 0 || x >= _model.bounds.width ||
y < 0 || y >= _model.bounds.height) {
return null;
}
// constrain destination pixels to fine coordinates
Point fpos = new Point();
IsoUtil.screenToFull(_model, x, y, fpos);
Point spos = new Point();
IsoUtil.fullToScreen(_model, fpos.x, fpos.y, spos);
// create path from current loc to destination
Path path = new Path(sprite.x, sprite.y);
int dir = IsoUtil.getDirection(_model, sprite.x, sprite.y, x, y);
path.addNode(spos.x, spos.y, dir);
return path;
}
/** The color to draw the highlighted tile. */
protected static final Color HLT_COLOR = Color.green;
/** The stroke object used to draw the highlighted tile. */
protected static final Stroke HLT_STROKE = new BasicStroke(3);
/** The currently highlighted tile. */
protected Point _htile;
/** The currently highlighted full coordinate. */
protected Point _hfull;
/** The font to draw tile coordinates. */
protected Font _font;
/** The dirty tiles that need to be re-painted. */
protected boolean _dirty[][];
/** The number of dirty tiles. */
protected int _numDirty;
/** The dirty rectangles that need to be re-painted. */
protected ArrayList _dirtyRects;
/** The scene model data. */
protected IsoSceneModel _model;
/** The scene object to be displayed. */
protected Scene _scene;
/** The sprite manager. */
protected SpriteManager _spritemgr;
/** The tile manager. */
protected TileManager _tilemgr;
}