first commit
This commit is contained in:
@@ -0,0 +1,645 @@
|
||||
package edu.gatech.cs2335.lemmings.graphics;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
//import java.net.URL;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* The tileset class simplifies loading bitmap files that contain
|
||||
* several tiles. It is a fairly flexible class, but it does impose
|
||||
* certain restrictions on the way the file is organized. First of
|
||||
* all, there needs to be a set of control pixels in the upper-right
|
||||
* corner of the image. The control pixels start at the coordinate
|
||||
* (width-1, 0) and go down from there in the following order:
|
||||
* transparency pixel, empty-region pixel, outside anchor pixel,
|
||||
* used-region pixel, inside anchor pixel; five in all. The
|
||||
* transparency pixel is used for color-keying, and all the pixels of
|
||||
* that color in the image will be made transparent. The outside and
|
||||
* inside anchor pixels do the same thing currently, and are used to
|
||||
* denote the image anchor. When the tile is blitted using the
|
||||
* putTile method, the anchor has the coordinates passed in to the
|
||||
* method, and the rest of the image is drawn relatively to it. In
|
||||
* other words, suppose we blit to rectangular tiles using the same
|
||||
* target coordinates. One of the tiles has its anchor in the
|
||||
* top-left corner, and the other - in the center. Then the center of
|
||||
* the latter tile will coincide with the top-left corner of the
|
||||
* former tile. The empty-region and used-region pixels are used to
|
||||
* denote where the useful areas of the tile are, and which ones can
|
||||
* be discarded. The reason for that is that the tiles in the tileset
|
||||
* file have to be arranged in a table. In other words, all tiles in
|
||||
* one column have to be the same width, and all the tiles in one row
|
||||
* have to be the same height. That is not to say, however, that the
|
||||
* width and the height have to be the equal, or that the tiles in
|
||||
* different rows have to have the same height, and in different
|
||||
* columns - the same width. Suppose a game has only two tiles. We
|
||||
* can arrange them in a column. One of the tiles is square, and the
|
||||
* other - a very thin rectangle. Then, we could mark the whole first
|
||||
* tile as used, and for the second tile - only the space that is
|
||||
* actually used up. Since we arranged the tiles in a column, the
|
||||
* widths of the tiles have to be the same. However, we can mark the
|
||||
* unused areas of the thin tile with the unused-area pixel color,
|
||||
* and they will be discarded when blitting.
|
||||
*
|
||||
* <PRE>
|
||||
* Revision History:
|
||||
* v1.0 (Mar. 11, 2004) - Created the TileSet class
|
||||
* </PRE>
|
||||
*
|
||||
* @author <A HREF="mailto:gtg308i@mail.gatech.edu">Vladimir Urazov</A>
|
||||
* @version Version 1.0, Mar. 11, 2004
|
||||
*/
|
||||
public class TileSet {
|
||||
|
||||
/**
|
||||
* Show debug output?
|
||||
*/
|
||||
protected static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Show lots of debug output?
|
||||
*/
|
||||
protected static final boolean VERBOSE = false;
|
||||
|
||||
/**
|
||||
* The list of informations pertaining to the frames in this set.
|
||||
*/
|
||||
private FrameInformation[] frameList;
|
||||
|
||||
/**
|
||||
* The name of the file we want to load the image from, in case it
|
||||
* needs to be reloaded somehow.
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* The image, where the whole tileset is stored.
|
||||
*/
|
||||
private BufferedImage framesetImage;
|
||||
|
||||
/**
|
||||
* Creates a new <code>TileSet</code> instance.
|
||||
*/
|
||||
public TileSet() {
|
||||
frameList = null;
|
||||
fileName = null;
|
||||
framesetImage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the tileset from a file. Returns true upon success or false
|
||||
* upon failure.
|
||||
*
|
||||
* @param name a <code>String</code> value
|
||||
* @return a <code>boolean</code> value
|
||||
*/
|
||||
public boolean loadTileset(String name) {
|
||||
//Dimensions of the image
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
//Number of tiles horizontally and vertically
|
||||
// int xCount = 0;
|
||||
// int yCount = 0;
|
||||
//Lists of coordinates for the tile bounds
|
||||
List horizontalTileBounds = new Vector();
|
||||
List verticalTileBounds = new Vector();
|
||||
//Color control
|
||||
Color transparentColor = null;
|
||||
Color boundColor = null;
|
||||
Color anchorColor = null;
|
||||
Color insideColor = null;
|
||||
Color insideAnchorColor = null;
|
||||
Color testColor = null;
|
||||
//Image info
|
||||
Raster tilesetRaster = null;
|
||||
ColorSpace tilesetSpace = null;
|
||||
//Miscellaneous temporaries
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
int currentTile = 0;
|
||||
// boolean found = false;
|
||||
//First, clean up if we already have something loaded:
|
||||
if (!unloadTileset()) {
|
||||
//Could not clean up...
|
||||
return false;
|
||||
}
|
||||
//Save the name of the file:
|
||||
fileName = name;
|
||||
//Load the image from the file:
|
||||
framesetImage = ImageLoader.getInstance().loadLocalImage(name);
|
||||
if (framesetImage == null) {
|
||||
System.err.println("TileSet.loadTileset - could not load image.");
|
||||
System.err.flush();
|
||||
return false;
|
||||
}
|
||||
//Get image info:
|
||||
tilesetRaster = framesetImage.getRaster();
|
||||
tilesetSpace = framesetImage.getColorModel().getColorSpace();
|
||||
//Get image dimensions:
|
||||
width = tilesetRaster.getWidth();
|
||||
height = tilesetRaster.getHeight();
|
||||
//Define color control variables:
|
||||
transparentColor = ImageUtilities.getInstance()
|
||||
.getPixel(framesetImage, width - 1, 0);
|
||||
boundColor = ImageUtilities.getInstance()
|
||||
.getPixel(framesetImage, width - 1, 1);
|
||||
anchorColor = ImageUtilities.getInstance()
|
||||
.getPixel(framesetImage, width - 1, 2);
|
||||
insideColor = ImageUtilities.getInstance()
|
||||
.getPixel(framesetImage, width - 1, 3);
|
||||
insideAnchorColor = ImageUtilities.getInstance()
|
||||
.getPixel(framesetImage, width - 1, 4);
|
||||
testColor = Color.black;
|
||||
//Find the coordinates of the vertical tile bounds:
|
||||
for (x = 0; x < width; x++) {
|
||||
testColor = ImageUtilities.getInstance().getPixel(framesetImage, x, 0);
|
||||
|
||||
if (testColor.equals(transparentColor)) {
|
||||
//Add the coordinate to the list:
|
||||
if (VERBOSE) {
|
||||
System.out.println("TileSet: Adding x coordinate - " + x);
|
||||
System.out.flush();
|
||||
}
|
||||
verticalTileBounds.add(new Integer(x));
|
||||
}
|
||||
}
|
||||
//Find the coordinates of the horizontal tile bounds:
|
||||
for (y = 0; y < height; y++) {
|
||||
testColor = ImageUtilities.getInstance().getPixel(framesetImage, 0, y);
|
||||
if (testColor.equals(transparentColor)) {
|
||||
//Add the coordinate to the list:
|
||||
if (VERBOSE) {
|
||||
System.out.println("TileSet: Adding y coordinate - " + y);
|
||||
System.out.flush();
|
||||
}
|
||||
horizontalTileBounds.add(new Integer(y));
|
||||
}
|
||||
}
|
||||
//Allocate the list of frame infos:
|
||||
frameList = new FrameInformation[(verticalTileBounds.size() - 1)
|
||||
* (horizontalTileBounds.size() - 1)];
|
||||
//Scan the tiles in:
|
||||
for (row = 0; row < horizontalTileBounds.size() - 1; row++) {
|
||||
for (col = 0; col < verticalTileBounds.size() - 1; col++) {
|
||||
currentTile = col + row * (verticalTileBounds.size() - 1);
|
||||
if (DEBUG) {
|
||||
System.out.println("TileSet: Processing tile (" + col + ", "
|
||||
+ row + ") - #" + currentTile);
|
||||
System.out.flush();
|
||||
}
|
||||
frameList[currentTile] = new FrameInformation();
|
||||
//Determine the anchor point:
|
||||
frameList[currentTile].getAnchor().x
|
||||
= findFirstOccurrence(((Integer) verticalTileBounds.get(col))
|
||||
.intValue(),
|
||||
((Integer) verticalTileBounds.get(col + 1))
|
||||
.intValue(),
|
||||
((Integer) horizontalTileBounds.get(row))
|
||||
.intValue(),
|
||||
anchorColor, insideAnchorColor, false);
|
||||
|
||||
frameList[currentTile].getAnchor().y
|
||||
= findFirstOccurrence(((Integer) horizontalTileBounds.get(row))
|
||||
.intValue(),
|
||||
((Integer) horizontalTileBounds.get(row + 1))
|
||||
.intValue(),
|
||||
((Integer) verticalTileBounds.get(col))
|
||||
.intValue(),
|
||||
anchorColor, insideAnchorColor, true);
|
||||
//Find the first tile pixel:
|
||||
frameList[currentTile].getSource().x
|
||||
= findFirstOccurrence(((Integer) verticalTileBounds.get(col))
|
||||
.intValue(),
|
||||
((Integer) verticalTileBounds.get(col + 1))
|
||||
.intValue(),
|
||||
((Integer) horizontalTileBounds.get(row))
|
||||
.intValue(),
|
||||
insideColor, insideAnchorColor, false);
|
||||
|
||||
frameList[currentTile].getSource().y
|
||||
= findFirstOccurrence(((Integer) horizontalTileBounds.get(row))
|
||||
.intValue(),
|
||||
((Integer) horizontalTileBounds.get(row + 1))
|
||||
.intValue(),
|
||||
((Integer) verticalTileBounds.get(col))
|
||||
.intValue(),
|
||||
insideColor, insideAnchorColor, true);
|
||||
if (frameList[currentTile].getSource().x == 0) {
|
||||
//Could not find. Use default:
|
||||
frameList[currentTile].getSource().x
|
||||
= ((Integer) verticalTileBounds.get(col)).intValue() + 1;
|
||||
}
|
||||
if (frameList[currentTile].getSource().y == 0) {
|
||||
//Could not find. Use default:
|
||||
frameList[currentTile].getSource().y
|
||||
= ((Integer) horizontalTileBounds.get(row)).intValue() + 1;
|
||||
}
|
||||
|
||||
//Find the last tile pixel:
|
||||
frameList[currentTile].getSource().width
|
||||
= findLastOccurrence(((Integer) verticalTileBounds.get(col))
|
||||
.intValue(),
|
||||
((Integer) verticalTileBounds.get(col + 1))
|
||||
.intValue(),
|
||||
((Integer) horizontalTileBounds.get(row))
|
||||
.intValue(),
|
||||
insideColor, insideAnchorColor, false)
|
||||
- frameList[currentTile].getSource().x + 1;
|
||||
|
||||
frameList[currentTile].getSource().height
|
||||
= findLastOccurrence(((Integer) horizontalTileBounds.get(row))
|
||||
.intValue(),
|
||||
((Integer) horizontalTileBounds.get(row + 1))
|
||||
.intValue(),
|
||||
((Integer) verticalTileBounds.get(col))
|
||||
.intValue(),
|
||||
insideColor, insideAnchorColor, true)
|
||||
- frameList[currentTile].getSource().y + 1;
|
||||
|
||||
if (frameList[currentTile].getSource().width <= 0) {
|
||||
//Could not find. Use default:
|
||||
frameList[currentTile].getSource().width
|
||||
= ((Integer) verticalTileBounds.get(col + 1)).intValue()
|
||||
- frameList[currentTile].getSource().x;
|
||||
}
|
||||
if (frameList[currentTile].getSource().height <= 0) {
|
||||
//Could not find. Use default:
|
||||
frameList[currentTile].getSource().height
|
||||
= ((Integer) horizontalTileBounds.get(row + 1)).intValue()
|
||||
- frameList[currentTile].getSource().y;
|
||||
}
|
||||
|
||||
//Calculate tile extent:
|
||||
frameList[currentTile]
|
||||
.setExtent(new Rectangle(frameList[currentTile].getSource()));
|
||||
frameList[currentTile].getExtent()
|
||||
.translate(-frameList[currentTile].getAnchor().x,
|
||||
-frameList[currentTile].getAnchor().y);
|
||||
}
|
||||
}
|
||||
|
||||
specialLoad();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe <code>specialLoad</code> method here.
|
||||
*
|
||||
* @return a <code>boolean</code> value
|
||||
*/
|
||||
protected boolean specialLoad() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe <code>getFrameList</code> method here.
|
||||
*
|
||||
* @return a <code>FrameInformation[]</code> value
|
||||
*/
|
||||
protected FrameInformation[] getFrameList() {
|
||||
return frameList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe <code>getFramesetImage</code> method here.
|
||||
*
|
||||
* @return a <code>BufferedImage</code> value
|
||||
*/
|
||||
protected BufferedImage getFramesetImage() {
|
||||
return framesetImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the coordinate of the first occurrence of one of the test colors.
|
||||
*
|
||||
* @param lowBound an <code>int</code> value
|
||||
* @param highBound an <code>int</code> value
|
||||
* @param coordinate an <code>int</code> value
|
||||
* @param c1 a <code>Color</code> value
|
||||
* @param c2 a <code>Color</code> value
|
||||
* @param vertical a <code>boolean</code> value
|
||||
* @return an <code>int</code> value
|
||||
*/
|
||||
protected int findFirstOccurrence(int lowBound, int highBound,
|
||||
int coordinate,
|
||||
Color c1, Color c2, boolean vertical) {
|
||||
Color testColor = null;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
for (int i = lowBound + 1; i < highBound; i++) {
|
||||
if (vertical) {
|
||||
x = coordinate;
|
||||
y = i;
|
||||
} else {
|
||||
x = i;
|
||||
y = coordinate;
|
||||
}
|
||||
|
||||
testColor = ImageUtilities.getInstance().getPixel(framesetImage, x, y);
|
||||
if (testColor.equals(c1) || testColor.equals(c2)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the coordinate of the last occurrence of one of the test colors.
|
||||
*
|
||||
* @param lowBound an <code>int</code> value
|
||||
* @param highBound an <code>int</code> value
|
||||
* @param coordinate an <code>int</code> value
|
||||
* @param c1 a <code>Color</code> value
|
||||
* @param c2 a <code>Color</code> value
|
||||
* @param vertical a <code>boolean</code> value
|
||||
* @return an <code>int</code> value
|
||||
*/
|
||||
protected int findLastOccurrence(int lowBound, int highBound,
|
||||
int coordinate,
|
||||
Color c1, Color c2, boolean vertical) {
|
||||
Color testColor = null;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
for (int i = highBound - 1; i > lowBound; i--) {
|
||||
if (vertical) {
|
||||
x = coordinate;
|
||||
y = i;
|
||||
} else {
|
||||
x = i;
|
||||
y = coordinate;
|
||||
}
|
||||
|
||||
testColor = ImageUtilities.getInstance().getPixel(framesetImage, x, y);
|
||||
if (testColor.equals(c1) || testColor.equals(c2)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the surface was lost, reloads it, but does not reparse the file.
|
||||
*
|
||||
* @return a <code>boolean</code> value
|
||||
*/
|
||||
public boolean reloadTileset() {
|
||||
//Load the image from the file:
|
||||
framesetImage = ImageLoader.getInstance().loadLocalImage(getFileName());
|
||||
if (framesetImage == null) {
|
||||
System.err.println("TileSet.reloadTileset - could not load image");
|
||||
System.err.flush();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs all the necessary clean-up operations.
|
||||
*
|
||||
* @return a <code>boolean</code> value
|
||||
*/
|
||||
public boolean unloadTileset() {
|
||||
//Clean up the tile list if necessary:
|
||||
if (frameList != null) {
|
||||
frameList = null;
|
||||
fileName = null;
|
||||
framesetImage = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of tiles in the tileset.
|
||||
* @return an <code>int</code> value
|
||||
*/
|
||||
public int getTileCount() {
|
||||
return frameList.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image on which the tileset resides.
|
||||
* @return a <code>BufferedImage</code> value
|
||||
*/
|
||||
public BufferedImage getImage() {
|
||||
return framesetImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the file containing the tileset in a string.
|
||||
* @return a <code>String</code> value
|
||||
*/
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the largest tile in the tileset.
|
||||
*
|
||||
* @return a <code>Dimension</code> value
|
||||
*/
|
||||
public Dimension getLargestDimension() {
|
||||
Dimension result = new Dimension();
|
||||
|
||||
for (int i = 0; i < frameList.length; i++) {
|
||||
int width = frameList[i].getSource().width;
|
||||
int height = frameList[i].getSource().height;
|
||||
|
||||
if (width > result.width) {
|
||||
result.width = width;
|
||||
}
|
||||
|
||||
if (height > result.height) {
|
||||
result.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the tile with the specified id.
|
||||
*
|
||||
* @param tileNum an <code>int</code> value
|
||||
* @return a <code>Dimension</code> value
|
||||
*/
|
||||
public Dimension getDimension(int tileNum) {
|
||||
Dimension result = new Dimension();
|
||||
|
||||
result.width = frameList[tileNum].getSource().width;
|
||||
result.height = frameList[tileNum].getSource().height;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extent of the tile with the specified id.
|
||||
*
|
||||
* @param tileNum an <code>int</code> value
|
||||
* @return a <code>Rectangle</code> value
|
||||
*/
|
||||
public Rectangle getExtent(int tileNum) {
|
||||
Rectangle result = new Rectangle(frameList[tileNum].getExtent());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the tile with the specified number onto the graphics context
|
||||
* passed in at the specified coordinates. Note that the position of
|
||||
* the tile will depend on its anchor point, which will be put
|
||||
* exactly at the coordinates passed in.
|
||||
*
|
||||
* @param destination the context to which we want to draw the tile.
|
||||
* @param coordinates the coordinates at which we want to draw the tile.
|
||||
* @param tileNum the number of the tile we want to draw. The tiles
|
||||
* will be numbered automatically, from left to right, from top to
|
||||
* bottom, when the image file is parsed.
|
||||
*/
|
||||
public void drawTile(Graphics destination, Point coordinates, int tileNum) {
|
||||
if (DEBUG) {
|
||||
System.out.println("TileSet.drawTile - Drawing tile " + tileNum + " at ("
|
||||
+ coordinates.x + ", " + coordinates.y + ")");
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
frameList[tileNum].getExtent().translate(coordinates.x, coordinates.y);
|
||||
|
||||
//Fetch raster:
|
||||
Rectangle src = frameList[tileNum].getSource();
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("TileSet.drawTile: src rectangle - " + src);
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
Raster data = framesetImage.getData(src);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("TileSet.drawTile: data - " + data);
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
BufferedImage temp = new BufferedImage(data.getWidth(),
|
||||
data.getHeight(),
|
||||
framesetImage.getType());
|
||||
|
||||
temp.setData(data.createRaster(data.getSampleModel(),
|
||||
data.getDataBuffer(), null));
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("TileSet.drawTile: temp - " + temp);
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
//Render tile:
|
||||
destination.drawImage(temp,
|
||||
frameList[tileNum].getExtent().x,
|
||||
frameList[tileNum].getExtent().y,
|
||||
null);
|
||||
frameList[tileNum].getExtent().translate(-coordinates.x, -coordinates.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Information structure for one tile in the tileset.
|
||||
*/
|
||||
protected class FrameInformation {
|
||||
|
||||
/**
|
||||
* This rectangle represents the location of the frame in the
|
||||
* master frame set.
|
||||
*/
|
||||
private Rectangle source;
|
||||
|
||||
/**
|
||||
* The extent of the frame.
|
||||
*/
|
||||
private Rectangle extent;
|
||||
|
||||
/**
|
||||
* The anchor point of the frame. That is, when we say that we
|
||||
* want to render the frame at certain coordinates, the point with
|
||||
* the coordinates stored here, relative to the top-left corner of
|
||||
* the frame, will be drawn at the specified coordinates.
|
||||
*/
|
||||
private Point anchor;
|
||||
|
||||
/**
|
||||
* Creates a new <code>FrameInformation</code>
|
||||
* instance. Initializes all of the data members to contain all
|
||||
* default values (zeros).
|
||||
*/
|
||||
public FrameInformation() {
|
||||
source = new Rectangle();
|
||||
extent = new Rectangle();
|
||||
anchor = new Point();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of source.
|
||||
* @return value of source.
|
||||
*/
|
||||
public Rectangle getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of source.
|
||||
* @param v Value to assign to source.
|
||||
*/
|
||||
public void setSource(Rectangle v) {
|
||||
this.source = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of extent.
|
||||
* @return value of extent.
|
||||
*/
|
||||
public Rectangle getExtent() {
|
||||
return extent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of extent.
|
||||
* @param v Value to assign to extent.
|
||||
*/
|
||||
public void setExtent(Rectangle v) {
|
||||
this.extent = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of anchor.
|
||||
* @return value of anchor.
|
||||
*/
|
||||
public Point getAnchor() {
|
||||
return anchor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of anchor.
|
||||
* @param v Value to assign to anchor.
|
||||
*/
|
||||
public void setAnchor(Point v) {
|
||||
this.anchor = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user