Making collisions that ignore transparency. its fun.
Anyway, so I've been working on an arcanists game (that old game from jagex/funorb) it is something of a magical land-based version of gravitee-wars for any who know that one, they are known as "artillery games"
The big problem is that while I have finally managed to get a working version of it, it is incredibly slow (meaning at max speed it runs at about the same as .5 speed)
here's the code currently (feel free to use this if all you want is accurate collisions, just know that it's slow)
It is a modified version of the classic smooth-mover class which is able to handle collisions based on opaque pixels colliding rather than the rectangular GreenfootImages as a whole intersecting.
import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo) import java.util.List; import java.util.ArrayList; /** * Write a description of class Mover here. * * @author (your name) * @version (a version number or a date) */ public class Mover extends Actor { private Vector movement; private double exactX; private double exactY; /** * get objects visually overlapping this object * @param cls - The type of object to return (null returns all objects) * @returns Objects */ public <E extends Mover> List<E> getCollidingObjects(Class<E> cls){ List<E> objects = getIntersectingObjects(cls); List<E> fObjects = new ArrayList(); //finalObjects list. this will contain any objects properly intersecting with this object. if(objects.size()>0){ int x = getImage().getWidth(); int y = getImage().getHeight(); int d = (int) Math.ceil(Math.sqrt(Math.pow(x,2)+Math.pow(y,2))); double scaleFactor = x>y ? (double) d / (double) y : (double) d / (double) x; // this coupled with the previous 3 lines set up the math for scaling our image to prevent cropping GreenfootImage img = new GreenfootImage((int) Math.ceil(x*scaleFactor), (int) Math.ceil(y*scaleFactor)); //this is the objects image so we can scale it to a large enough size that rotation won't crop it. img.drawImage(getImage(), (img.getWidth()-getImage().getWidth())/2, (img.getHeight()-getImage().getHeight())/2); //puts our image into the center of the enlarged image for rotating img.rotate(getRotation()); //rotating the image to match direction boolean[][] bitmask = new boolean[img.getWidth()][img.getHeight()];//Not sure on the terminology, but "bitmask" appears to be how to refer to a 1/0 version of an image which is what this is, 1 (true) being opaque (or partially transparent), 0 (false) being fully transparent for(int col = 0; col < bitmask.length; col++){ //Cycles through every x value of image for(int row = 0; row < bitmask[0].length; row++){ //Cycles through every y value of image bitmask[col][row] = (img.getColorAt(col, row).getAlpha()>0); //alpha 0 is transparent, this sets the bitmask pixel to the proper transparency t/f state } } for(E a : objects){ //Cycles through every "intersecting" object so we can check if it really is intersecting if(isCollidedWith(a, bitmask)){ fObjects.add(a); } } } return fObjects; } /** * For checking lots of objects, you provide the bitmask for this object so it is only calculated once. returns if this and "atr" are visually colliding or not * @param atr - the actor to check if it is colliding with * @param bitmask - the bitmask of this object * @returns true - is collided with "atr", false - isn't collided with "atr" */ public boolean isCollidedWith(Actor atr, boolean[][] bitmask){ int x2 = atr.getImage().getWidth(); int y2 = atr.getImage().getHeight(); int d2 = (int) Math.ceil(Math.sqrt(Math.pow(x2,2)+Math.pow(y2,2))); double scaleFactor2 = x2>y2 ? (double) d2 / (double) y2 : (double) d2 / (double) x2; // this coupled with the previous 3 lines set up the math for scaling our image to prevent cropping GreenfootImage img2 = new GreenfootImage((int) Math.ceil(x2*scaleFactor2), (int) Math.ceil(y2*scaleFactor2)); //this is the objects image so we can scale it to a large enough size that rotation won't crop it. img2.drawImage(atr.getImage(), (img2.getWidth()-atr.getImage().getWidth())/2, (img2.getHeight()-atr.getImage().getHeight())/2); //puts our image into the center of the enlarged image for rotating img2.rotate(atr.getRotation()); //rotating the image to match direction boolean[][] bitmask2 = new boolean[img2.getWidth()][img2.getHeight()];//new bitmask for the image of colliding object, 1 (true) being opaque (or partially transparent), 0 (false) being fully transparent for(int col = 0; col < bitmask.length && col < img2.getWidth(); col++){ //Cycles through every x value of image for(int row = 0; row < bitmask[0].length && row < img2.getHeight(); row++){ //Cycles through every y value of image bitmask2[col][row] = img2.getColorAt(col, row).getAlpha()>0; //alpha 0 is transparent, this sets the bitmask pixel to the proper transparency t/f state } } int xOffset = 0; //the col of THIS to start with if - it changes start of atr int yOffset = 0; //the row of THIS to start with, if - it changes start of atr xOffset += 0-(getX()-atr.getX()); //position offset yOffset += 0-(getY()-atr.getY()); //position offset xOffset += (int) Math.ceil((double) ((bitmask.length-img2.getWidth())/2.0)); //image offset - goes right 1 pixel for each pixel/2 rounded up yOffset += (int) Math.ceil((double) ((bitmask[0].length-img2.getHeight())/2.0)); //image offset - goes right 1 pixel for each pixel/2 rounded up int cLength = (xOffset>0 ? (bitmask.length-xOffset > bitmask2.length ? bitmask2.length : bitmask.length-xOffset) : (bitmask2.length+xOffset > bitmask.length ? bitmask.length : bitmask2.length+xOffset)); //amount of cols to process int rLength = (yOffset>0 ? (bitmask[0].length-yOffset > bitmask2[0].length ? bitmask2[0].length : bitmask[0].length-yOffset) : (bitmask2[0].length+yOffset > bitmask[0].length ? bitmask[0].length : bitmask2[0].length+yOffset)); //amount of rows to process int startX = xOffset>0 ? xOffset : 0; int startX2 = xOffset>0 ? 0 : 0-xOffset; for(int xChange = 0; xChange < cLength; xChange++){ int startY = yOffset>0 ? yOffset : 0; int startY2 = yOffset>0 ? 0 : 0-yOffset; for(int yChange = 0; yChange < rLength; yChange++){ if(bitmask[xChange+startX][yChange+startY] && bitmask2[xChange+startX2][yChange+startY2]){ return true; } } } return false; } /** * For checking a single object, calculates bitmask for this for you. returns if this and "atr" are visually colliding or not * @param atr - the actor to check if it is colliding with * @returns true - is collided with "atr", false - isn't collided with "atr" */ public boolean isCollidedWith(Actor atr){ int x = getImage().getWidth(); int y = getImage().getHeight(); int d = (int) Math.ceil(Math.sqrt(Math.pow(x,2)+Math.pow(y,2))); double scaleFactor = x>y ? (double) d / (double) y : (double) d / (double) x; // this coupled with the previous 3 lines set up the math for scaling our image to prevent cropping GreenfootImage img = new GreenfootImage((int) Math.ceil(x*scaleFactor), (int) Math.ceil(y*scaleFactor)); //this is the objects image so we can scale it to a large enough size that rotation won't crop it. img.drawImage(getImage(), (img.getWidth()-getImage().getWidth())/2, (img.getHeight()-getImage().getHeight())/2); //puts our image into the center of the enlarged image for rotating img.rotate(getRotation()); //rotating the image to match direction boolean[][] bitmask = new boolean[img.getWidth()][img.getHeight()];//Not sure on the terminology, but "bitmask" appears to be how to refer to a 1/0 version of an image which is what this is, 1 (true) being opaque (or partially transparent), 0 (false) being fully transparent for(int col = 0; col < bitmask.length; col++){ //Cycles through every x value of image for(int row = 0; row < bitmask[0].length; row++){ //Cycles through every y value of image bitmask[col][row] = img.getColorAt(col, row).getAlpha()!=0; //alpha 0 is transparent, this sets the bitmask pixel to the proper transparency t/f state } } int x2 = atr.getImage().getWidth(); int y2 = atr.getImage().getHeight(); int d2 = (int) Math.ceil(Math.sqrt(Math.pow(x2,2)+Math.pow(y2,2))); double scaleFactor2 = x2>y2 ? (double) d2 / (double) y2 : (double) d2 / (double) x2; // this coupled with the previous 3 lines set up the math for scaling our image to prevent cropping GreenfootImage img2 = new GreenfootImage((int) Math.ceil(x2*scaleFactor2), (int) Math.ceil(y2*scaleFactor2)); //this is the objects image so we can scale it to a large enough size that rotation won't crop it. img2.drawImage(atr.getImage(), (img2.getWidth()-atr.getImage().getWidth())/2, (img2.getHeight()-atr.getImage().getHeight())/2); //puts our image into the center of the enlarged image for rotating img2.rotate(atr.getRotation()); //rotating the image to match direction boolean[][] bitmask2 = new boolean[img2.getWidth()][img2.getHeight()];//new bitmask for the image of colliding object, 1 (true) being opaque (or partially transparent), 0 (false) being fully transparent for(int col = 0; col < bitmask.length; col++){ //Cycles through every x value of image for(int row = 0; row < bitmask[0].length; row++){ //Cycles through every y value of image bitmask2[col][row] = img2.getColorAt(col, row).getAlpha()!=0; //alpha 0 is transparent, this sets the bitmask pixel to the proper transparency t/f state } } //check stuff return false; } public Mover() { this(new Vector()); } /** * Create new thing initialised with given speed. */ public Mover(Vector movement) { this.movement = movement; } /** * Move in the current movement direction. Wrap around to the opposite edge of the * screen if moving out of the world. */ public void move() { exactX = exactX + movement.getX(); exactY = exactY + movement.getY(); if(exactX >= getWorld().getWidth()) { exactX = 0; } if(exactX < 0) { exactX = getWorld().getWidth() - 1; } if(exactY >= getWorld().getHeight()) { exactY = 0; } if(exactY < 0) { exactY = getWorld().getHeight() - 1; } super.setLocation((int) exactX, (int) exactY); } /** * Set the location from exact coordinates. */ public void setLocation(double x, double y) { exactX = x; exactY = y; super.setLocation((int) x, (int) y); } /** * Set the location from int coordinates. */ public void setLocation(int x, int y) { exactX = x; exactY = y; super.setLocation(x, y); } /** * Return the exact x-coordinate (as a double). */ public double getExactX() { return exactX; } /** * Return the exact y-coordinate (as a double). */ public double getExactY() { return exactY; } /** * Increase the speed with the given vector. */ public void addForce(Vector force) { movement.add(force); } /** * Accelerate the speed of this mover by the given factor. (Factors < 1 will * decelerate.) */ public void accelerate(double factor) { movement.scale(factor); if (movement.getLength() < 0.15) { movement.setNeutral(); } } /** * Return the speed of this actor. */ public double getSpeed() { return movement.getLength(); } /** * Stop movement of this actor. */ public void stop() { movement.setNeutral(); } /** * Return the current speed. */ public Vector getMovement() { return movement; } }