// // Nenya library - tools for developing networked games // Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved // https://github.com/threerings/nenya // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.threerings.miso.client; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import com.samskivert.util.StringUtil; import com.samskivert.swing.RuntimeAdjust; import com.threerings.media.tile.NoSuchTileSetException; import com.threerings.media.tile.ObjectTile; import com.threerings.media.tile.TileManager; import com.threerings.media.tile.TileUtil; import com.threerings.media.tile.TileSet.Colorizer; import com.threerings.miso.MisoPrefs; import com.threerings.miso.data.ObjectInfo; import com.threerings.miso.util.MisoSceneMetrics; import com.threerings.miso.util.MisoUtil; import static com.threerings.miso.Log.log; /** * Contains resolved information on an object in a scene. */ public class SceneObject { /** The object's info record. */ public ObjectInfo info; /** The object tile used to display this object. */ public ObjectTile tile; /** The screen coordinate bounds of our object tile given its position * in the scene. */ public Rectangle bounds; /** * Creates a scene object for display by the specified panel. The appropriate object tile is * resolved and the object's in-situ bounds are computed. */ public SceneObject (MisoScenePanel panel, ObjectInfo info) { this(panel.getSceneMetrics(), panel.getTileManager(), panel.getColorizer(info), info); } public SceneObject (MisoSceneMetrics metrics, TileManager mgr, Colorizer colorizer, ObjectInfo info) { this.info = info; // resolve our object tile refreshObjectTile(metrics, mgr, colorizer); } /** * Creates a scene object for display by the specified panel. */ public SceneObject (MisoScenePanel panel, ObjectInfo info, ObjectTile tile) { this(panel.getSceneMetrics(), info, tile); } /** * Creates a scene object for display according to the supplied metrics. */ public SceneObject ( MisoSceneMetrics metrics, ObjectInfo info, ObjectTile tile) { this.info = info; this.tile = tile; computeInfo(metrics); } /** * Used to flag overlapping scene objects that have no resolving * object priorities. */ public void setWarning (boolean warning) { _warning = warning; } /** * Requests that this scene object render itself. */ public void paint (Graphics2D gfx) { // if we're rendering footprints, paint that boolean footpaint = _fprintDebug.getValue(); if (footpaint) { gfx.setColor(Color.black); gfx.draw(_footprint); if (_hideObjects.getValue()) { // We're in footprints but no objects mode, so let's also fill it in so we can // see things better/tell if we have overlapping ones Composite ocomp = gfx.getComposite(); gfx.setComposite(ALPHA_WARN); gfx.fill(_footprint); gfx.setComposite(ocomp); } } if (_hideObjects.getValue()) { return; } // if we have a warning, render an alpha'd red rectangle over our bounds if (_warning) { Composite ocomp = gfx.getComposite(); gfx.setComposite(ALPHA_WARN); gfx.fill(bounds); gfx.setComposite(ocomp); } // paint our tile tile.paint(gfx, bounds.x, bounds.y); // and possibly paint the object's spot if (footpaint && _sspot != null) { // translate the origin to center on the portal gfx.translate(_sspot.x, _sspot.y); // rotate to reflect the spot orientation double rot = (Math.PI / 4.0f) * tile.getSpotOrient(); gfx.rotate(rot); // draw the spot triangle gfx.setColor(Color.green); gfx.fill(_spotTri); // outline the triangle in black gfx.setColor(Color.black); gfx.draw(_spotTri); // restore the original transform gfx.rotate(-rot); gfx.translate(-_sspot.x, -_sspot.y); } } /** * Returns the location associated with this object's "spot" in fine * coordinates or null if it has no spot. */ public Point getObjectSpot () { return _fspot; } /** * Returns the location associated with this object's "spot" in screen * coordinates or null if it has no spot. */ public Point getObjectScreenSpot () { return _sspot; } /** * Returns true if this object's footprint overlaps that of the * specified other object. */ public boolean objectFootprintOverlaps (SceneObject so) { return _frect.intersects(so._frect); } /** * Returns true if this object's footprint overlaps the supplied tile * coordinate rectangle. */ public boolean objectFootprintOverlaps (Rectangle rect) { return _frect.intersects(rect); } /** * Returns a polygon bounding all footprint tiles of this scene * object. * * @return the bounding polygon. */ public Polygon getObjectFootprint () { return _footprint; } /** * Returns the render priority of this scene object. */ public int getPriority () { // if we have no overridden priority, return our object tile's // default priority return (info.priority == 0 && tile != null) ? tile.getPriority() : info.priority; } /** * Overrides the render priority of this object. */ public void setPriority (byte priority) { info.priority = (byte)Math.max( Byte.MIN_VALUE, Math.min(Byte.MAX_VALUE, priority)); } /** * Informs this scene object that the mouse is now hovering over it. * Custom objects may wish to adjust some internal state and return * true from this method indicating that they should be repainted. */ public boolean setHovered (boolean hovered) { return false; } /** * Updates this object's origin tile coordinate. Its bounds and other * cached screen coordinate information are updated. */ public void relocateObject (MisoSceneMetrics metrics, int tx, int ty) { // Log.info("Relocating object " + this + " to " + // StringUtil.coordsToString(tx, ty)); info.x = tx; info.y = ty; computeInfo(metrics); } /** * Reloads and recolorizes our object tile. It is not intended for the actual object tile used * by a scene object to change in its lifetime, only attributes of that object like its * colorizations. So don't do anything crazy like change our {@link ObjectInfo}'s * <code>tileId</code> and call this method or things might break. */ public void refreshObjectTile (MisoScenePanel panel) { refreshObjectTile(panel.getSceneMetrics(), panel.getTileManager(), panel.getColorizer(info)); } /** * Reloads and recolorizes our object tile. It is not intended for the actual object tile used * by a scene object to change in its lifetime, only attributes of that object like its * colorizations. So don't do anything crazy like change our {@link ObjectInfo}'s * <code>tileId</code> and call this method or things might break. */ public void refreshObjectTile (MisoSceneMetrics metrics, TileManager mgr, Colorizer colorizer) { int tsid = TileUtil.getTileSetId(info.tileId); int tidx = TileUtil.getTileIndex(info.tileId); try { tile = (ObjectTile)mgr.getTile(tsid, tidx, colorizer); computeInfo(metrics); } catch (NoSuchTileSetException te) { log.warning("Scene contains non-existent object tileset [info=" + info + "]."); } } /** * Computes our screen bounds, tile footprint and other useful cached metrics. */ protected void computeInfo (MisoSceneMetrics metrics) { // start with the screen coordinates of our origin tile Point tpos = MisoUtil.tileToScreen( metrics, info.x, info.y, new Point()); // if the tile has an origin coordinate, use that, otherwise // compute it from the tile footprint int tox = tile.getOriginX(), toy = tile.getOriginY(); if (tox == Integer.MIN_VALUE) { tox = tile.getBaseWidth() * metrics.tilehwid; } if (toy == Integer.MIN_VALUE) { toy = tile.getHeight(); } bounds = new Rectangle(tpos.x + metrics.tilehwid - tox, tpos.y + metrics.tilehei - toy, tile.getWidth(), tile.getHeight()); // compute our object footprint as well _frect = new Rectangle(info.x - tile.getBaseWidth() + 1, info.y - tile.getBaseHeight() + 1, tile.getBaseWidth(), tile.getBaseHeight()); _footprint = MisoUtil.getFootprintPolygon( metrics, _frect.x, _frect.y, _frect.width, _frect.height); // compute our object spot if we've got one if (tile.hasSpot()) { _fspot = MisoUtil.tilePlusFineToFull( metrics, info.x, info.y, tile.getSpotX(), tile.getSpotY(), new Point()); _sspot = MisoUtil.fullToScreen( metrics, _fspot.x, _fspot.y, new Point()); } // Log.info("Computed object metrics " + // "[tpos=" + StringUtil.coordsToString(tx, ty) + // ", info=" + info + // ", sbounds=" + StringUtil.toString(bounds) + "]."); } @Override public String toString () { return info + "[" + StringUtil.toString(bounds) + "]"; } /** Our object as a tile coordinate rectangle. */ protected Rectangle _frect; /** Our object footprint as a polygon. */ protected Polygon _footprint; /** The full-coordinates of our object spot; or null if we have none. */ protected Point _fspot; /** The screen-coordinates of our object spot; or null if we have none. */ protected Point _sspot; /** Used to mark objects with errors. */ protected boolean _warning; /** A debug hook that toggles rendering of objects. */ protected static RuntimeAdjust.BooleanAdjust _hideObjects = new RuntimeAdjust.BooleanAdjust( "Toggles rendering of objects in the scene view.", "narya.miso.hide_objects", MisoPrefs.config, false); /** A debug hook that toggles rendering of object footprints. */ protected static RuntimeAdjust.BooleanAdjust _fprintDebug = new RuntimeAdjust.BooleanAdjust( "Toggles rendering of object footprints in the scene view.", "narya.miso.iso_fprint_debug_render", MisoPrefs.config, false); /** The triangle used to render an object's spot. */ protected static Polygon _spotTri; static { _spotTri = new Polygon(); _spotTri.addPoint(-3, -3); _spotTri.addPoint(3, -3); _spotTri.addPoint(0, 3); } /** The alpha used to fill our bounds for warning purposes. */ protected static final Composite ALPHA_WARN = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); }