I've looked at several examples of people creating tile maps, and I am unable to get the tile position where my mouse is pointed at.
I am using a spritebatch and GameTile[][] to create the map. Keep in mind that the tiles themselves are isometric and not actually a square.
The method renderMap() is where the map is actually is being rendered. createMap() just sets the initial GameTiles for an empty map.
The map is able to be dragged and zoomed in and out using Ortho camera.
Zooming out gives me an issue as well, the tiles seem to be shifted over on click
public class MapEditor implements GameScene {
private GameContext context;
private SpriteBatch batch;
private OrthographicCamera camera;
public static GameTile[][] tiles; //GameTile.WIDTH = 64 & GameTile.HEIGHT =48
public static final int MAP_WIDTH = 20;
public static final int MAP_HEIGHT = 36;
public MapEditor(GameContext context) {
this.context = context;
tiles = new GameTile[MAP_WIDTH][MAP_HEIGHT];
}
#Override
public void create() {
renderer = new ShapeRenderer();
this.batch = new SpriteBatch();
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
}
public void createMap() {
// Create the sea tiles
for (int x = 0; x < MAP_WIDTH; x++) {
for (int y = 0; y < MAP_HEIGHT; y++) {
if (y < 3 || y > 32) {
if(tiles[x][y] == null) {
tiles[x][y] = safezone;
}
}
else {
if(tiles[x][y] == null) {
tiles[x][y] = cell;
}
}
}
}
}
#Override
public void update(){
// update the camera
camera.update();
}
#Override
public void render() {
batch.setProjectionMatrix(camera.combined);
batch.begin();
Gdx.gl.glViewport(0,0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
renderMap();
batch.end();
}
public int getTileX(float x, float y) {
/*
* getRegionWidth() = TILE_WIDTH_HALF
* getRegionHeight() = TILE_HEIGHT_HALF
* these are the ones being added to worldCoords.x/y
*/
Vector3 worldCoords = camera.unproject(new Vector3(x, y, 0));
return (int)((TILE_WIDTH_HALF * ((-TILE_HEIGHT_HALF + (worldCoords.y + TILE_HEIGHT_HALF)) /
TILE_HEIGHT_HALF) + (worldCoords.x + TILE_WIDTH_HALF)) / TILE_WIDTH_HALF) / 2;
}
public int getTileY(float x, float y) {
/*
* getRegionWidth() = TILE_WIDTH_HALF
* getRegionHeight() = TILE_HEIGHT_HALF
* these are the ones being added to worldCoords.x/y
*/
Vector3 worldCoords = camera.unproject(new Vector3(x, y, 0));
return (int)(((-TILE_HEIGHT_HALF * (TILE_WIDTH_HALF + (worldCoords.x + TILE_WIDTH_HALF)) /
TILE_WIDTH_HALF) + (worldCoords.y + TILE_HEIGHT_HALF)) / TILE_HEIGHT_HALF) / 2;
}
#Override
public boolean handleClick(float x, float y, int button) {
int tileX = getTileX(x,y);
int tileY = getTileY(x,y);
System.out.println("Tile:"+tileX + ","+tileY);
}
private void renderMap() {
for (int i = 0; i < tiles.length; i++) {
for(int j = 0; j < tiles[i].length; j++) {
TextureRegion region = tiles[i][j].getRegion();
int x = (i * GameTile.TILE_WIDTH / 2) - (j * GameTile.TILE_WIDTH / 2) - region.getRegionWidth() / 2;
int y = (i * GameTile.TILE_HEIGHT / 2) + (j * GameTile.TILE_HEIGHT / 2) - region.getRegionHeight() / 2;
if (canDraw(x, y, GameTile.TILE_WIDTH, GameTile.TILE_HEIGHT)) {
batch.draw(region, x, y);
}
}
}
}
Actual tile before doing anything to it;
Actual:
Desired:
Converting Cartesian coordinates to isometric is (sort of) done like this:
float isometricX = cartesianX - cartesianY;
float isometricY = (cartesianX + cartesianY) * 0.5f;
The formula needs to be scaled by the height-to-width ratio of the tiles as well and I think that is where it's going wrong in your code.
Given an unprojected worldMousePosition you can get the coordinates and tile coordinates like this:
float r = (float) TILE_HEIGHT / (float) TILE_WIDTH;
float mapx = (worldMousePosition.x / TILE_HEIGHT + worldMousePosition.y / (TILE_HEIGHT * r)) * r;
float mapy = (worldMousePosition.y / (TILE_HEIGHT * r) - (worldMousePosition.x / TILE_HEIGHT)) * r;
worldPosition = new Vector2(mapx - 0.5f, mapy + 0.5f); // -.5/+.5 because the drawing isn't aligned to the tile, it's aligned to the image
int tileX = (int) worldPosition.x;
int tileY = (int) worldPosition.y;
Full source code for the example above:
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
public class SandboxGame extends Game {
public static final int TILE_NONE = -1;
public static final int MAP_WIDTH = 20;
public static final int MAP_HEIGHT = 36;
public static final int TILE_WIDTH = 64;
public static final int TILE_HEIGHT = 48;
private SpriteBatch batch;
private OrthographicCamera camera;
private BitmapFont font;
private Vector3 unprojectVector = new Vector3();
private Vector2 worldMousePosition = new Vector2();
private Vector2 worldPosition = new Vector2();
private Texture[] textures;
private int[][] tiles = new int[MAP_WIDTH][MAP_HEIGHT];
#Override
public void create() {
batch = new SpriteBatch();
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
font = new BitmapFont(Gdx.files.internal("default.fnt"), Gdx.files.internal("default.png"), false);
textures = new Texture[] {
new Texture(Gdx.files.internal("tile.png"))
};
for(int x = 0; x < MAP_WIDTH; ++x) {
for(int y = 0; y < MAP_HEIGHT; ++y) {
int rnd = MathUtils.random(10);
if (rnd < 1)
tiles[x][y] = TILE_NONE;
else
tiles[x][y] = 0;
}
}
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
float scrollSpeed = 64;
float zoomSpeed = 2;
float delta = Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.A))
camera.position.x -= delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.D))
camera.position.x += delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.W))
camera.position.y += delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.S))
camera.position.y -= delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.Q))
camera.zoom = Math.min(camera.zoom + zoomSpeed * delta, 8.0f);
if (Gdx.input.isKeyPressed(Input.Keys.E))
camera.zoom = Math.max(camera.zoom - zoomSpeed * delta, 0.5f);
camera.update();
int mx = Gdx.input.getX();
int my = Gdx.input.getY();
camera.unproject(unprojectVector.set(mx, my, 0.0f));
worldMousePosition.set(unprojectVector.x, unprojectVector.y);
float r = (float) TILE_HEIGHT / (float) TILE_WIDTH;
float mapx = (worldMousePosition.x / TILE_HEIGHT + worldMousePosition.y / (TILE_HEIGHT * r)) * r;
float mapy = (worldMousePosition.y / (TILE_HEIGHT * r) - (worldMousePosition.x / TILE_HEIGHT)) * r;
worldPosition = new Vector2(mapx - 0.5f, mapy + 0.5f); // -.5/+.5 because the drawing isn't aligned to the tile, it's aligned to the image
int tileX = (int) worldPosition.x;
int tileY = (int) worldPosition.y;
batch.setProjectionMatrix(camera.combined);
batch.begin();
for (int col = MAP_WIDTH - 1; col >= 0; --col) {
for (int row = MAP_HEIGHT - 1; row >= 0; --row) {
if (tiles[col][row] != TILE_NONE) {
Texture texture = textures[tiles[col][row]];
int x = (col * TILE_WIDTH / 2) - (row * TILE_WIDTH / 2);
int y = (col * TILE_HEIGHT / 2) + (row * TILE_HEIGHT / 2);
batch.setColor(col == tileX && row == tileY ? Color.GRAY : Color.WHITE);
batch.draw(texture, x, y);
}
}
}
if (Gdx.input.isKeyPressed(Input.Keys.SPACE)) {
for (int col = MAP_WIDTH - 1; col >= 0; --col) {
for (int row = MAP_HEIGHT - 1; row >= 0; --row) {
int x = (col * TILE_WIDTH / 2) - (row * TILE_WIDTH / 2);
int y = (col * TILE_HEIGHT / 2) + (row * TILE_HEIGHT / 2);
font.draw(batch, String.format("(%d, %d)", col, row), x, y);
}
}
}
String str = String.format("World position (%.2f, %.2f), Tile (%d, %d)", worldPosition.x, worldPosition.y, (int)worldPosition.x, (int)worldPosition.y);
font.draw(batch, str, worldMousePosition.x, worldMousePosition.y);
batch.end();
}
}
I cant respond to bornander's post, but my tweak would be at
int tileX = (int) Math.Floor(worldPosition.x);
int tileY = (int) Math.Floor(worldPosition.y);
Where simple (int) cast will provide wrong position around 0 with negative values, if there are tiles, while using Math.Floor will work as intended.
Related
I'm working on a Breakout game, and I'm having a brick collision problem. The ball bounces off the wall, oar and brick. However, when the ball touches the oar, the oar disappears, although I seem to have prescribed that the ball in this case should rebound. I'm really stuck on this one. How can I fix it?
public class Breakout extends WindowProgram {
/**
* Width and height of application window in pixels
*/
public static final int APPLICATION_WIDTH = 400;
public static final int APPLICATION_HEIGHT = 600;
/**
* Dimensions of game board (usually the same)
*/
private static final int WIDTH = APPLICATION_WIDTH;
private static final int HEIGHT = APPLICATION_HEIGHT;
/**
* Dimensions of the paddle
*/
private static final int PADDLE_WIDTH = 60;
private static final int PADDLE_HEIGHT = 10;
/**
* Offset of the paddle up from the bottom
*/
private static final int PADDLE_Y_OFFSET = 30;
/**
* Number of bricks per row
*/
private static final int NBRICKS_PER_ROW = 10;
/**
* Number of rows of bricks
*/
private static final int NBRICK_ROWS = 10;
/**
* Separation between bricks
*/
private static final int BRICK_SEP = 4;
/**
* Width of a brick
*/
private static final int BRICK_WIDTH =
(WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
/**
* Height of a brick
*/
private static final int BRICK_HEIGHT = 8;
/**
* Radius of the ball in pixels
*/
private static final int BALL_RADIUS = 10;
private static final int DIAMETER = 2 * BALL_RADIUS;
/**
* Offset of the top brick row from the top
*/
private static final int BRICK_Y_OFFSET = 70;
/**
* Number of turns
*/
private static final int NTURNS = 3;
private static final double DX = 1;
RandomGenerator rgen = RandomGenerator.getInstance();
private void moveBall(GOval o) {
double vx;
double vy = 1;
vx = rgen.nextDouble(1.0, 3.0);
if (rgen.nextBoolean(0.5))
vx = -vx;
while (o.getY() < HEIGHT) {
if (ballAboveRoof(o) && vy < 0) {
vy = -vy;
}
if (ballBehindWallR(o) && vx > 0) {
vx = -vx;
}
if (ballBehindWallL(o) && vx < 0) {
vx = -vx;
}
if (ballBelowFloor(o)) {
GLabel l = new GLabel("GAME OVER");
l.setColor(Color.RED);
l.setFont("Verdana-30");
l.setLocation(((getWidth() - l.getWidth()) / 2), ((getHeight() + l.getAscent()) / 2));
add(l);
break;
}
if (getCollidingObject(o) == paddle) {
println("Paddle");
vy = -vy;
}
if (getCollidingObject(o) != brick) {
println("Brick");
remove(getCollidingObject(o));
}
o.move(vx, vy);
pause(10);
}
}
GRect bricks;
GRect brick;
private GRect paddle;
GOval o;
// GObject collider = getCollidingObject(o);
private GObject getCollidingObject(GObject o) {
double x = o.getX(), y = o.getY();
if (getElementAt(x, y) != null) {
return getElementAt(x, y);
} else if (getElementAt(x, y + BALL_RADIUS * 2) != null) {
return getElementAt(x, y + BALL_RADIUS * 2);
} else if (getElementAt(x + BALL_RADIUS * 2, y + BALL_RADIUS * 2) != null) {
return getElementAt(x + BALL_RADIUS * 2, y + BALL_RADIUS * 2);
} else if (getElementAt(x + BALL_RADIUS * 2, y) != null) {
return getElementAt(x + BALL_RADIUS * 2, y);
} else {
return null;
}
}
private boolean ballAboveRoof(GOval o) {
return o.getY() <= 0;
}
private boolean ballBelowFloor(GOval o) {
return o.getY() + o.getHeight() >= getHeight();
}
private boolean ballBehindWallL(GOval o) {
return o.getX() <= 0;
}
private boolean ballBehindWallR(GOval o) {
return o.getX() + o.getWidth() >= getWidth();
}
private GOval addBall() {
int x = (getWidth() - DIAMETER) / 2;
int y = (getHeight() - DIAMETER) / 2;
GOval o = new GOval(x, y, DIAMETER, DIAMETER);
o.setFilled(true);
o.setFillColor(Color.BLACK);
o.setColor(Color.BLACK);
add(o);
return o;
}
private void addPaddle() {
paddle = new GRect((WIDTH - PADDLE_WIDTH) / 2.0, HEIGHT - PADDLE_Y_OFFSET, PADDLE_WIDTH, PADDLE_HEIGHT);
paddle.setColor(Color.BLACK);
paddle.setFilled(true);
paddle.setFillColor(Color.BLACK);
add(paddle);
}
public void mouseMoved(MouseEvent mouseEvent) {
int newX = mouseEvent.getX();
if (newX - (double) PADDLE_WIDTH / 2 >= 0 && newX + (double) PADDLE_WIDTH / 2 <= getWidth()) {
paddle.setLocation(newX - (double) PADDLE_WIDTH / 2, HEIGHT - PADDLE_Y_OFFSET);
}
}
private void addBricks() {
for (int i = 0; i < NBRICK_ROWS; i++) {
for (int j = 0; j < NBRICKS_PER_ROW; j++) {
addBrick(i, j);
}
}
}
private void addBrick(int i, int j) {
int x = BRICK_SEP / 2;
int n1 = i < NBRICKS_PER_ROW ? 1 : 0;
int n2 = j < NBRICK_ROWS ? 1 : 0;
GRect brick = new GRect(x + i * (BRICK_WIDTH + (BRICK_SEP) * n1), BRICK_Y_OFFSET + j * (BRICK_HEIGHT + BRICK_SEP * n2), BRICK_WIDTH, BRICK_HEIGHT);
brick.setFillColor(Color.BLACK);
brick.setFilled(true);
brick.setColor(Color.BLACK);
add(brick);
}
// #Override
public void run() {
getMenuBar().setVisible(false);
addMouseListeners();
addPaddle();
addBricks();
o = addBall();
moveBall(o);
}
}
What I made:
I drew X and Y Axises and my segments;
I wrote a code, that returns true if the segments intersect.
I wrote a code, that returns the coordinate of an intersection point.
But how I can calculate a ratio does the intersection point divide line segments?
Thanks a lot for your help. ^^
My code is:
package com.staaankey;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Cartesian {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
CartesianFrame frame = new CartesianFrame();
frame.showUI();
}
});
}
}
class CartesianFrame extends JFrame {
CartesianPanel panel;
public CartesianFrame() {
panel = new CartesianPanel();
add(panel);
}
public void showUI() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Main");
setSize(700, 700);
setVisible(true);
}
}
class CartesianPanel extends JPanel {
// x-axis coord constants
public static final int X_AXIS_FIRST_X_COORD = 50;
public static final int X_AXIS_SECOND_X_COORD = 600;
public static final int X_AXIS_Y_COORD = 600;
// y-axis coord constants
public static final int Y_AXIS_FIRST_Y_COORD = 50;
public static final int Y_AXIS_SECOND_Y_COORD = 600;
public static final int Y_AXIS_X_COORD = 50;
//arrows of axis are represented with "hipotenuse" of
//triangle
// now we are define length of cathetas of that triangle
public static final int FIRST_LENGHT = 10;
public static final int SECOND_LENGHT = 5;
// size of start coordinate lenght
public static final int ORIGIN_COORDINATE_LENGHT = 6;
// distance of coordinate strings from axis
public static final int AXIS_STRING_DISTANCE = 20;
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// x-axis
g2.drawLine(X_AXIS_FIRST_X_COORD, X_AXIS_Y_COORD,
X_AXIS_SECOND_X_COORD, X_AXIS_Y_COORD);
// y-axis
g2.drawLine(Y_AXIS_X_COORD, Y_AXIS_FIRST_Y_COORD,
Y_AXIS_X_COORD, Y_AXIS_SECOND_Y_COORD);
// x-axis arrow
g2.drawLine(X_AXIS_SECOND_X_COORD - FIRST_LENGHT,
X_AXIS_Y_COORD - SECOND_LENGHT,
X_AXIS_SECOND_X_COORD, X_AXIS_Y_COORD);
g2.drawLine(X_AXIS_SECOND_X_COORD - FIRST_LENGHT,
X_AXIS_Y_COORD + SECOND_LENGHT,
X_AXIS_SECOND_X_COORD, X_AXIS_Y_COORD);
// y-axis arrow
g2.drawLine(Y_AXIS_X_COORD - SECOND_LENGHT,
Y_AXIS_FIRST_Y_COORD + FIRST_LENGHT,
Y_AXIS_X_COORD, Y_AXIS_FIRST_Y_COORD);
g2.drawLine(Y_AXIS_X_COORD + SECOND_LENGHT,
Y_AXIS_FIRST_Y_COORD + FIRST_LENGHT,
Y_AXIS_X_COORD, Y_AXIS_FIRST_Y_COORD);
// draw origin Point
g2.fillOval(
X_AXIS_FIRST_X_COORD - (ORIGIN_COORDINATE_LENGHT / 2),
Y_AXIS_SECOND_Y_COORD - (ORIGIN_COORDINATE_LENGHT / 2),
ORIGIN_COORDINATE_LENGHT, ORIGIN_COORDINATE_LENGHT);
// draw text "X" and draw text "Y"
g2.drawString("X", X_AXIS_SECOND_X_COORD - AXIS_STRING_DISTANCE / 2,
X_AXIS_Y_COORD + AXIS_STRING_DISTANCE);
g2.drawString("Y", Y_AXIS_X_COORD - AXIS_STRING_DISTANCE,
Y_AXIS_FIRST_Y_COORD + AXIS_STRING_DISTANCE / 2);
g2.drawString("(0, 0)", X_AXIS_FIRST_X_COORD - AXIS_STRING_DISTANCE,
Y_AXIS_SECOND_Y_COORD + AXIS_STRING_DISTANCE);
// numerate axis
int xCoordNumbers = 10;
int yCoordNumbers = 10;
int xLength = (X_AXIS_SECOND_X_COORD - X_AXIS_FIRST_X_COORD)
/ xCoordNumbers;
int yLength = (Y_AXIS_SECOND_Y_COORD - Y_AXIS_FIRST_Y_COORD)
/ yCoordNumbers;
// draw x-axis numbers
for(int i = 1; i < xCoordNumbers; i++) {
g2.drawLine(X_AXIS_FIRST_X_COORD + (i * xLength),
X_AXIS_Y_COORD - SECOND_LENGHT,
X_AXIS_FIRST_X_COORD + (i * xLength),
X_AXIS_Y_COORD + SECOND_LENGHT);
g2.drawString(Integer.toString(i),
X_AXIS_FIRST_X_COORD + (i * xLength) - 3,
X_AXIS_Y_COORD + AXIS_STRING_DISTANCE);
}
//draw y-axis numbers
for(int i = 1; i < yCoordNumbers; i++) {
g2.drawLine(Y_AXIS_X_COORD - SECOND_LENGHT,
Y_AXIS_SECOND_Y_COORD - (i * yLength),
Y_AXIS_X_COORD + SECOND_LENGHT,
Y_AXIS_SECOND_Y_COORD - (i * yLength));
g2.drawString(Integer.toString(i),
Y_AXIS_X_COORD - AXIS_STRING_DISTANCE,
Y_AXIS_SECOND_Y_COORD - (i * yLength));
}
//draw first section
int fX1 = 125;
int fY1 = 75;
int fX2 = 135;
int fY2 = 75;
g2.drawLine(fX1, fY1, fX2, fY2);
//draw second section
int sX1 = 130;
int sY1 = 50;
int sX2 = 140;
int sY2 = 150;
g2.drawLine(sX1, sY1, sX2, sY2);
int v1 = (sX2 - sX1) * (fY1 - sY1) - (sY2 - sY1) * (fX1 - sX1);
int v2 = (sX2 - sX1) * (fY2 - sX1) - (sY2 - sY1) * (fX2 - sX1);
int v3 = (fX2 - fX1) *(sY1 - fY1) - (fY2 - fY1) * (sX1 - fX1);
int v4 = (fX2 - fX1) * (sY2 - fY1) - (fY2 - fY1) * (sX2 - fX1);
Boolean result;
if((v1 * v2 < 0) && (v3 * v4 < 0)){
result = true;
}
else{
result = false;
}
System.out.println(result);
int detL1 = det(fX1, fY1, fX2, fY2);
int detL2 = det(sX1, sY1, sX2, sY2);
int x1mx2 = fX1 - fX2;
int x3mx4 = sX1 - sX2;
int y1my2 = fY1 - fY2;
int y3my4 = sY1 - sY2;
int denom = det(x1mx2, y1my2, x3mx4, y3my4);
if(denom == 0){
return;
}
int xnom = det(detL1 ,x1mx2, detL2, x3mx4);
int ynom = det(detL1, y1my2, detL2, y3my4);
int ixOut = xnom / denom;
int iYOut = ynom / denom;
System.out.println("ixOut: ");
System.out.println("iYOut: ");
System.out.println(ixOut);;
System.out.println(iYOut);
}
public int det(int x1, int y1, int x2, int y2){
return x1 * y2 - y1 * x2;
}
}
Use the distance formula, Math.hypot() to calculate the length of the segments. That would be from one end of the line (it's x,y location) to the intersection point. Then from the other lines x,y location to the intersection point. Then divide the smaller length by the larger length. Make certain you use floating point math for the result.
Example:
// some arbitrary line with slope = 5 and y-intercept = 23
UnaryOperator<Integer> y = x->5*x + 23;
int endx = 2;
int endy = y.apply(endx);
int otherendx = 10;
int otherendy = y.apply(otherendx);
int intx = 5;
int inty = y.apply(intx);
double seg1Length = Math.hypot(endx-intx, endy - inty);
double seg2Length = Math.hypot(otherendx-intx, otherendy - inty);
double ratio = Math.min(seg1Length,seg2Length)/Math.max(seg1Length,seg2Length);
prints
Ratio = 0.6
I'm trying to always show the front of a 3D font to the user. I tried rotating the font when rotating the camera, but never could get it to work.
I currently have this:
I'm trying to do this (font always faces front):
TrueTypeFont.java
package com.displee.render.font;
import lombok.AllArgsConstructor;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* A TrueType font implementation originally for Slick, edited for Bobjob's Engine
* #original author James Chambers (Jimmy)
* #original author Jeremy Adams (elias4444)
* #original author Kevin Glass (kevglass)
* #original author Peter Korzuszek (genail)
* #new version edited by David Aaron Muhar (bobjob)
*/
public class TrueTypeFont {
public final static int ALIGN_LEFT = 0, ALIGN_RIGHT = 1, ALIGN_CENTER = 2;
/**
* Array that holds necessary information about the font characters
*/
private IntObject[] charArray = new IntObject[256];
/**
* Map of user defined font characters (Character <-> IntObject)
*/
private Map customChars = new HashMap();
/**
* Boolean flag on whether AntiAliasing is enabled or not
*/
private boolean antiAlias;
/**
* Font's size
*/
private int fontSize = 0;
/**
* Font's height
*/
private int fontHeight = 0;
/**
* Texture used to cache the font 0-255 characters
*/
private int fontTextureID;
/**
* Default font texture width
*/
private int textureWidth = 512;
/**
* Default font texture height
*/
private int textureHeight = 512;
/**
* A reference to Java's AWT Font that we create our font texture from
*/
private Font font;
/**
* The font metrics for our Java AWT font
*/
private FontMetrics fontMetrics;
private int correctL = 9, correctR = 8;
private class IntObject {
/**
* Character's width
*/
public int width;
/**
* Character's height
*/
public int height;
/**
* Character's stored x position
*/
public int storedX;
/**
* Character's stored y position
*/
public int storedY;
}
public TrueTypeFont(Font font, boolean antiAlias, char[] additionalChars) {
this.font = font;
this.fontSize = font.getSize() + 3;
this.antiAlias = antiAlias;
createSet(additionalChars);
fontHeight -= 1;
if (fontHeight <= 0) {
fontHeight = 1;
}
}
public TrueTypeFont(Font font, boolean antiAlias) {
this(font, antiAlias, null);
}
public void setCorrection(boolean on) {
if (on) {
correctL = 2;
correctR = 1;
} else {
correctL = 0;
correctR = 0;
}
}
private BufferedImage getFontImage(char ch) {
// Create a temporary image to extract the character's size
BufferedImage tempfontImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
if (antiAlias == true) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
g.setFont(font);
fontMetrics = g.getFontMetrics();
int charwidth = fontMetrics.charWidth(ch) + 8;
if (charwidth <= 0) {
charwidth = 7;
}
int charheight = fontMetrics.getHeight() + 3;
if (charheight <= 0) {
charheight = fontSize;
}
// Create another image holding the character we are creating
BufferedImage fontImage;
fontImage = new BufferedImage(charwidth, charheight, BufferedImage.TYPE_INT_ARGB);
Graphics2D gt = (Graphics2D) fontImage.getGraphics();
if (antiAlias == true) {
gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
gt.setFont(font);
gt.setColor(Color.WHITE);
int charx = 3;
int chary = 1;
gt.drawString(String.valueOf(ch), (charx), (chary) + fontMetrics.getAscent());
//fontImage = ImageUtils.flipVertically(fontImage);
//fontImage = ImageUtils.flipHorizontally(fontImage);
//fontImage = ImageUtils.flipHorizontallyAndVertically(fontImage);
return fontImage;
}
private void createSet(char[] customCharsArray) {
// If there are custom chars then I expand the font texture twice
if (customCharsArray != null && customCharsArray.length > 0) {
textureWidth *= 2;
}
// In any case this should be done in other way. Texture with size 512x512
// can maintain only 256 characters with resolution of 32x32. The texture
// size should be calculated dynamicaly by looking at character sizes.
try {
BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) imgTemp.getGraphics();
g.setColor(new Color(0, 0, 0, 1));
g.fillRect(0, 0, textureWidth, textureHeight);
int rowHeight = 0;
int positionX = 0;
int positionY = 0;
int customCharsLength = (customCharsArray != null) ? customCharsArray.length : 0;
for (int i = 0; i < 256 + customCharsLength; i++) {
// get 0-255 characters and then custom characters
char ch = (i < 256) ? (char) i : customCharsArray[i - 256];
BufferedImage fontImage = getFontImage(ch);
IntObject newIntObject = new IntObject();
newIntObject.width = fontImage.getWidth();
newIntObject.height = fontImage.getHeight();
if (positionX + newIntObject.width >= textureWidth) {
positionX = 0;
positionY += rowHeight;
rowHeight = 0;
}
newIntObject.storedX = positionX;
newIntObject.storedY = positionY;
if (newIntObject.height > fontHeight) {
fontHeight = newIntObject.height;
}
if (newIntObject.height > rowHeight) {
rowHeight = newIntObject.height;
}
// Draw it here
g.drawImage(fontImage, positionX, positionY, null);
positionX += newIntObject.width;
if (i < 256) { // standard characters
charArray[i] = newIntObject;
} else { // custom characters
customChars.put(new Character(ch), newIntObject);
}
fontImage = null;
}
fontTextureID = loadImage(imgTemp);
//.getTexture(font.toString(), imgTemp);
} catch (Exception e) {
System.err.println("Failed to create font.");
e.printStackTrace();
}
}
private void drawQuad(float drawX, float drawY, float drawX2, float drawY2, float srcX, float srcY, float srcX2, float srcY2, float z) {
float DrawWidth = drawX2 - drawX;
float DrawHeight = drawY2 - drawY;
float TextureSrcX = srcX / textureWidth;
float TextureSrcY = srcY / textureHeight;
float SrcWidth = srcX2 - srcX;
float SrcHeight = srcY2 - srcY;
float RenderWidth = (SrcWidth / textureWidth);
float RenderHeight = (SrcHeight / textureHeight);
GL11.glTexCoord2f(TextureSrcX, TextureSrcY);
GL11.glVertex3f(drawX, drawY, z);
GL11.glTexCoord2f(TextureSrcX, TextureSrcY + RenderHeight);
GL11.glVertex3f(drawX, drawY + DrawHeight, z);
GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight);
GL11.glVertex3f(drawX + DrawWidth, drawY + DrawHeight, z);
GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY);
GL11.glVertex3f(drawX + DrawWidth, drawY, z);
}
public int getWidth(String whatchars) {
int totalwidth = 0;
IntObject intObject = null;
int currentChar = 0;
for (int i = 0; i < whatchars.length(); i++) {
currentChar = whatchars.charAt(i);
if (currentChar < 256) {
intObject = charArray[currentChar];
} else {
intObject = (IntObject) customChars.get(new Character((char) currentChar));
}
if (intObject != null) {
totalwidth += intObject.width;
}
}
return totalwidth;
}
public int getHeight() {
return fontHeight;
}
public int getHeight(String HeightString) {
return fontHeight;
}
public int getLineHeight() {
return fontHeight;
}
public void drawString(float x, float y, float z, String whatchars, float scaleX, float scaleY) {
drawString(x, y, z, whatchars, 0, whatchars.length() - 1, scaleX, scaleY, ALIGN_LEFT);
}
public void drawString(float x, float y, float z, String whatchars, float scaleX, float scaleY, int format) {
drawString(x, y, z, whatchars, 0, whatchars.length() - 1, scaleX, scaleY, format);
}
public void drawString(float x, float y, float z, String whatchars, int startIndex, int endIndex, float scaleX, float scaleY, int format) {
IntObject intObject = null;
int charCurrent;
int totalwidth = 0;
int i = startIndex, d, c;
float startY = 0;
switch (format) {
case ALIGN_RIGHT: {
d = -1;
c = correctR;
while (i < endIndex) {
if (whatchars.charAt(i) == '\n') {
startY -= fontHeight;
}
i++;
}
break;
}
case ALIGN_CENTER: {
for (int l = startIndex; l <= endIndex; l++) {
charCurrent = whatchars.charAt(l);
if (charCurrent == '\n') {
break;
}
if (charCurrent < 256) {
intObject = charArray[charCurrent];
} else {
intObject = (IntObject) customChars.get(new Character((char) charCurrent));
}
totalwidth += intObject.width - correctL;
}
totalwidth /= -2;
}
case ALIGN_LEFT:
default: {
d = 1;
c = correctL;
break;
}
}
java.util.List<QuadObject> list = new ArrayList<>(endIndex - startIndex);
while (i >= startIndex && i <= endIndex) {
charCurrent = whatchars.charAt(i);
if (charCurrent < 256) {
intObject = charArray[charCurrent];
} else {
intObject = (IntObject) customChars.get(new Character((char) charCurrent));
}
if (intObject != null) {
if (d < 0) {
totalwidth += (intObject.width - c) * d;
}
if (charCurrent == '\n') {
startY -= fontHeight * d;
totalwidth = 0;
if (format == ALIGN_CENTER) {
for (int l = i + 1; l <= endIndex; l++) {
charCurrent = whatchars.charAt(l);
if (charCurrent == '\n') {
break;
}
if (charCurrent < 256) {
intObject = charArray[charCurrent];
} else {
intObject = (IntObject) customChars.get(new Character((char) charCurrent));
}
totalwidth += intObject.width - correctL;
}
totalwidth /= -2;
}
//if center get next lines total width/2;
} else {
QuadObject quad = new QuadObject((totalwidth + intObject.width) * scaleX + x, startY * scaleY + y, totalwidth * scaleX + x, (startY + intObject.height) * scaleY + y, intObject.storedX + intObject.width, intObject.storedY + intObject.height, intObject.storedX, intObject.storedY, z);
list.add(quad);
if (d > 0) {
totalwidth += (intObject.width - c) * d;
}
}
i += d;
}
}
float centerX = 0;
for(QuadObject quad : list) {
centerX += quad.drawX + (quad.drawX2 - quad.drawX);
}
centerX /= 2.0f;
float centerY = 0;
for(QuadObject quad : list) {
centerY += quad.drawY + (quad.drawY2 - quad.drawY);
}
centerY /= 2.0f;
//GL11.glTranslatef( -centerX, -centerY, -z);
//GL11.glTranslatef(0, 0, -z);
GL11.glRotatef(-Test3DFont.rotation.x, 0.0f, 0.0f, 0.0f);
GL11.glRotatef(-Test3DFont.rotation.y, 0.0f, 1.0f, 0.0f);
//GL11.glRotatef(-Test3DFont.rotation.z, 0.0f, 0.0f, 1.0f);
//GL11.glTranslatef(0, 0, z);
//GL11.glTranslatef(centerX, centerY, z); // M1 - 2nd translation
GL11.glEnable(GL11.GL_TEXTURE_2D); // Enable Texture Mapping
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID);
GL11.glBegin(GL11.GL_QUADS);
for(QuadObject quad : list) {
drawQuad(quad.drawX, quad.drawY, quad.drawX2, quad.drawY2, quad.srcX, quad.srcY, quad.srcX2, quad.srcY2, quad.z);
}
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnd();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
public static int loadImage(BufferedImage bufferedImage) {
try {
short width = (short) bufferedImage.getWidth();
short height = (short) bufferedImage.getHeight();
//textureLoader.bpp = bufferedImage.getColorModel().hasAlpha() ? (byte)32 : (byte)24;
int bpp = (byte) bufferedImage.getColorModel().getPixelSize();
ByteBuffer byteBuffer;
DataBuffer db = bufferedImage.getData().getDataBuffer();
if (db instanceof DataBufferInt) {
int intI[] = ((DataBufferInt) (bufferedImage.getData().getDataBuffer())).getData();
byte newI[] = new byte[intI.length * 4];
for (int i = 0; i < intI.length; i++) {
byte b[] = intToByteArray(intI[i]);
int newIndex = i * 4;
newI[newIndex] = b[1];
newI[newIndex + 1] = b[2];
newI[newIndex + 2] = b[3];
newI[newIndex + 3] = b[0];
}
byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(newI);
} else {
byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(((DataBufferByte) (bufferedImage.getData().getDataBuffer())).getData());
}
byteBuffer.flip();
int internalFormat = GL11.GL_RGBA8, format = GL11.GL_RGBA;
IntBuffer textureId = BufferUtils.createIntBuffer(1);
;
GL11.glGenTextures(textureId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId.get(0));
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL11.GL_UNSIGNED_BYTE, byteBuffer.order(ByteOrder.nativeOrder()));
return textureId.get(0);
} catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
return -1;
}
public static boolean isSupported(String fontname) {
Font font[] = getFonts();
for (int i = font.length - 1; i >= 0; i--) {
if (font[i].getName().equalsIgnoreCase(fontname)) {
return true;
}
}
return false;
}
public static Font[] getFonts() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
}
public static byte[] intToByteArray(int value) {
return new byte[]{(byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value};
}
public void destroy() {
IntBuffer scratch = BufferUtils.createIntBuffer(1);
scratch.put(0, fontTextureID);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GL11.glDeleteTextures(scratch);
}
#AllArgsConstructor
private class QuadObject {
private float drawX;
private float drawY;
private float drawX2;
private float drawY2;
private float srcX;
private float srcY;
private float srcX2;
private float srcY2;
private float z;
}
}
Test3DFont.java
package com.displee.render.font;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Vector3f;
import java.awt.*;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.gluPerspective;
public class Test3DFont {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final float FOV = 45f;
private static final float NEAR = 0.1f;
private static final float FAR = 1000f;
private static boolean mousePressed;
private static Vector3f startCoordinations = new Vector3f();
private static float scale = 0.05f;
public static Vector3f rotation = new Vector3f(0, 0, 0);
private static Vector3f startRotation = new Vector3f();
private static TrueTypeFont font;
private static boolean running = true;
public static void main(String[] args) throws Exception {
initializeDisplay();
font = new TrueTypeFont(new Font("serif", Font.PLAIN, 30), true);
initializeGL();
while(running) {
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
handleMouse();
font.drawString(0, 0, 0, "Test", 0.1f, 0.1f);
loadDefaultRotation();
setViewport();
drawGrid();
Display.sync(60);
Display.update();
if (Display.isCloseRequested()) {
break;
}
}
font.destroy();
Display.destroy();
}
private static void initializeDisplay() throws LWJGLException {
Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
Display.create();
setViewport();
}
public static void set2DMode() {
//GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glMatrixMode(GL11.GL_PROJECTION); // Select The Projection Matrix
GL11.glPushMatrix(); // Store The Projection Matrix
GL11.glLoadIdentity(); // Reset The Projection Matrix
GL11.glOrtho(0, WIDTH, 0, HEIGHT, -1, 1); // Set Up An Ortho Screen
GL11.glMatrixMode(GL11.GL_MODELVIEW); // Select The Modelview Matrix
GL11.glPushMatrix(); // Store The Modelview Matrix
GL11.glLoadIdentity(); // Reset The Modelview Matrix
}
public static void set3DMode() {
GL11.glMatrixMode(GL11.GL_PROJECTION); // Select The Projection Matrix
GL11.glPopMatrix(); // Restore The Old Projection Matrix
GL11.glMatrixMode(GL11.GL_MODELVIEW); // Select The Modelview Matrix
GL11.glPopMatrix(); // Restore The Old Projection Matrix
//GL11.glEnable(GL11.GL_DEPTH_TEST);
}
private static void setViewport() {
glViewport(0, 0, WIDTH, HEIGHT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(FOV, (float) WIDTH / (float) HEIGHT, NEAR, FAR);
glMatrixMode(GL_MODELVIEW);
}
private static void initializeGL() {
glShadeModel(GL_SMOOTH);
glEnable(GL_NORMALIZE);
glEnable(GL_BLEND);
glCullFace(GL_BACK);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
private static void handleMouse() {
scale += Mouse.getDWheel() > 0 ? 0.005f : Mouse.getDWheel() < 0 ? -0.005f : 0;
int x = Mouse.getY();
int y = Mouse.getX();
if (!mousePressed) {
mousePressed = Mouse.isButtonDown(0);
if (mousePressed) {
startCoordinations.set((float) x, (float) y, 0.0f);
startRotation = new Vector3f(rotation);
}
} else if (!Mouse.isButtonDown(0)) {
mousePressed = false;
}
if (!mousePressed) {
return;
}
float differenceX = x - startCoordinations.x;
float differenceY = y - startCoordinations.y;
rotation.set(startRotation.x - (differenceX * 0.5F), startRotation.y + (differenceY * 0.5F), 0);
}
private static void loadDefaultRotation() {
glLoadIdentity();
Vector3f cameraPosition = new Vector3f();
glTranslatef(cameraPosition.x, cameraPosition.y, -10);
glRotatef(rotation.x, 1.0F, 0.0F, 0.0F);
glRotatef(rotation.y, 0.0F, 1.0F, 0.0F);
glRotatef(rotation.z, 0.0F, 0.0F, 1.0F);
glScalef(scale, scale, scale);
}
private static void drawGrid() {
glColor4f(0.7176471f, 0.7176471f, 0.7176471f, 1.0f);
glBegin(GL_LINES);
float size = 50;
float step = 10;
for (float i = -size; i <= size; i += step) {
glVertex3f(i, 0, size);
glVertex3f(i, 0, -size);
glVertex3f(size, 0, i);
glVertex3f(-size, 0, i);
}
glEnd();
}
public static int[] getScreenCoords(double x, double y, double z) {
FloatBuffer screenCoords = BufferUtils.createFloatBuffer(4);
IntBuffer viewport = BufferUtils.createIntBuffer(16);
FloatBuffer modelView = BufferUtils.createFloatBuffer(16);
FloatBuffer projection = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelView);
GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projection);
GL11.glGetInteger(GL11.GL_VIEWPORT, viewport);
boolean result = GLU.gluProject((float) x, (float) y, (float) z, modelView, projection, viewport, screenCoords);
if (result) {
return new int[] { (int) screenCoords.get(0), (int) screenCoords.get(1) };
}
return null;
}
}
Can anyone help me with this? How can I make it so I always see the front of the font?
Update 1:
I've got it almost working by rotating the quads. I've added the following code in the drawString method before enabling texture 2D:
GL11.glRotatef(-Test3DFont.rotation.x, 0.0f, 0.0f, 0.0f);
GL11.glRotatef(-Test3DFont.rotation.y, 0.0f, 1.0f, 0.0f);
I've updated the code. It currently looks like this:
I finally fixed it by pushing a matrix and using the original x, y and z coords in the translation. I also had to subtract 180 from the rotation because that's my starting rotation. Ended up with the following code:
GL11.glPushMatrix();
GL11.glTranslatef(x, y, z);
GL11.glRotatef(180 - rotation.y, 0.0f, 1.0f, 0.0f);
GL11.glTranslatef(-x, -y, -z);
GL11.glEnable(GL11.GL_TEXTURE_2D); // Enable Texture Mapping
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID);
GL11.glBegin(GL11.GL_QUADS);
for(QuadObject quad : list) {
drawQuad(quad.drawX, quad.drawY, quad.drawX2, quad.drawY2, quad.srcX, quad.srcY, quad.srcX2, quad.srcY2, quad.z);
}
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnd();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GL11.glPopMatrix();
i am having a bug which i can't figure out.I tried many times,the collision detection and calculating new velocities seems fine ,but some balls seem to stuck with each other i don't why.Can you please help me out.
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.Random;
import javax.swing.JFrame;
public class ElasticCollision extends Canvas implements Runnable {
private static final int WIDTH = 300;
private static final int HEIGHT = WIDTH / 16 * 9;
private static final int SCALE = 3;
private static final String TITLE = "Elastic collision";
private boolean running = false;
private JFrame frame;
private Thread thread;
private Random random = new Random();
private Color color;
private int a, b, c;
private Ball[] ball;
private int x = 0, y = 0;
private int radius = 0;
private int speedX = 0, speedY = 0;
private int noOfBalls = 25;
private double newVelX1 = 0, newVelY1 = 0;
private double newVelX2 = 0, newVelY2 = 0;
private double angle1 = 0, angle2 = 0, angle3 = 0;
private int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
public ElasticCollision() {
Dimension size = new Dimension(WIDTH * SCALE, HEIGHT * SCALE);
setPreferredSize(size);
frame = new JFrame();
ball = new Ball[noOfBalls];
}
public void start() {
for (int i = 0; i < noOfBalls; i++) {
x = random.nextInt(getWidth());
y = random.nextInt(getHeight());
radius = 25 + random.nextInt(25);
speedX = 1 + random.nextInt(2);
speedY = 1 + random.nextInt(2);
ball[i] = new Ball(x, y, radius, speedX, speedY);
}
running = true;
thread = new Thread(this);
thread.start();
}
public void stop() {
running = false;
}
public void run() {
long lastTime = System.nanoTime();
double unprocessed = 0;
double nsPerTick = 1000000000.0 / 60;
int frames = 0;
int ticks = 0;
long lastTimer = System.currentTimeMillis();
while (running) {
long now = System.nanoTime();
unprocessed += (now - lastTime) / nsPerTick;
lastTime = now;
while (unprocessed >= 1) {
ticks++;
update();
unprocessed -= 1;
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < noOfBalls; i++) {
for (int j = i + 1; j < noOfBalls; j++) {
if (ball[i].inCollision != true
|| ball[j].inCollision != true)
checkCollision(ball[i], ball[j]);
}
}
frames++;
render();
if (System.currentTimeMillis() - lastTimer > 1000) {
lastTimer += 1000;
frame.setTitle(TITLE + " | " + ticks + " ticks, " + frames
+ " fps");
frames = 0;
ticks = 0;
}
}
stop();
}
public void update() {
for (int i = 0; i < noOfBalls; i++) {
ball[i].x += ball[i].speedX;
ball[i].y += ball[i].speedY;
if (ball[i].x >= getWidth() - ball[i].radius && ball[i].speedX > 0)
ball[i].speedX = -ball[i].speedX;
if (ball[i].x <= ball[i].radius && ball[i].speedX < 0)
ball[i].speedX = -ball[i].speedX;
if (ball[i].y >= getHeight() - ball[i].radius && ball[i].speedY > 0)
ball[i].speedY = -ball[i].speedY;
if (ball[i].y <= ball[i].radius && ball[i].speedY < 0)
ball[i].speedY = -ball[i].speedY;
}
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.yellow);
g.fillRect(0, 0, getWidth(), getHeight());
for (int i = 0; i < noOfBalls; i++)
ball[i].paint(g);
g.dispose();
bs.show();
}
public void checkCollision(Ball ball1, Ball ball2) {
double distance;
if (ball1.x + ball1.radius + ball2.radius > ball2.x
&& ball1.x < ball1.x + ball1.radius + ball2.radius
&& ball1.y + ball1.radius + ball2.radius > ball2.y
&& ball1.y < ball2.y + ball1.radius + ball2.radius) {
distance = Math.sqrt(((ball1.x - ball2.x) * (ball1.x - ball2.x))
+ ((ball1.y - ball2.y) * (ball1.y - ball2.y)));
if ((int) distance < ball1.radius + ball2.radius) {
ball1.collision = true;
ball2.collision = true;
ball1.inCollision = true;
ball2.inCollision = true;
ball1.collisionX = ((ball1.x * ball2.radius) + (ball2.x * ball1.radius))
/ (ball1.radius + ball2.radius) + ball1.radius;
ball1.collisionY = ((ball1.y * ball2.radius) + (ball2.y * ball1.radius))
/ (ball1.radius + ball2.radius) + ball1.radius;
ball2.collisionX = ((ball1.x * ball2.radius) + (ball2.x * ball1.radius))
/ (ball1.radius + ball2.radius) + ball2.radius;
ball2.collisionY =
((ball1.y * ball2.radius) + (ball2.y * ball1.radius))
/ (ball1.radius + ball2.radius) + ball2.radius;
/*
* x1 = (ball1.x - getWidth()) / 2; y1 = (ball2.y - getHeight())
* / 2; angle1 = Math.toDegrees(Math.atan2(y1, x1));
*
* x2 = (ball1.x - getWidth()) / 2; y2 = (ball2.y - getHeight())
* / 2; angle2 = Math.toDegrees(Math.atan2(y2, x2));
*/
double colision_angle = Math.toDegrees(Math.atan2(
(ball2.y - ball1.y), (ball2.x - ball1.x)));
double speed1 = Math.sqrt(ball1.speedX * ball1.speedX
+ ball1.speedY * ball1.speedY);
double speed2 = Math.sqrt(ball2.speedX * ball2.speedX
+ ball2.speedY * ball2.speedY);
double direction1 = Math.atan2(ball1.speedY, ball1.speedX);
double direction2 = Math.atan2(ball2.speedY, ball2.speedX);
double vx_1 = speed1 * Math.cos(direction1 - colision_angle);
double vy_1 = speed1 * Math.sin(direction1 - colision_angle);
double vx_2 = speed2 * Math.cos(direction2 - colision_angle);
double vy_2 = speed2 * Math.sin(direction2 - colision_angle);
double final_vx_1 = ((ball1.radius - ball2.radius) * vx_1 + (ball2.radius + ball2.radius)
* vx_2)
/ (ball1.radius + ball2.radius);
double final_vx_2 = ((ball1.radius + ball1.radius) * vx_1 + (ball2.radius - ball1.radius)
* vx_2)
/ (ball1.radius + ball2.radius);
double final_vy_1 = vy_1;
double final_vy_2 = vy_2;
newVelX1 = (int) (Math.cos(colision_angle) * final_vx_1 + Math
.cos(colision_angle + Math.PI / 2) * final_vy_1);
newVelY1 = (int) (Math.sin(colision_angle) * final_vx_1 + Math
.sin(colision_angle + Math.PI / 2) * final_vy_1);
newVelX2 = (int) (Math.cos(colision_angle) * final_vx_2 + Math
.cos(colision_angle + Math.PI / 2) * final_vy_2);
newVelY2 = (int) (Math.sin(colision_angle) * final_vx_2 + Math
.sin(colision_angle + Math.PI / 2) * final_vy_2);
ball1.speedX = (int) newVelX1;
ball1.speedY = (int) newVelY1;
ball2.speedX = (int) newVelX2;
ball2.speedY = (int) newVelY2;
ball1.x = ball1.x + (int) newVelX1;
ball1.y = ball1.y + (int) newVelY1;
ball2.x = ball2.x + (int) newVelX2;
ball2.y = ball2.y + (int) newVelY2;
}
}
}
public static void main(String[] args) {
ElasticCollision balls = new ElasticCollision();
balls.frame.setResizable(false);
balls.frame.add(balls);
balls.frame.pack();
balls.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
balls.frame.setVisible(true);
balls.start();
}
}
class Ball {
protected int x = 0, y = 0;
protected int radius;
protected int speedX = 0, speedY = 0;
protected boolean collision = false;
protected int collisionX = 0, collisionY = 0;
protected boolean inCollision = false;
public Ball(int x, int y, int radius, int speedX, int speedY) {
this.x = x + radius;
this.y = y + radius;
this.radius = radius;
this.speedX = speedX;
this.speedY = speedY;
}
public void paint(Graphics g) {
if (!collision) {
g.setColor(Color.red);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
} else {
g.setColor(Color.green);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
g.setColor(Color.blue);
g.fillOval(collisionX - radius, collisionY - radius, 20, 20);
g.setColor(Color.black);
g.drawOval(collisionX - radius, collisionY - radius, 20, 20);
collision = false;
inCollision = false;
}
g.setColor(Color.black);
g.drawOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
My guess is that your logic doesn't let the balls move apart once the collision is detected. Only change the direction of movement once and not every instant the balls are close to one another.
I would like to know if its possible to draw a Arc on a graphics Panel using a gradient and how I would go about it.
My end goal would be to rotate the arc in a full circle so it would be similar to a rotating loading circle. However it is not a loading bar. It would be a background of a custom JButton.
Any suggestions to alternatives that would create a similar effect would be appreciated.
This is similar to what oi want to draw. Keep in mind that it will be "rotating"
public class TestArc {
public static void main(String[] args) {
new TestArc();
}
public TestArc() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int radius = Math.min(getWidth(), getHeight());
int x = (getWidth() - radius) / 2;
int y = (getHeight() - radius) / 2;
RadialGradientPaint rgp = new RadialGradientPaint(
new Point(getWidth() / 2, getHeight() / 2),
radius,
new float[]{0f, 1f},
new Color[]{Color.RED, Color.YELLOW}
);
g2d.setPaint(rgp);
g2d.fill(new Arc2D.Float(x, y, radius, radius, 0, 45, Arc2D.PIE));
g2d.dispose();
}
}
}
You might like to have a look at 2D Graphics for more info
Updated after additional input
So you want a conical fill effect then...
The implementation I have comes from Harmonic Code, but I can't find a direct reference to it (I think it's part of his (excellent) series), but you can see the source code here
Now. I had issues with the angles as it appears that 0 starts at the top point (not the left) and it doesn't like negative angles...you might have better luck, but what I did was create a basic buffer at a position I could easily get working and then rotate the graphics context using an AffineTransformation...
public class TestArc {
public static void main(String[] args) {
new TestArc();
}
public TestArc() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private float angle = 0;
private float extent = 270;
private BufferedImage buffer;
public TestPane() {
Timer timer = new Timer(125, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
angle -= 5;
if (angle > 360) {
angle = 0;
}
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(false);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected BufferedImage getBuffer() {
if (buffer == null) {
int radius = Math.min(getWidth(), getHeight());
int x = (getWidth() - radius) / 2;
int y = (getHeight() - radius) / 2;
buffer = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffer.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
float startAngle = 0;
Color start = new Color(0, 128, 0, 128);
Color end = new Color(0, 128, 0, 0);
ConicalGradientPaint rgp = new ConicalGradientPaint(
true,
new Point(getWidth() / 2, getHeight() / 2),
0.5f,
new float[]{startAngle, extent},
new Color[]{start, end});
g2d.setPaint(rgp);
g2d.fill(new Arc2D.Float(x, y, radius, radius, startAngle + 90, -extent, Arc2D.PIE));
// g2d.fill(new Ellipse2D.Float(0, 0, radius, radius));
g2d.dispose();
g2d.dispose();
}
return buffer;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int radius = Math.min(getWidth(), getHeight());
int x = (getWidth()) / 2;
int y = (getHeight()) / 2;
BufferedImage buffer = getBuffer();
g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(angle), x, y));
x = (getWidth() - buffer.getWidth()) / 2;
y = (getHeight() - buffer.getHeight()) / 2;
g2d.drawImage(buffer, x, y, this);
g2d.dispose();
}
}
public final class ConicalGradientPaint implements java.awt.Paint {
private final java.awt.geom.Point2D CENTER;
private final double[] FRACTION_ANGLES;
private final double[] RED_STEP_LOOKUP;
private final double[] GREEN_STEP_LOOKUP;
private final double[] BLUE_STEP_LOOKUP;
private final double[] ALPHA_STEP_LOOKUP;
private final java.awt.Color[] COLORS;
private static final float INT_TO_FLOAT_CONST = 1f / 255f;
/**
* Standard constructor which takes the FRACTIONS in values from 0.0f to
* 1.0f
*
* #param CENTER
* #param GIVEN_FRACTIONS
* #param GIVEN_COLORS
* #throws IllegalArgumentException
*/
public ConicalGradientPaint(final java.awt.geom.Point2D CENTER, final float[] GIVEN_FRACTIONS, final java.awt.Color[] GIVEN_COLORS) throws IllegalArgumentException {
this(false, CENTER, 0.0f, GIVEN_FRACTIONS, GIVEN_COLORS);
}
/**
* Enhanced constructor which takes the FRACTIONS in degress from 0.0f to
* 360.0f and also an GIVEN_OFFSET in degrees around the rotation CENTER
*
* #param USE_DEGREES
* #param CENTER
* #param GIVEN_OFFSET
* #param GIVEN_FRACTIONS
* #param GIVEN_COLORS
* #throws IllegalArgumentException
*/
public ConicalGradientPaint(final boolean USE_DEGREES, final java.awt.geom.Point2D CENTER, final float GIVEN_OFFSET, final float[] GIVEN_FRACTIONS, final java.awt.Color[] GIVEN_COLORS) throws IllegalArgumentException {
// Check that fractions and colors are of the same size
if (GIVEN_FRACTIONS.length != GIVEN_COLORS.length) {
throw new IllegalArgumentException("Fractions and colors must be equal in size");
}
final java.util.ArrayList<Float> FRACTION_LIST = new java.util.ArrayList<Float>(GIVEN_FRACTIONS.length);
final float OFFSET;
if (USE_DEGREES) {
final double DEG_FRACTION = 1f / 360f;
if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), -0.5) == 0) {
OFFSET = -0.5f;
} else if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), 0.5) == 0) {
OFFSET = 0.5f;
} else {
OFFSET = (float) (GIVEN_OFFSET * DEG_FRACTION);
}
for (float fraction : GIVEN_FRACTIONS) {
FRACTION_LIST.add((float) (fraction * DEG_FRACTION));
}
} else {
// Now it seems to work with rotation of 0.5f, below is the old code to correct the problem
// if (GIVEN_OFFSET == -0.5)
// {
// // This is needed because of problems in the creation of the Raster
// // with a angle offset of exactly -0.5
// OFFSET = -0.49999f;
// }
// else if (GIVEN_OFFSET == 0.5)
// {
// // This is needed because of problems in the creation of the Raster
// // with a angle offset of exactly +0.5
// OFFSET = 0.499999f;
// }
// else
{
OFFSET = GIVEN_OFFSET;
}
for (float fraction : GIVEN_FRACTIONS) {
FRACTION_LIST.add(fraction);
}
}
// Check for valid offset
if (OFFSET > 0.5f || OFFSET < -0.5f) {
throw new IllegalArgumentException("Offset has to be in the range of -0.5 to 0.5");
}
// Adjust fractions and colors array in the case where startvalue != 0.0f and/or endvalue != 1.0f
final java.util.List<java.awt.Color> COLOR_LIST = new java.util.ArrayList<java.awt.Color>(GIVEN_COLORS.length);
COLOR_LIST.addAll(java.util.Arrays.asList(GIVEN_COLORS));
// Assure that fractions start with 0.0f
if (FRACTION_LIST.get(0) != 0.0f) {
FRACTION_LIST.add(0, 0.0f);
final java.awt.Color TMP_COLOR = COLOR_LIST.get(0);
COLOR_LIST.add(0, TMP_COLOR);
}
// Assure that fractions end with 1.0f
if (FRACTION_LIST.get(FRACTION_LIST.size() - 1) != 1.0f) {
FRACTION_LIST.add(1.0f);
COLOR_LIST.add(GIVEN_COLORS[0]);
}
// Recalculate the fractions and colors with the given offset
final java.util.Map<Float, java.awt.Color> FRACTION_COLORS = recalculate(FRACTION_LIST, COLOR_LIST, OFFSET);
// Clear the original FRACTION_LIST and COLOR_LIST
FRACTION_LIST.clear();
COLOR_LIST.clear();
// Sort the hashmap by fraction and add the values to the FRACION_LIST and COLOR_LIST
final java.util.SortedSet<Float> SORTED_FRACTIONS = new java.util.TreeSet<Float>(FRACTION_COLORS.keySet());
final java.util.Iterator<Float> ITERATOR = SORTED_FRACTIONS.iterator();
while (ITERATOR.hasNext()) {
final float CURRENT_FRACTION = ITERATOR.next();
FRACTION_LIST.add(CURRENT_FRACTION);
COLOR_LIST.add(FRACTION_COLORS.get(CURRENT_FRACTION));
}
// Set the values
this.CENTER = CENTER;
COLORS = COLOR_LIST.toArray(new java.awt.Color[]{});
// Prepare lookup table for the angles of each fraction
final int MAX_FRACTIONS = FRACTION_LIST.size();
this.FRACTION_ANGLES = new double[MAX_FRACTIONS];
for (int i = 0; i < MAX_FRACTIONS; i++) {
FRACTION_ANGLES[i] = FRACTION_LIST.get(i) * 360;
}
// Prepare lookup tables for the color stepsize of each color
RED_STEP_LOOKUP = new double[COLORS.length];
GREEN_STEP_LOOKUP = new double[COLORS.length];
BLUE_STEP_LOOKUP = new double[COLORS.length];
ALPHA_STEP_LOOKUP = new double[COLORS.length];
for (int i = 0; i < (COLORS.length - 1); i++) {
RED_STEP_LOOKUP[i] = ((COLORS[i + 1].getRed() - COLORS[i].getRed()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
GREEN_STEP_LOOKUP[i] = ((COLORS[i + 1].getGreen() - COLORS[i].getGreen()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
BLUE_STEP_LOOKUP[i] = ((COLORS[i + 1].getBlue() - COLORS[i].getBlue()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
ALPHA_STEP_LOOKUP[i] = ((COLORS[i + 1].getAlpha() - COLORS[i].getAlpha()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
}
}
/**
* Recalculates the fractions in the FRACTION_LIST and their associated
* colors in the COLOR_LIST with a given OFFSET. Because the conical
* gradients always starts with 0 at the top and clockwise direction you
* could rotate the defined conical gradient from -180 to 180 degrees which
* equals values from -0.5 to +0.5
*
* #param FRACTION_LIST
* #param COLOR_LIST
* #param OFFSET
* #return Hashmap that contains the recalculated fractions and colors after
* a given rotation
*/
private java.util.HashMap<Float, java.awt.Color> recalculate(final java.util.List<Float> FRACTION_LIST, final java.util.List<java.awt.Color> COLOR_LIST, final float OFFSET) {
// Recalculate the fractions and colors with the given offset
final int MAX_FRACTIONS = FRACTION_LIST.size();
final java.util.HashMap<Float, java.awt.Color> FRACTION_COLORS = new java.util.HashMap<Float, java.awt.Color>(MAX_FRACTIONS);
for (int i = 0; i < MAX_FRACTIONS; i++) {
// Add offset to fraction
final float TMP_FRACTION = FRACTION_LIST.get(i) + OFFSET;
// Color related to current fraction
final java.awt.Color TMP_COLOR = COLOR_LIST.get(i);
// Check each fraction for limits (0...1)
if (TMP_FRACTION <= 0) {
FRACTION_COLORS.put(1.0f + TMP_FRACTION + 0.0001f, TMP_COLOR);
final float NEXT_FRACTION;
final java.awt.Color NEXT_COLOR;
if (i < MAX_FRACTIONS - 1) {
NEXT_FRACTION = FRACTION_LIST.get(i + 1) + OFFSET;
NEXT_COLOR = COLOR_LIST.get(i + 1);
} else {
NEXT_FRACTION = 1 - FRACTION_LIST.get(0) + OFFSET;
NEXT_COLOR = COLOR_LIST.get(0);
}
if (NEXT_FRACTION > 0) {
final java.awt.Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, NEXT_COLOR, (int) ((NEXT_FRACTION - TMP_FRACTION) * 10000), (int) ((-TMP_FRACTION) * 10000));
FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
}
} else if (TMP_FRACTION >= 1) {
FRACTION_COLORS.put(TMP_FRACTION - 1.0f - 0.0001f, TMP_COLOR);
final float PREVIOUS_FRACTION;
final java.awt.Color PREVIOUS_COLOR;
if (i > 0) {
PREVIOUS_FRACTION = FRACTION_LIST.get(i - 1) + OFFSET;
PREVIOUS_COLOR = COLOR_LIST.get(i - 1);
} else {
PREVIOUS_FRACTION = FRACTION_LIST.get(MAX_FRACTIONS - 1) + OFFSET;
PREVIOUS_COLOR = COLOR_LIST.get(MAX_FRACTIONS - 1);
}
if (PREVIOUS_FRACTION < 1) {
final java.awt.Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, PREVIOUS_COLOR, (int) ((TMP_FRACTION - PREVIOUS_FRACTION) * 10000), (int) (TMP_FRACTION - 1.0f) * 10000);
FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
}
} else {
FRACTION_COLORS.put(TMP_FRACTION, TMP_COLOR);
}
}
// Clear the original FRACTION_LIST and COLOR_LIST
FRACTION_LIST.clear();
COLOR_LIST.clear();
return FRACTION_COLORS;
}
/**
* With the START_COLOR at the beginning and the DESTINATION_COLOR at the
* end of the given RANGE the method will calculate and return the color
* that equals the given VALUE. e.g. a START_COLOR of BLACK (R:0, G:0, B:0,
* A:255) and a DESTINATION_COLOR of WHITE(R:255, G:255, B:255, A:255) with
* a given RANGE of 100 and a given VALUE of 50 will return the color that
* is exactly in the middle of the gradient between black and white which is
* gray(R:128, G:128, B:128, A:255) So this method is really useful to
* calculate colors in gradients between two given colors.
*
* #param START_COLOR
* #param DESTINATION_COLOR
* #param RANGE
* #param VALUE
* #return Color calculated from a range of values by given value
*/
public java.awt.Color getColorFromFraction(final java.awt.Color START_COLOR, final java.awt.Color DESTINATION_COLOR, final int RANGE, final int VALUE) {
final float SOURCE_RED = START_COLOR.getRed() * INT_TO_FLOAT_CONST;
final float SOURCE_GREEN = START_COLOR.getGreen() * INT_TO_FLOAT_CONST;
final float SOURCE_BLUE = START_COLOR.getBlue() * INT_TO_FLOAT_CONST;
final float SOURCE_ALPHA = START_COLOR.getAlpha() * INT_TO_FLOAT_CONST;
final float DESTINATION_RED = DESTINATION_COLOR.getRed() * INT_TO_FLOAT_CONST;
final float DESTINATION_GREEN = DESTINATION_COLOR.getGreen() * INT_TO_FLOAT_CONST;
final float DESTINATION_BLUE = DESTINATION_COLOR.getBlue() * INT_TO_FLOAT_CONST;
final float DESTINATION_ALPHA = DESTINATION_COLOR.getAlpha() * INT_TO_FLOAT_CONST;
final float RED_DELTA = DESTINATION_RED - SOURCE_RED;
final float GREEN_DELTA = DESTINATION_GREEN - SOURCE_GREEN;
final float BLUE_DELTA = DESTINATION_BLUE - SOURCE_BLUE;
final float ALPHA_DELTA = DESTINATION_ALPHA - SOURCE_ALPHA;
final float RED_FRACTION = RED_DELTA / RANGE;
final float GREEN_FRACTION = GREEN_DELTA / RANGE;
final float BLUE_FRACTION = BLUE_DELTA / RANGE;
final float ALPHA_FRACTION = ALPHA_DELTA / RANGE;
//System.out.println(DISTANCE + " " + CURRENT_FRACTION);
return new java.awt.Color(SOURCE_RED + RED_FRACTION * VALUE, SOURCE_GREEN + GREEN_FRACTION * VALUE, SOURCE_BLUE + BLUE_FRACTION * VALUE, SOURCE_ALPHA + ALPHA_FRACTION * VALUE);
}
#Override
public java.awt.PaintContext createContext(final java.awt.image.ColorModel COLOR_MODEL, final java.awt.Rectangle DEVICE_BOUNDS, final java.awt.geom.Rectangle2D USER_BOUNDS, final java.awt.geom.AffineTransform TRANSFORM, final java.awt.RenderingHints HINTS) {
final java.awt.geom.Point2D TRANSFORMED_CENTER = TRANSFORM.transform(CENTER, null);
return new ConicalGradientPaintContext(TRANSFORMED_CENTER);
}
#Override
public int getTransparency() {
return java.awt.Transparency.TRANSLUCENT;
}
private final class ConicalGradientPaintContext implements java.awt.PaintContext {
final private java.awt.geom.Point2D CENTER;
public ConicalGradientPaintContext(final java.awt.geom.Point2D CENTER) {
this.CENTER = new java.awt.geom.Point2D.Double(CENTER.getX(), CENTER.getY());
}
#Override
public void dispose() {
}
#Override
public java.awt.image.ColorModel getColorModel() {
return java.awt.image.ColorModel.getRGBdefault();
}
#Override
public java.awt.image.Raster getRaster(final int X, final int Y, final int TILE_WIDTH, final int TILE_HEIGHT) {
final double ROTATION_CENTER_X = -X + CENTER.getX();
final double ROTATION_CENTER_Y = -Y + CENTER.getY();
final int MAX = FRACTION_ANGLES.length;
// Create raster for given colormodel
final java.awt.image.WritableRaster RASTER = getColorModel().createCompatibleWritableRaster(TILE_WIDTH, TILE_HEIGHT);
// Create data array with place for red, green, blue and alpha values
int[] data = new int[(TILE_WIDTH * TILE_HEIGHT * 4)];
double dx;
double dy;
double distance;
double angle;
double currentRed = 0;
double currentGreen = 0;
double currentBlue = 0;
double currentAlpha = 0;
for (int py = 0; py < TILE_HEIGHT; py++) {
for (int px = 0; px < TILE_WIDTH; px++) {
// Calculate the distance between the current position and the rotation angle
dx = px - ROTATION_CENTER_X;
dy = py - ROTATION_CENTER_Y;
distance = Math.sqrt(dx * dx + dy * dy);
// Avoid division by zero
if (distance == 0) {
distance = 1;
}
// 0 degree on top
angle = Math.abs(Math.toDegrees(Math.acos(dx / distance)));
if (dx >= 0 && dy <= 0) {
angle = 90.0 - angle;
} else if (dx >= 0 && dy >= 0) {
angle += 90.0;
} else if (dx <= 0 && dy >= 0) {
angle += 90.0;
} else if (dx <= 0 && dy <= 0) {
angle = 450.0 - angle;
}
// Check for each angle in fractionAngles array
for (int i = 0; i < (MAX - 1); i++) {
if ((angle >= FRACTION_ANGLES[i])) {
currentRed = COLORS[i].getRed() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * RED_STEP_LOOKUP[i];
currentGreen = COLORS[i].getGreen() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * GREEN_STEP_LOOKUP[i];
currentBlue = COLORS[i].getBlue() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * BLUE_STEP_LOOKUP[i];
currentAlpha = COLORS[i].getAlpha() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * ALPHA_STEP_LOOKUP[i];
continue;
}
}
// Fill data array with calculated color values
final int BASE = (py * TILE_WIDTH + px) * 4;
data[BASE + 0] = (int) (currentRed * 255);
data[BASE + 1] = (int) (currentGreen * 255);
data[BASE + 2] = (int) (currentBlue * 255);
data[BASE + 3] = (int) (currentAlpha * 255);
}
}
// Fill the raster with the data
RASTER.setPixels(0, 0, TILE_WIDTH, TILE_HEIGHT, data);
return RASTER;
}
}
}
}