We are making a six-player multi-player game for school and I am responsible for a networking class. The game incorporates Slick 2D. In the game there are entities (code shown below)
package Game;
import org.newdawn.slick.*;
public abstract class Entity {
double maxHealth, health, speed, damage, width, height, locationX, locationY;
Animation standing, walkingLeft, walkingRight, attacking, jumping, current;
long pastTime = 0;
public boolean isReadyToAttack(long delta) {
if(pastTime < 2 * 500) { //multiply by 1000 to get milliseconds
pastTime += delta;
return false;
}else{
pastTime = 0;
return true;
}
}
public void setMaxHealth(double d){
maxHealth = d;
}
public void setHealth(double d){
health = d;
}
public void setSpeed(double x){
speed = x;
}
public void setDamage(double x){
damage = x;
}
public void setLocation(double x, double y){
locationX = x;
locationY = y;
}
public void setSprites(Animation s, Animation r){
standing = s;
walkingRight = r;
}
public void setCurrent(Animation a){
current = a;
}
public double getMaxHealth(){
return maxHealth;
}
public double getHealth(){
return health;
}
public double getSpeed(){
return speed;
}
public double getDamage(){
return damage;
}
public double getLocationX(){
return locationX;
}
public double getLocationY(){
return locationY;
}
public Animation getStanding(){
return standing;
}
public Animation getRight(){
return walkingRight;
}
public Animation getCurrent(){
return current;
}
public abstract double getAttackRange();
}
The entities class is also extended by the Heroes class which is further extended by the Ares class. Ares is one of the six characters in our game and when you start the game you can choose one of the six in the start menu.
Here is the Hero class:
package Game;
import org.newdawn.slick.Animation;
public abstract class Hero extends Entity{
static double gravity = 0.1;
double velocity;
int level;
boolean disabled, grounded = true;
public void setLevel(int i){
level = i;
}
public void setVelocity(double d){
velocity = d;
}
public void setDisabled(boolean b){
disabled = b;
}
public void setGrounded(boolean b){
grounded = b;
}
public int getLevel(){
return level;
}
public double getVelocity(){
return velocity;
}
public boolean getDisabled(){
return disabled;
}
public boolean getGrounded(){
return grounded;
}
public double getGravity(){
return gravity;
}
public boolean isAlive(){
if(getHealth() > 0)
return true;
return false;
}
public void attack(Entity gettingAttacked, Hero attacking) {
gettingAttacked.setHealth(gettingAttacked.getHealth() - attacking.getDamage());
}
public boolean isReadyToRegenerate(long delta) {
if(pastTime < 1 * 1000){
pastTime += delta;
return false;
}
else{
pastTime = 0;
return true;
}
}
public static void regenerate(Hero h, long delta){
if(h.getHealth() >= 0 && h.getHealth() < h.getMaxHealth())
if(h.isReadyToRegenerate(delta))
h.setHealth(h.getHealth() + (h.getMaxHealth()) * 0.01);
}
public abstract void levelUp();
}
Here is the Ares class:
package Game;
import org.newdawn.slick.Animation;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
public class Ares extends Hero {
public Ares() throws SlickException {
setHealth(500);
setSpeed(0.4);
setDamage(70);
setLocation(850,625);
setSprites(new Animation(new SpriteSheet("res/Ares.png", 191, 275), 200), new Animation(new SpriteSheet("res/AresWalk.png", 191, 275), 200));
}
public void levelUp() {
level += 1;
health *= 1.1;
damage *= 1.1;
}
public double getAttackRange() {
return 250;
}
public double maxHealth() {
return 500;
}
}
Here is the Battle code, which contains the main gameplay code, the code is currently uncompleted and only has movement:
package Game;
import java.util.ArrayList;
import org.lwjgl.input.*;
import org.newdawn.slick.*;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.state.*;
public class Battle extends BasicGameState {
private ArrayList<Entity> entities = new ArrayList<Entity>();
private Hero player;
private Image background;
private int midScreen, ground;
public void init(GameContainer gc, StateBasedGame sbg) throws SlickException{
gc.setVSync(true);
player = new Ares();
player.setCurrent(player.getStanding());
entities.add(player);
background = new Image("res/Background.png");
midScreen = 800;
ground = 625;
}
public void render(GameContainer gc, StateBasedGame sbg, Graphics g) throws SlickException{
g.translate((float)-player.getLocationX() + midScreen, (float)-player.getLocationY() + ground);
background.draw(0, -920);
for(int i = 0; i < entities.size(); i++)
entities.get(i).getCurrent().draw((float)entities.get(i).getLocationX(), (float)entities.get(i).getLocationY());
}
public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException {
player.getCurrent().update(delta); // ensures
Input kb = gc.getInput();
if (kb.isKeyDown(Input.KEY_SPACE) && player.getGrounded()) {
player.setGrounded(false);
player.setVelocity(7);
player.setLocation(player.getLocationX(), player.getLocationY() - player.getVelocity());
player.setCurrent(player.getStanding());
}
else if (kb.isKeyDown(Input.KEY_A) && player.getLocationX() > 800) {
player.setLocation(player.getLocationX() - player.getSpeed() * delta, player.getLocationY());
if(player.grounded)
player.setCurrent(player.getRight());
}
else if (kb.isKeyDown(Input.KEY_D) && player.getLocationX() < 8580) {
player.setLocation(player.getLocationX() + player.getSpeed() * delta, player.getLocationY());
if(player.grounded)
player.setCurrent(player.getRight());
}
else
player.setCurrent(player.getStanding());
if (Math.abs(player.getLocationY() - ground) < .01)
player.setGrounded(true);
if (player.getGrounded() == false) {
player.setVelocity(player.getVelocity() - player.getGravity());
player.setLocation(player.getLocationX(), player.getLocationY() - player.getVelocity());
}
//else if (kb.isKeyDown(Input.KEY_A)) //&& (!(aresRect.intersects(turretRect))))
//{
// x -= 0.3 * delta;
//}
//if(kb.isKeyDown(Input.KEY_S))
// y += 0.3 * delta;
//else if (kb.isKeyDown(Input.KEY_W)) //&& (!(aresRect.intersects(turretRect))))
// y -= 0.3 * delta;
}
public int getID(){
return 1;
}
}
What I am having difficulty with is creating a server and client that gets the input from the players, such as location, who chose which player, etc. I'm wondering that if in Battle, when Player objects are made, how would I be able to get location and other specific attributes of the objects and give them to the other computers?
So far, the furthest I have gotten with networking code is a chat server, but I am confused as to how to apply parts of that information into the game to include getting input from a user. I have looked at a bunch of tutorials on creating a multi-player network, but I'm still confused. As for my experience, I've had around 2 years experience coding (only one with Java) but this is my first time working with networking. I experimented with a UDP connection, here is my Server:
package net;
import java.io.*;
import java.net.*;
import java.util.*;
import main.Game;
import packets.*;
import packets.Packet.PacketTypes;
public class GameServer extends Thread{
private DatagramSocket socket;
private Game game;
private List<PlayerMP> connectedPlayers = new ArrayList <PlayerMP>();
public GameServer(Game game){
this.game = game;
try {
socket = new DatagramSocket(7777);
} catch (SocketException e) {
e.printStackTrace();
}
}
public void run(){
while(true){
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
try {
socket.receive(packet);
} catch(IOException e) {
e.printStackTrace();
}
parsePacket(packet.getData(), packet.getAddress(), packet.getPort());
}
}
private void parsePacket(byte[] data, InetAddress address, int port) {
String message = new String(data).trim();
PacketTypes type = Packet.lookupPacket(message.substring(0, 2));
Packet packet = null;
switch(type) {
default :
case INVALID:
break;
case LOGIN:
packet = new Packet00Login(data);
System.out.println("[" + address.getHostAddress() + ":" + port + "] " + ((Packet00Login) packet).getUsername() +
" has connected.");
PlayerMP player = new PlayerMP(address, port);
this.addConnection(player, ((Packet00Login)packet));
this.connectedPlayers.add(player);
break;
case DISCONNECT:
break;
case MOVE:
packet = new Packet02Move(data);
System.out.println(((Packet02Move)packet).getName() +"has moved to" + ((Packet02Move)packet).getX() + "," + ((Packet02Move)packet).getY());
this.handleMove((Packet02Move)packet);
}
}
private void handleMove(Packet02Move packet) {
if (getPlayerMP(packet.getUsername()) != null){
int index = getPlayerMPIndex(getName());
this.connectedPlayers.get(index).locationY = packet.getX();
this.connectedPlayers.get(index).locationX = packet.getX();
packet.writeData(this);
}
}
public void addConnection(PlayerMP player, Packet00Login packet) {
boolean alreadyConnected = false;
for(PlayerMP p : this.connectedPlayers) {
if (player.getUsername().equalsIgnoreCase(p.getUsername())) {
if(p.ipAddress == null){
p.ipAddress = player.ipAddress;
}
if(p.port == -1) {
p.port = player.port;
}
alreadyConnected = true;
} else {
sendData(packet.getData(), p.ipAddress, p.port);
packet = new Packet00Login(p.getUsername());
sendData(packet.getData(), player.ipAddress, player.port);
}
if (alreadyConnected){
this.connectedPlayers.add(player);
packet.writeData(this);
}
}
}
public void sendData(byte[] data, InetAddress ipAddress, int port) {
DatagramPacket packet = new DatagramPacket(data, data.length, ipAddress, port);
try {
socket.send(packet);
} catch(IOException e) {
e.printStackTrace();
}
}
public void sendDataToAllClients(byte[] data) {
for(PlayerMP p : connectedPlayers)
sendData(data, p.ipAddress, p.port);
}
}
I could not figure out how to get much further than sending a message back and forth with a TCP connection:
import java.io.*;
import java.net.*;
public class Server {
private static ServerSocket serverSocket;
private static Socket socket;
private static DataOutputStream out;
private static Users[] user = new Users[6];
private static DataInputStream in;
public static void main(String[] args) throws Exception{
System.out.println("Starting server");
serverSocket = new ServerSocket(0);
System.out.println("Server Started");
while(true){
socket = serverSocket.accept();
for (int i = 0; i < 6; i++){
System.out.println("Connection from: " + socket.getInetAddress());
out = new DataOutputStream(socket.getOutputStream());
in = new DataInputStream(socket.getInputStream());
if (user[i] == null){
user[i] = new Users(out, in, user);
Thread thread = new Thread();
thread.start();
break;
}
}
}
}
}
class Users implements Runnable{
private DataOutputStream out;
private DataInputStream in;
private Users[] user = new Users[6];
public Users(DataOutputStream fromOut, DataInputStream goingIn, Users[] userArr){
out = fromOut;
in = goingIn;
user = userArr;
}
public void run(){
while(true){
try{
String message = in.readUTF();
for (int i = 0; i < 6; i++){
if (user[i] != null)
user[i].out.writeUTF(message);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
I would like to figure this out. I am not even completely sure where to start or whether or not I am on the right track.
Note: I am coding in Java, and trying to use a TCP connection to code this. I am open to UDP if you think it may be easier.
Related
I have three classes that I am working with.
The first class opens the GPS and reads the data.
The second parses the data.
The third is going to be the parsed data in a GUI.
My question is how do I pass the global variable gpsData to classC from NMEA?
I am using code that I found online for the gps data.
It looks like the NMEA class is dependent on serialGps and serialGps is dependent on NMEA.
I need to pass the inputLine which sets the global variable "gpsData" contained in the NMEA class to a third class which will be my GUI.
This is the serialGps class
package gpsData;
import com.fazecast.jSerialComm.SerialPort;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
#SuppressWarnings({"unused", "WeakerAccess"})
public class SerialGps {
public interface StateListener {
void onGpsStateUpdated(NMEA.GpsState state);
}
private String portName;
private int baudRate;
private boolean isRunning = false;
private List<StateListener> stateListeners = new ArrayList<>();
public SerialGps(String portName, int baudRate) {
this.portName = portName;
this.baudRate = baudRate;
}
public SerialGps(String portName) {
this(portName, 4800);
}
public void addStateListener(StateListener stateListener) {
stateListeners.add(stateListener);
}
public void start() {
NMEA nmea = new NMEA();
SerialPort[] serialPorts = SerialPort.getCommPorts();
SerialPort gpsPort = null;
for (SerialPort serialPort : serialPorts) {
if (serialPort.getDescriptivePortName().toLowerCase().contains("serial")) {
gpsPort = serialPort;
}
}
if (gpsPort == null) {
System.out.println("failed to find gps serial port");
return;
}
System.out.println("using serial port: " + gpsPort.getDescriptivePortName());
gpsPort.setBaudRate(4800);
gpsPort.openPort();
InputStream inStream = gpsPort.getInputStream();
if (inStream == null) {
System.out.println("opening port " + gpsPort.getDescriptivePortName() + " failed");
return;
}
Thread thread = new Thread(() -> {
String inputLine = "";
isRunning = true;
while (isRunning) {
try {
if (inStream.available() > 0) {
char b = (char) inStream.read();
if (b == '\n') {
NMEA.GpsState gpsState = nmea.getUpdatedStatus(inputLine);
updateState(gpsState);
inputLine = "";
} else {
inputLine += b;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
public void stop() throws InterruptedException {
isRunning = false;
}
private void updateState(NMEA.GpsState gpsState) {
stateListeners.forEach(stateListener -> stateListener.onGpsStateUpdated(gpsState));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
SerialGps app = new SerialGps("/dev/cu.usbserial", 4800);
app.start();
}
}
The NMEA class:
package gpsData;
import java.io.IOException;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class NMEA {
public static String gpsData = null;
interface SentenceParser {
boolean parse(String[] tokens, GpsState position);
}
private class GPGGA implements SentenceParser {
public boolean parse(String[] tokens, GpsState position) {
position.time = Float.parseFloat(tokens[1]);
position.lat = Latitude2Decimal(tokens[2], tokens[3]);
position.lon = Longitude2Decimal(tokens[4], tokens[5]);
position.quality = Integer.parseInt(tokens[6]);
position.altitude = Float.parseFloat(tokens[9]);
return true;
}
}
private class GPGGL implements SentenceParser {
public boolean parse(String[] tokens, GpsState position) {
position.lat = Latitude2Decimal(tokens[1], tokens[2]);
position.lon = Longitude2Decimal(tokens[3], tokens[4]);
position.time = Float.parseFloat(tokens[5]);
return true;
}
}
private class GPRMC implements SentenceParser {
public boolean parse(String[] tokens, GpsState position) {
position.time = Float.parseFloat(tokens[1]);
position.lat = Latitude2Decimal(tokens[3], tokens[4]);
position.lon = Longitude2Decimal(tokens[5], tokens[6]);
position.velocity = Float.parseFloat(tokens[7]);
position.dir = Float.parseFloat(tokens[8]);
return true;
}
}
private class GPVTG implements SentenceParser {
public boolean parse(String[] tokens, GpsState position) {
position.dir = Float.parseFloat(tokens[3]);
return true;
}
}
private class GPRMZ implements SentenceParser {
public boolean parse(String[] tokens, GpsState position) {
position.altitude = Float.parseFloat(tokens[1]);
return true;
}
}
private static float Latitude2Decimal(String lat, String NS) {
float result = (Float.parseFloat(lat.substring(2)) / 60.0f) + Float.parseFloat(lat.substring(0, 2));
if (NS.startsWith("S")) {
result *= -1.0f;
}
return result;
}
private static float Longitude2Decimal(String lon, String WE) {
float med = (Float.parseFloat(lon.substring(3)) / 60.0f) + Float.parseFloat(lon.substring(0, 3));
if (WE.startsWith("W")) {
med *= -1.0f;
}
return med;
}
#SuppressWarnings("WeakerAccess")
public class GpsState {
public float time = 0.0f;
public float lat = 0.0f;
public float lon = 0.0f;
public boolean hasFix = false;
public int quality = 0;
public float dir = 0.0f;
public float altitude = 0.0f;
public float velocity = 0.0f;
void updatefix() {
hasFix = quality > 0;
}
public String toString() {
return String.format("POSITION: lat: %f, lon: %f, time: %f, Q: %d, dir: %f, alt: %f, vel: %f", lat, lon, time, quality, dir, altitude, velocity);
}
}
private GpsState position = new GpsState();
private static final Map<String, SentenceParser> sentenceParsers = new HashMap<>();
NMEA() {
sentenceParsers.put("GPGGA", new GPGGA());
sentenceParsers.put("GPGGL", new GPGGL());
sentenceParsers.put("GPRMC", new GPRMC());
sentenceParsers.put("GPRMZ", new GPRMZ());
sentenceParsers.put("GPVTG", new GPVTG());
}
GpsState getUpdatedStatus(String inputLine) {
if (inputLine.startsWith("$")) {
//System.out.println(inputLine);
String nmea = inputLine.substring(1);
String[] tokens = nmea.split(",");
String type = tokens[0];
this.gpsData = inputLine;
//System.out.println(gpsData);
if (sentenceParsers.containsKey(type)) {
try {
sentenceParsers.get(type).parse(tokens, position);
} catch (Exception e) {}
}
position.updatefix();
//System.out.println(gpsData);
}
return position;
}
public static String passer() {
String gpsData2 = gpsData;
return gpsData2;
}
}
classC
package gpsData;
public class classC {
static String inputLinet = null;
public void print() {
inputLinet = NMEA.gpsData;
if (inputLinet.startsWith("$")) {
System.out.print(inputLinet);
}
}
}
I am programming a little drum sequencer, a roland tr808 knockoff with 16 steps/measure and 16 instruments(=drum samples). User has a gui where he can thus create a 16x16 pattern.
However, if a sample is played more than once in quick succession, it often just gets played once. Say, I got a bassdrum on step 1, 5, 9 and 13 and tempo's 130BPM, it sometimes plays just the bd on 1 and 9, and sometimes the ones on 5 and/or 13 as well. If the sample is very short or the tempo is slow, the chances are higher that every step in the pattern is played correctly. So I assume that the audio line doesn't like it when I try to play a sample again when it hasn't finished yet.
But actually I thought I'd taken that into account in my code. I'd be really thankful if someone told me what's wrong with my code.
Here's my complete code as suggested by Andrew Thompson, modified so that it takes some samples from the internet. Loading them takes a bit, though. the part causing the issue is probably the play() method in the Instrument class:
package testbox;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.sound.sampled.*;
public class boomboxtest {
public static void main(String[] args) {
Sequencer seq = new Sequencer();
//bassdrum
seq.toggleInstrument(0,0);
seq.toggleInstrument(0,4);
seq.toggleInstrument(0,8);
seq.toggleInstrument(0,12);
//snare
seq.toggleInstrument(1,4);
seq.toggleInstrument(1,12);
//Hihat
seq.toggleInstrument(2, 2);
seq.toggleInstrument(2, 6);
seq.toggleInstrument(2, 10);
//Bongo
seq.toggleInstrument(3, 6);
seq.toggleInstrument(3, 10);
seq.setTempo(130);
seq.play();
}
}
class Sequencer {
private Mixer mixer;
private List<SequencerListener> listeners = new ArrayList<SequencerListener>();
public static final int INSTR_COUNT = 4;
private int tempo_bpm = 120;
private ExecutorService executor;
private int current_step = 0;
private int current_max_step = 16;
private boolean[][] pattern = new boolean[32][INSTR_COUNT];
private ArrayList<Instrument> instruments;
Line[] lines = new Line[16];
private SequencerEngine seq;
private String[] filenames = {"http://www.canadianmusicartists.com/sample/kick_02.wav", "http://www.canadianmusicartists.com/sample/snare01.wav", "http://www.canadianmusicartists.com/sample/H_closedhat_01.wav", "http://www.canadianmusicartists.com/sample/bongo01.wav"};
public Sequencer() {
seq = new SequencerEngine();
try{
Mixer.Info[] mixerInfo = AudioSystem.getMixerInfo();
mixer = AudioSystem.getMixer(mixerInfo[0]);
} catch (Exception e) {e.printStackTrace();}
instruments = new ArrayList<Instrument>(INSTR_COUNT);
for (int i = 0; i < INSTR_COUNT; i++) {
System.out.println("Loading instrument " + i);
Instrument instr = new Instrument(filenames[i], mixer);
instruments.add(instr);
lines[i] = instr.getLine();
}
syncMixer();
executor = Executors.newCachedThreadPool();
executor.submit(seq);
}
public void syncMixer() {
if (mixer.isSynchronizationSupported(lines, false)) {
mixer.synchronize(lines, false);
} else {
System.out.println("No hay synchronisado");
}
}
public boolean isPlaying() {
return seq.getRunning();
}
public boolean toggleInstrument (int instrument, int beat) {
pattern[beat][instrument] = !pattern[beat][instrument];
return pattern[beat][instrument];
}
public void play() {
seq.toggleRun(true);
}
public void pause() {
seq.toggleRun(false);
}
public void stop() {
pause();
setCurrent_step(0);
}
public int getTempo() {
return tempo_bpm;
}
public void setTempo(int tempo) {
if (tempo < 30) {
tempo = 30;
} else if (tempo > 200) {
tempo = 200;
} else {
this.tempo_bpm = tempo;
}
}
public int getCurrent_step() {
return current_step;
}
public void setCurrent_step(int current_step) {
this.current_step = current_step;
}
public boolean[][] getPattern() {
return pattern;
}
public void kill() {
seq.kill();
executor.shutdownNow();
}
public void addListener(SequencerListener toAdd) {
listeners.add(toAdd);
}
public class SequencerEngine implements Runnable{
private boolean running;
private boolean alive = true;
public void run() {
while( getAlive()) {
while (getRunning()) {
if (current_step >= current_max_step) {
current_step = 0;
}
for (; current_step < current_max_step ; current_step++) {
stepListen();
if(!getRunning()) {
break;
}
long time = System.currentTimeMillis();
long steptime = 60000/(4*tempo_bpm);
for (int k = 0; k < INSTR_COUNT; k++) {
if (pattern[current_step][k]) {
instruments.get(k).play();
}
}
while((System.currentTimeMillis()-time) < steptime) {}
}
}
}
}
public void stepListen() {
for (SequencerListener sl : listeners) {
sl.stepEvent(current_step);
}
}
public boolean getRunning() {
return running;
}
public boolean getAlive() {
return alive;
}
public void toggleRun(boolean toggle) {
running = toggle;
}
public void kill() {
alive = false;
}
}
}
class Instrument {
private String name;
private File soundFile;
private AudioInputStream stream;
private AudioFormat format;
private DataLine.Info info;
private Clip clip;
private Mixer mixer;
public Instrument(String filename, Mixer mixer ) {
this.name = filename;
try {
//soundFile = new File("sounds/" + filename);
URL url = new URL(filename);
this.mixer = mixer;
//stream = AudioSystem.getAudioInputStream(soundFile);
stream = AudioSystem.getAudioInputStream(url);
format = stream.getFormat();
info = new DataLine.Info(Clip.class, format);
clip = (Clip) mixer.getLine(info);
clip.open(stream);
}
catch (Exception e) {
e.printStackTrace();
}
}
public void play() {
clip.stop();
clip.setFramePosition(0);
clip.start();
}
public Line getLine() {
return clip;
}
}
interface SequencerListener {
void stepEvent(int current_step);
}
The samples are of rather questionable quality, but especially the bassdrum sample illustrates my problem really good.
Within the scope of a paper I am writing at high school I chose to make my own audio-file-to-spectrogram-converter from scratch in order to create landscapes out of these spectrograms.
I already do have my implementation of an FFT and of using that to make a heightmap, a spectrogram. But I often get weird artifacts in the form of vertical stripes when the frequencies get dense, as you can see in the image below.
The example is right at the beginning with a window length of 2048 and on a log^2-scale. The FFT I am using is flawless, I've already compared it to others and they produce the same result.
This is the function which transforms the amplitudes into frequencies and stores them in a 2D-array:
private void transform(int from, int until) {
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++)
chunk[j] = data[0][i*n+j+start];
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
map[i][j] = val;
}
}
}
Now my Question: Where do these vertical stripes come from and how do I get rid of them?
I currently don't employ a window function and every calculation is stringed to one another, which means there is no overlapping. It is the simplest way you can think of making a spectrogram. Could it help introducing a window function or doing each calculation independent of whether the frame was already involved in a previous calculation, that is to say overlapping the frame-windows?
Also, what other ways are there to improve on my basic approach in order to get a better result?
This is the whole class. I feed it the data and all the necessary information from an audio file:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import org.jtransforms.fft.DoubleFFT_1D;
public class Heightmap extends JFrame implements WindowListener{
public static final int LOG_SCALE = 0;
public static final int LOG_SQUARE_SCALE = 1;
public static final int SQUARE_SCALE = 2;
public static final int LINEAR_SCALE = 3;
private BufferedImage heightmap;
private FileDialog chooser;
private JMenuBar menuBar;
private JMenu fileMenu;
private JMenuItem save, close;
private DoubleFFT_1D fft;
private int[][] data;
private double[][] map;
private double[] chunk;
private int width, height, n, start, scale;
private String name;
private boolean inactive;
public Heightmap(int[][] data, int resolution, int start,
int width, int height, int scale, String name) {
this.data = data;
this.n = resolution;
this.start = start;
this.width = width;
this.height = height;
this.scale = scale;
this.name = name;
fft = new DoubleFFT_1D(n);
map = new double[width][height];
heightmap = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
chunk = new double[n];
System.out.println("Starting transformation...");
long time;
time = System.currentTimeMillis();
transform();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for calculation: "+time+" ms");
time = System.currentTimeMillis();
makeHeightmap();
initComponents();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for drawing heightmap: "+time+" ms");
}
private void initComponents() {
this.setSize(width, height);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setTitle(name);
createMenuBar();
chooser = new FileDialog(this, "Choose a directory", FileDialog.SAVE);
chooser.setDirectory("/Users/<user>/Desktop");
this.addMouseListener(new HeightmapMouseListener());
this.addKeyListener(new HeightmapKeyListener());
this.addWindowListener(this);
this.setVisible(true);
}
private void createMenuBar() {
menuBar = new JMenuBar();
fileMenu = new JMenu();
fileMenu.setText("File");
save = new JMenuItem("Save...", KeyEvent.VK_S);
save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.META_DOWN_MASK));
save.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
chooser.setVisible(true);
String fileName = chooser.getFile();
String dir = chooser.getDirectory();
chooser.setDirectory(dir);
if(fileName != null) {
try {
File outputfile = new File(dir + fileName + ".png");
ImageIO.write(heightmap, "png", outputfile);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Saved "+fileName+".png to "+dir);
}
}
});
close = new JMenuItem("Close", KeyEvent.VK_C);
close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK));
close.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
}
});
fileMenu.add(save);
fileMenu.addSeparator();
fileMenu.add(close);
menuBar.add(fileMenu);
this.setJMenuBar(menuBar);
}
public void paint(Graphics g) {
g.drawImage(heightmap, 0, 0, null);
}
private void transform() {
transform(0, width);
}
private void transform(int from, int until) {
double max = Double.MIN_VALUE;
double min = Double.MAX_VALUE;
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++) {
chunk[j] = data[0][i*n+j+start];
}
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
if(val > max)
max = val;
if(val < min)
min = val;
map[i][j] = val;
}
if(min != 0) {
step = max/(max-min);
for(int j=0; j<height; j++)
map[i][j] = (map[i][j]-min)*step;
}
}
}
/*
* Paints heightmap into the BufferedImage
*/
private void makeHeightmap() {
double max = 0;
switch(scale) {
case LOG_SCALE: max = Math.log(findMax(map)+1); break;
case LOG_SQUARE_SCALE: max = Math.pow(Math.log(findMax(map)+1), 2); break;
case SQUARE_SCALE: max = Math.sqrt(findMax(map)); break;
case LINEAR_SCALE: max = findMax(map); break;
default: max = Math.pow(Math.log(findMax(map)+1), 2); break;
}
double stepsize = 255.0/max;
int val, rgb;
for(int x=0; x<width; x++)
for(int y=0; y<height; y++) {
switch(scale) {
case LOG_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); break;
case LOG_SQUARE_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
case SQUARE_SCALE: val = (int) (Math.sqrt(map[x][y])*stepsize); break;
case LINEAR_SCALE: val = (int) (map[x][y]*stepsize); break;
default: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
}
rgb = 255<<24 | val<<16 | val<<8 | val;
heightmap.setRGB(x, height-y-1, rgb);
}
}
private double findMax(double[][] data) {
double max = 0;
for(double[] val1: data)
for(double d: val1)
if(d > max)
max = d;
return max;
}
private class HeightmapKeyListener implements KeyListener {
boolean busy = false;
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_RIGHT && !busy && start < data[0].length-width*n) {
busy = true;
for(int x=0; x<width-1; x++)
map[x] = map[x+1].clone();
start += n;
transform(width-1, width);
makeHeightmap();
repaint();
busy = false;
}
else if(e.getKeyCode() == KeyEvent.VK_LEFT && !busy && start > 0) {
busy = true;
for(int x=width-1; x>0; x--)
map[x] = map[x-1];
start -= n;
transform(0, 1);
makeHeightmap();
repaint();
busy = false;
}
}
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) { }
}
private class HeightmapMouseListener implements MouseListener {
public void mouseClicked(MouseEvent e) {
if(inactive) {
inactive = false;
return;
}
long time = System.currentTimeMillis();
int posX = e.getX();
int diff = posX - width/2; //difference between old and new center in pixels
int oldStart = start;
start = start + diff*n;
if(start < 0) start = 0;
int maxFrame = data[0].length-width*n;
if(start > maxFrame) start = maxFrame;
if(start == oldStart) return;
System.out.println("Changing center...");
int absDiff = Math.abs(diff);
if(start < oldStart) { //shift the start backward, recalculate the start
for(int x=width-1; x>=absDiff; x--)
map[x] = map[x-absDiff].clone();
transform(0, absDiff);
}
else if(start > oldStart) { //shift the back forward, recalculate the back
for(int x=0; x<width-absDiff; x++)
map[x] = map[x+absDiff].clone();
transform(width-absDiff, width);
}
makeHeightmap();
repaint();
System.out.println("Time taken: "+(System.currentTimeMillis()-time)+" ms");
}
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
}
public void windowActivated(WindowEvent arg0) { }
public void windowClosed(WindowEvent arg0) { }
public void windowClosing(WindowEvent arg0) { }
public void windowDeactivated(WindowEvent arg0) {
inactive = true;
}
public void windowDeiconified(WindowEvent arg0) { }
public void windowIconified(WindowEvent arg0) { }
public void windowOpened(WindowEvent arg0) { }
}
EDIT:
Implementing a window function improved the result drastically. I really didn't understand what a window function would do and therefore underestimated its effect.
However, after doing so I tried mapping a cosine wave with a frequency of 10kHz which (again) produced some strange artifacts:
What could be the cause of this one? I implemented a overflow protection by clipping everything under 0 to 0 and over 255 to 255 with no change whatsoever.
This type of artifact can be due to overflow or otherwise exceeding parameter bounds before or in your color mapping function, or perhaps with some function (log?) returning NaN values. You might be able to find this by putting in some asserts for out-of-range or illegal values.
Ok so I get this null Pointer exception and neither myself nor my lecturer can figure it out. (he reckons its bad logic and I'm inclined to agree)
I'm making my own 3d object and rendering it with direct world to screenpace co-ords for x and y. This gives me exactly what I want for the excercise and I am able to refine the numbers to get smooth rendering without dropping many frames on my course computer (meaning I cant reproduce the null pointer for my lecturer) but as soon as I run it on beast machine at home the code frames appear to be going much faster, and the cube I'm rendering flashes on and off repeatedly while spitting the null pointer exception to the console(even when I manipulate the numbers to slow it all down). It never crashes though.
Here are the classes that are being used.
Viewport.java(attatched to a JFrame)
package com.my3d.graphics;
import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import com.my3d.geometry.Cube;
import com.my3d.utils.Props;
public class Viewport extends Canvas implements Runnable
{
private static final long serialVersionUID = 1L;
private static final String DEFAULT_NAME = "Viewport";
private static int viewportCount = 0;
private static final BasicStroke stroke = new BasicStroke(0.5f);
private Cube cube;
private boolean running;
private String name;
private int number;
public Viewport()
{
super();
//name the Viewport
if(viewportCount < 10)
setThisName(DEFAULT_NAME +"_0" +viewportCount);
else
setThisName(DEFAULT_NAME +"_" +viewportCount);
//set Identity
setNumber(viewportCount);
//increment the Viewport counter
viewportCount++;
cube = new Cube();
cube.printCubeVertices();
cube.rotateZ(20);
cube.printCubeVertices();
cube.rotateX(20);
cube.printCubeVertices();
running = true;
}
public Viewport(String n)
{
setThisName(n);
viewportCount++;
cube = new Cube();
//cube.printCubeVertices();
//cube.rotateZ(20);
// cube.printCubeVertices();
//cube.rotateX(20);
//cube.printCubeVertices();
running = true;
}
public void tick()
{
//cube.rotateY(0.0001);
//cube.rotateX(0.0001);
repaint();
tock();
}
private void tock()
{
tick();
}
public void setThisName(String n)
{
name = n;
}
private void setNumber(int n)
{
number = n;
}
public Cube getCube()
{
return cube;
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(stroke);
Vertex[] verts = cube.getVerts();
for(int i = 0; i < verts.length; i++)
{
ArrayList<Vertex> links = verts[i].getLinks();
for(int j = 0; j < links.size(); j++)
{
Line2D l = new Line2D.Double(verts[i].getPosition().x()+500,/**/verts[i].getPosition().y()+400,/*|*/links.get(j).getPosition().x()+500,/**/links.get(j).getPosition().y()+400);
g2.setColor(Color.RED);
g2.draw(l);
}
}
}
#Override
public void run()
{
int frame = 0;
while(running)
{
if(frame == 1)
{
cube.rotateY(0.0004);
cube.rotateZ(-0.0005);
cube.rotateX(-0.0003);
}
if(frame > 200000)
{
//cube.rotateY(0.0001);
//cube.rotateZ(0.000015);
repaint();
frame = 0;
}
frame++;
}
}
}
Cube.java
package com.my3d.geometry;
import java.util.ArrayList;
import com.my3d.graphics.Vector3;
import com.my3d.graphics.Vertex;
public class Cube
{
private static final String DEFAULT_NAME = "Cube";
private static final double DEFAULT_SIZE = 100;
private static int cubeCount = 0;
private Vertex[] corners;
private Vector3 position;
private Vector3 rotation;
private String name;
private int number;
public Cube()
{
if(cubeCount < 10)
setName(DEFAULT_NAME +"_0" +cubeCount);
else
setName(DEFAULT_NAME +"_" +cubeCount);
//assign Id
number = cubeCount;
//Increment cube counter
cubeCount++;
corners = new Vertex[8];
for(int i = 0; i < 8; i++)
corners[i] = new Vertex();
corners[0].setPosition(new Vector3(DEFAULT_SIZE,DEFAULT_SIZE,DEFAULT_SIZE));
corners[1].setPosition(new Vector3(-DEFAULT_SIZE,DEFAULT_SIZE,DEFAULT_SIZE));
corners[2].setPosition(new Vector3(-DEFAULT_SIZE,DEFAULT_SIZE,-DEFAULT_SIZE));
corners[3].setPosition(new Vector3(DEFAULT_SIZE,DEFAULT_SIZE,-DEFAULT_SIZE));
corners[4].setPosition(new Vector3(DEFAULT_SIZE,-DEFAULT_SIZE,DEFAULT_SIZE));
corners[5].setPosition(new Vector3(-DEFAULT_SIZE,-DEFAULT_SIZE,DEFAULT_SIZE));
corners[6].setPosition(new Vector3(-DEFAULT_SIZE,-DEFAULT_SIZE,-DEFAULT_SIZE));
corners[7].setPosition(new Vector3(DEFAULT_SIZE,-DEFAULT_SIZE,-DEFAULT_SIZE));
corners[0].addLink(corners[1]);
corners[1].addLink(corners[2]);
corners[2].addLink(corners[3]);
corners[3].addLink(corners[0]);
corners[0].addLink(corners[4]);
corners[1].addLink(corners[5]);
corners[2].addLink(corners[6]);
corners[3].addLink(corners[7]);
corners[4].addLink(corners[5]);
corners[5].addLink(corners[6]);
corners[6].addLink(corners[7]);
corners[7].addLink(corners[4]);
// corners[0].addLink(corners[5]);
// corners[1].addLink(corners[6]);
// corners[2].addLink(corners[7]);
// corners[3].addLink(corners[4]);
//
// corners[0].addLink(corners[2]);
// corners[5].addLink(corners[7]);
setPosition(new Vector3());
}
public Cube(String n)
{
setName(n);
//assign Id
number = cubeCount;
//Increment cube counter
cubeCount++;
}
////////////////////////////////////////////////////////////////
//Setters
////////////////////////////////////////////////////////////////
private void setName(String n)
{
name = n;
}
private void setPosition(Vector3 v)
{
position = v;
}
public Vertex[] getVerts()
{
return corners;
}
public void printCubeVertices()
{
for(int i = 0; i < 8; i++)
System.out.println(corners[i].getPosition().toString());
}
public void rotateZ(double degrees)
{
for(int i = 0; i < corners.length; i++)
{
double tempZ = corners[i].getPosition().z();
double newX = (corners[i].getPosition().x()-position.x())*Math.cos(degrees)-(corners[i].getPosition().y()-position.y())*Math.sin(degrees);
double newY = (corners[i].getPosition().x()-position.x())*Math.sin(degrees)+(corners[i].getPosition().y()-position.y())*Math.cos(degrees);
corners[i].setPosition(new Vector3(newX,newY,tempZ));
}
}
public void rotateX(double degrees)
{
for(int i = 0; i < corners.length; i++)
{
double tempX = corners[i].getPosition().x();
double newZ = (corners[i].getPosition().z()-position.z())*Math.cos(degrees)-(corners[i].getPosition().y()-position.y())*Math.sin(degrees);
double newY = (corners[i].getPosition().z()-position.z())*Math.sin(degrees)+(corners[i].getPosition().y()-position.y())*Math.cos(degrees);
corners[i].setPosition(new Vector3(tempX,newY,newZ));
}
}
public void rotateY(double degrees)
{
for(int i = 0; i < corners.length; i++)
{
double tempY = corners[i].getPosition().y();
double newZ = (corners[i].getPosition().z()-position.z())*Math.cos(degrees)-(corners[i].getPosition().x()-position.x())*Math.sin(degrees);
double newX = (corners[i].getPosition().z()-position.z())*Math.sin(degrees)+(corners[i].getPosition().x()-position.x())*Math.cos(degrees);
corners[i].setPosition(new Vector3(newX,tempY,newZ));
}
}
}
Vertex.java
package com.my3d.graphics;
import java.util.ArrayList;
public class Vertex
{
private Vector3 position;
private ArrayList<Vertex> linked = new ArrayList<Vertex>();
public Vertex()
{
setPosition(new Vector3(0.0,0.0,0.0));
}
public Vertex(Vector3 v)
{
setPosition(v);
}
public void setPosition(Vector3 pos)
{
position = pos;
}
public void addLink(Vertex v)
{
linked.add(v);
}
public void removeLink(Vertex v)
{
linked.remove(v);
}
/////////////////////////////////////////////////////////////////
//Getters
/////////////////////////////////////////////////////////////////
public Vector3 getPosition()
{
return position;
}
public ArrayList<Vertex> getLinks()
{
return linked;
}
}
Vector3.java
package com.my3d.graphics;
public class Vector3
{
private double[] xyz;
// public double x;
// public double y;
// public double z;
public Vector3()
{
xyz = new double []{0.0,0.0,0.0};
// x = xyz[0];
// y = xyz[1];
// z = xyz[2];
}
public Vector3(double x,double y,double z)
{
xyz = new double []{x,y,z};
// xyz[0] = x;
// xyz[1] = y;
// xyz[2] = z;
}
public Vector3(double[] array)
{
try
{
if(array.length > 3)
{
RuntimeException e = new RuntimeException("The Vector3 is too big by (" +(array.length - 3) +") elements you muppet.\nremember that a Vector3 supports arrays with a length of 3. You have (" +array.length +")");
throw e;
}
if(array.length < 3)
{
RuntimeException e = new RuntimeException("The Vector3 is too small by (" +(3 - array.length) +") elements you muppet.\nremember that a Vector3 supports arrays with a length of 3. You have (" +array.length +")");
throw e;
}
xyz[0] = array[0];
xyz[1] = array[1];
xyz[2] = array[2];
}
catch(RuntimeException e)
{
System.out.println(e);
}
}
///////////////////////////////////////////////////////////////////////////
public void x(double a)
{
xyz[0] = a;
}
public void y(double a)
{
xyz[1] = a;
}
public void z(double a)
{
xyz[2] = a;
}
public double x()
{
return xyz[0];
}
public double y()
{
return xyz[1];
}
public double z()
{
return xyz[2];
}
public void normalize()
{
double length = Math.sqrt((xyz[0] * xyz[0]) + (xyz[1] * xyz[1]) + (xyz[2] * xyz[2]));
xyz[0] = xyz[0]/length;
xyz[1] = xyz[1]/length;
xyz[2] = xyz[2]/length;
}
public String toString()
{
return "(" + xyz[0] + "," + xyz[1] + "," + xyz[2] + ")";
}
}
The best I can do is trace it to public double x(){return xyz[0];}
in the Vector3 class when the paint method is called in the viewport class but when I try and step through they are never null. I get the impression that the jvm is catching up with itself while trying to reassign new positions to the vertices during the rotation.
Edit: Stacktrace
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at com.my3d.graphics.Vector3.x(Vector3.java:70)
at com.my3d.graphics.Viewport.paint(Viewport.java:107)
at java.awt.Canvas.update(Unknown Source)
at sun.awt.RepaintArea.updateComponent(Unknown Source)
at sun.awt.RepaintArea.paint(Unknown Source)
at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Additional Info:
Manager.java
package com.my3d.core;
import java.awt.Color;
import com.my3d.graphics.Viewport;
import com.my3d.graphics.Wind;
import com.my3d.utils.Props;
public class Manager {
/**
* #param args
*/
public static void main(String[] args)
{
Props.loadPropsFromFile();
Wind wind = new Wind();
wind.setVisible(true);
Viewport view = new Viewport("Main view");
view.setPreferredSize(Props.getWindowSize());
view.setBackground(Color.LIGHT_GRAY);
wind.add(view);
view.setVisible(true);
wind.pack();
view.run();
}
}
Wind.java
package com.my3d.graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import com.my3d.utils.Props;
import javax.swing.JFrame;
public class Wind extends JFrame
{
/**
*
*/
private static final long serialVersionUID = 1L;
private boolean fullScreen = false;
//Constructor
public Wind()
{
super(Props.getDefaultTitle() +Props.getTitleSplash());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fullScreen = Props.getFullScreen();
if (fullScreen)
{
setUndecorated(true);
GraphicsDevice vc;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
vc= ge.getDefaultScreenDevice();
vc.setFullScreenWindow(this);
}
else
{
setBounds(0,0,Props.getWindowWidth(),Props.getWindowHeight());
setLocationRelativeTo(null);
}
}
public void setTitle(String s)
{
//if(Props.getRandomSplashes())
super.setTitle(Props.getDefaultTitle() +s);
}
public void setRandomTitle()
{
if(Props.getRandomSplashes())
Props.setTitleSplash(Props.randomizeSplash());
super.setTitle(Props.getDefaultTitle() +Props.getTitleSplash());
}
public void setFullScreen(boolean b)
{
fullScreen = b;
}
//this method wont work >>
public void refactorWindow()
{
if (fullScreen)
{
setUndecorated(true);
GraphicsDevice vc;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
vc= ge.getDefaultScreenDevice();
vc.setFullScreenWindow(this);
}
else
{
setSize(Props.getWindowSize());
setLocationRelativeTo(null);
}
}
}
Props.java
package com.my3d.utils;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;
import javax.imageio.ImageIO;
public class Props
{
private static String gameTitle;
private static String splashTitle;
private static Dimension windowSize;
//private static int gameState;
private static boolean fullScreen;
private static boolean randomSplashes;
private static BufferedImage tileSheet;
///////////////////////////////////////////////////////////////
//Getters
///////////////////////////////////////////////////////////////
public static Dimension getWindowSize()
{
return windowSize;
}
public static int getWindowWidth()
{
return (int) windowSize.getWidth();
}
public static int getWindowHeight()
{
return (int) windowSize.getHeight();
}
public static String getTitleSplash()
{
return splashTitle;
}
public static String getDefaultTitle()
{
return gameTitle;
}
public static boolean getFullScreen()
{
return fullScreen;
}
public static boolean getRandomSplashes()
{
return randomSplashes;
}
public static BufferedImage getTileSheet()
{
return tileSheet;
}
/////////////////////////////////////////////////////////////////////
//Setters
/////////////////////////////////////////////////////////////////////
public static void setWindowSize(int width, int height)
{
windowSize = new Dimension(width,height);
}
public static void setTitleSplash(String s)
{
splashTitle = s;
}
private static void setDefaultTitle(String s)
{
gameTitle = s;
}
////////////////////////////////////////////////////////////////////
//loader
////////////////////////////////////////////////////////////////////
public static void loadPropsFromFile()
{
File file = new File("res\\config\\props.props");
try
{
Scanner input = new Scanner(file);
String lInput = input.nextLine().trim();
setWindowSize(Integer.parseInt(lInput.substring(lInput.indexOf('=')+1,lInput.indexOf(','))),Integer.parseInt(lInput.substring(lInput.indexOf(',')+1,lInput.length())));
lInput = input.nextLine().trim();
if(lInput.substring(lInput.indexOf('=')+1,lInput.length()).equals("true"))
fullScreen = true;
else
fullScreen = false;
lInput = input.nextLine().trim();
if(lInput.substring(lInput.indexOf('=')+1,lInput.length()).equals("true"))
{
lInput = input.nextLine().trim();
setDefaultTitle(lInput.substring(lInput.indexOf('=')+1,lInput.length()));
randomSplashes = true;
setTitleSplash(randomizeSplash());
}
else
{
lInput = input.nextLine().trim();
setDefaultTitle(lInput.substring(lInput.indexOf('=')+1,lInput.length()));
setTitleSplash("");
randomSplashes = false;
}
lInput = input.nextLine().trim();
try
{
tileSheet = ImageIO.read(new File(loadFromDirectory(lInput.substring(lInput.indexOf('=')+1,lInput.length()))));
}
catch (IOException e)
{
System.out.println("Tilesheet Cannot be found.");
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
}
///////////////////////////////////////////////////////////////////
//Utility
///////////////////////////////////////////////////////////////////
public static String loadFromDirectory(String key)
{
String s = "";
File sFile = new File("res\\config\\dirs.dir");
try
{
Scanner sInput = new Scanner(sFile);
while(sInput.hasNextLine())
{
String st = sInput.nextLine().trim();
if(key.equals(st.substring(1,st.length())));
s = sInput.nextLine().trim();
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
return s;
}
public static String randomizeSplash()
{
if(randomSplashes)
{
File sFile = new File("res\\config\\splash.props");
ArrayList<String> sArray = new ArrayList<String>();
try
{
Scanner sInput = new Scanner(sFile);
while(sInput.hasNextLine())
{
sArray.add(sInput.nextLine().trim());
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
Random rand = new Random();
String s = sArray.get(rand.nextInt(sArray.size()));
splashTitle = s;
return s;
}
else
return "";
}
}
props.props
windowSize =1000,800
startInFullscreen =false
randomSplashes =false
defaultTitle =3D Prototype
tileSheet =tileSheet
dirs.dir
<startGame>
res\\img\\button\\Start_game_text.png
res\\img\\button\\Start_game_active.png
res\\img\\button\\Start_game_click.png
<quitGame>
res\\img\\button\\Quit_game_text.png
res\\img\\button\\Quit_game_active.png
res\\img\\button\\Quit_game_click.png
<menuBackground>
res\\img\\Menu_background.png
<resumeGame>
res\\img\\button\\Resume_game_text.png
res\\img\\button\\Resume_game_active.png
res\\img\\button\\Resume_game_click.png
<tileSheet>
res\\img\\Final_Sub_Hunt.png
Update: mark your field xyz in Vector3 as final. My reading of the JMM says that, without a synchronizer or final, it is legal for the Event Dispatch Thread (EDT), from whence your stack trace comes, to see that non-final field as not yet initialized by the constructor when the constructor is run in a separate (your Viewport.run()) thread. See this SO question or the JMM FAQ.
The correct place to look in the JLS for this is in Section 17.5 (though you really need to read all of 17, and a lot of times, to start to grok this):
An object is considered to be completely initialized when its
constructor finishes. A thread that can only see a reference to an
object after that object has been completely initialized is guaranteed
to see the correctly initialized values for that object's final
fields.
Note that the same guarantee does not hold for non-final fields; if you want safety there, you will need to use synchronization edges of some kind (explicit synchronizers, volatile, whatever.)
Related discussion from Alex Miller, who I think putters around StackOverflow as https://stackoverflow.com/users/7671/alex-miller and might poke his head in if we say hello.) And a related SO question.
Original Answer
Looks like you have an error in your Vector3(double[]) constructor, which never initializes the double[] xyz field. Only place I could find where that field would not be initialized and yield an NPE.
I can't tell from your code how many threads you have running around. If you are creating instances of Vector3 in one thread and placing them into a collection while they are being displayed by a separate rendering thread, it is possible you are observing them in a state of partial construction. Generally things are arranged to make this unlikely (I can't remember off the top of my head what the memory model says about publishing partially-constructed objects, but it can happen if you let this slip out of the constructor; I don't see that happening here.)
JA
catch(RuntimeException e)
{
System.out.println(e);
}
This segment allows you to create a Vector3 without properly initialising the array. That's a potiential problem.
Both of the non-default constructors make no attempt to check x, y or z is null before assigning them.
I would start there, then consider unit tests.
So I have all these classes put together for all the connectivity between Predators and Prey and the world. The only thing I'm really stumped on is the run() method for the Predator class (how they hunt).
The theory is simple. The predators have to gather around the Prey on it's North, South, East and West side, and the DataChannel class will notice that and capture the prey and take it off the map. But my job is to get this to happen, by having the Predators communicate with one another, and then chase and hunt down the Prey (who I programmed to move randomly).
Here's all the classes. Remember, the run() method for the Predator class is where I'm stumped. Everything else is how I want it to be. Any help?
/**
Predator class, with no "hunting" functionality.
*/
import java.io.*;
import javax.imageio.ImageIO;
import java.util.ArrayList;
import javaclient2.*;
import javaclient2.structures.*;
public class Predator extends Thread
{
private Position2DInterface position_interface = null;
private BlobfinderInterface blob_finder = null;
private PlayerClient playerClient = null;
private DataChannel dc = null;
private String name = "";
public Predator(String name, DataChannel dc, int id, float x, float y){
this.name = name;
this.playerClient = new PlayerClient("localhost", 6665);
blob_finder = playerClient.requestInterfaceBlobfinder(id,
PlayerConstants.PLAYER_OPEN_MODE);
position_interface = playerClient.requestInterfacePosition2D(id,
PlayerConstants.PLAYER_OPEN_MODE);
playerClient.runThreaded (-1, -1);
//wait until the intefaces are ready before doing anything
while(!blob_finder.isDataReady() ||
!position_interface.isDataReady()) {
try{
sleep(100);
}catch(Exception e){
System.err.println("Error sleeping!");
e.printStackTrace();
System.exit(-1);
}
}
PlayerPose pp = new PlayerPose();
pp.setPx(x);
pp.setPy(y);
position_interface.setOdometry(pp);
this.dc = dc;
dc.registerPredator(name, position_interface);
}
/**
* #param recipient The predator to deliver the message to.
* #param msg The message.
*
* Deliver a message to another predator.
*
*/
public void sendMessage(String recipient, Object msg){
dc.sendMessage(recipient, msg);
}
/**
* #param msg The message.
*
* Deliver a message to all other predators.
*
*/
public void broadcastMessage(Object msg){
for(String predator : dc.getPredators()){
sendMessage(predator, msg);
}
}
/**
*
* Get the next message from other predators.
*
* #return The next message, or null if there are no unread messages.
*
*/
public Object getMessage(){
return dc.getMessage(this.name);
}
public void run(){
// hunt the prey!
System.out.println("There are " + dc.numLivingPreys() +
" left to capture!");
}
}
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Vector;
import java.util.Set;
import javaclient2.*;
import javaclient2.structures.*;
/**
Object that records all of the predator locations, and kills prey when
they have been captured.
*/
public class DataChannel extends Thread{
static final float FUDGE_FACTOR = 1;
static final float CAPTURE_RANGE = 5;
private ConcurrentHashMap<String, Position2DInterface> pred_pids =
new ConcurrentHashMap<String, Position2DInterface>();
private ConcurrentHashMap<String, Position2DInterface> prey_pids =
new ConcurrentHashMap<String, Position2DInterface>();
private ConcurrentHashMap<String, Prey> preys =
new ConcurrentHashMap<String, Prey>();
private ConcurrentHashMap<String, ConcurrentLinkedQueue<Object>> msgs =
new ConcurrentHashMap<String, ConcurrentLinkedQueue<Object>>();
public void registerPredator(String name, Position2DInterface pid){
pred_pids.put(name, pid);
msgs.put(name, new ConcurrentLinkedQueue<Object>());
}
public void registerPrey(String name, Position2DInterface pid, Prey prey){
prey_pids.put(name, pid);
preys.put(name, prey);
}
public int numLivingPreys(){
return preys.size();
}
public Set<String> getPredators(){
return msgs.keySet();
}
public void sendMessage(String recipient, Object msg){
(msgs.get(recipient)).add(msg);
}
public Object getMessage(String recipient){
return (msgs.get(recipient)).poll();
}
public float getPredX(String predator){
try{
return (pred_pids.get(predator)).getX();
}catch(Exception ex) {}
return -1.0f;
}
public float getPredY(String predator){
try{
return (pred_pids.get(predator)).getY();
}catch(Exception ex) {}
return -1.0f;
}
public float getPreyX(String prey){
try{
return (prey_pids.get(prey)).getX();
}catch(Exception ex) {}
return -1.0f;
}
public float getPreyY(String prey){
try{
return (prey_pids.get(prey)).getY();
}catch(Exception ex) {}
return -1.0f;
}
public void run(){
while(true){
try{
sleep(100);
}catch(Exception e){
System.err.println("Error sleeping!");
e.printStackTrace();
System.exit(-1);
}
//get the location of each predator
Vector<Float> xpos = new Vector<Float>();
Vector<Float> ypos = new Vector<Float>();
Vector<String> pred_names= new Vector<String>();
for(String predator : pred_pids.keySet()){
if(pred_pids.get(predator) == null){
System.err.println("pred_pids does not have " + predator);
System.exit(-1);
}
xpos.add(getPredX(predator));
ypos.add(getPredY(predator));
pred_names.add(predator);
}
//for each prey, see if all of the four positions are guarded
for(String prey : prey_pids.keySet()){
boolean north = false;
boolean south = false;
boolean east = false;
boolean west = false;
if(prey_pids.get(prey) == null){
System.err.println("prey_pids does not have " + prey);
System.exit(-1);
}
float prey_x = getPreyX(prey);
float prey_y = getPreyY(prey);
for(int i=0; i < xpos.size(); i++){
//NORTH
if(Math.abs(xpos.get(i) - prey_x)<FUDGE_FACTOR &&
(ypos.get(i) - prey_y) > 0 &&
(ypos.get(i) - prey_y) < CAPTURE_RANGE){
north = true;
}
//SOUTH
if(Math.abs(xpos.get(i) - prey_x)<FUDGE_FACTOR &&
(prey_y - ypos.get(i)) > 0 &&
(prey_y - ypos.get(i)) < CAPTURE_RANGE){
south = true;
}
//EAST
if(Math.abs(ypos.get(i) - prey_y)<FUDGE_FACTOR &&
(xpos.get(i) - prey_x) > 0 &&
(xpos.get(i) - prey_x) < CAPTURE_RANGE){
east = true;
}
//WEST
if(Math.abs(ypos.get(i) - prey_y)<FUDGE_FACTOR &&
(prey_x - xpos.get(i)) > 0 &&
(prey_x - xpos.get(i)) < CAPTURE_RANGE){
west = true;
}
}
//prey is boxed in
if(north && south && east && west){
(preys.get(prey)).die();
preys.remove(prey);
prey_pids.remove(prey);
}
}
if(preys.size() == 0){
System.err.println("Congratulations: All prey are captured.");
System.exit(0);
}
}
}
}
import javaclient2.structures.*;
import javaclient2.*;
import java.util.Random;
/**
Prey class.
*/
public class Prey extends Thread{
private final int ROTATE_SECONDS = 500;
private final int MOVE_SECONDS = 3000;
private final int WAIT_SECONDS = 8000;
private final int WAIT_JITTER = 4000;
private Position2DInterface position_interface = null;
private PlayerClient playerClient = null;
private DataChannel dc = null;
private String my_name = null;
private boolean keep_going = true;
Random rand = new Random();
public Prey(String name, DataChannel dc, int id, float x, float y){
this.playerClient = new PlayerClient("localhost", 6665);
position_interface = playerClient.requestInterfacePosition2D(id,
PlayerConstants.PLAYER_OPEN_MODE);
playerClient.runThreaded (-1, -1);
this.dc = dc;
this.my_name = name;
while(!position_interface.isDataReady()) {
try{
sleep(100);
}catch(Exception e){
System.err.println("Error sleeping!");
e.printStackTrace();
System.exit(-1);
}
}
PlayerPose pp = new PlayerPose();
pp.setPx(x);
pp.setPy(y);
position_interface.setOdometry(pp);
dc.registerPrey(name, position_interface, this);
}
public float getX(){
try{
return position_interface.getX();
}catch(Exception ex) {}
return -1.0f;
}
public float getY(){
try{
return position_interface.getY();
}catch(Exception ex) {}
return -1.0f;
}
public void run(){
float old_x = getX();
float old_y = getY();
while(keep_going){
float current_x = getX();
float current_y = getY();
float x, y;
if(current_x <=0){
if(rand.nextFloat() < 0.75)
x = rand.nextFloat()*6;
else
x = rand.nextFloat()*-6;
}else{
if(rand.nextFloat() < 0.75)
x = rand.nextFloat()*-6;
else
x = rand.nextFloat()*6;
}
if(current_y <=0){
if(rand.nextFloat() < 0.75)
y = rand.nextFloat()*12;
else
y = rand.nextFloat()*-12;
}else{
if(rand.nextFloat() < 0.75)
y = rand.nextFloat()*-12;
else
y = rand.nextFloat()*12;
}
PlayerPose pp = new PlayerPose();
pp.setPx(x);
pp.setPy(y);
position_interface.setVelocity(pp, 0);
sleep(MOVE_SECONDS);
position_interface.setSpeed(0.0f, 0.0f);
sleep(WAIT_SECONDS + rand.nextInt() % WAIT_JITTER);
}
position_interface.setSpeed(9999.0f, 0.0f);
}
public void sleep(int ms){
try{
Thread.sleep(ms);
}catch(Exception e){
System.err.println("Error sleeping.");
e.printStackTrace();
System.exit(-1);
}
}
public float angle_diff(float current, float desired)
{
float diff = desired - current;
while(diff > 180.0f) diff -= 360.0f;
while(diff < -180.0f) diff += 360.0f;
return diff;
}
public float fix_angle(float f){
while(f < 0.0f) f += 360.0f;
while(f > 360.0f) f -= 360.0f;
return f;
}
public void die(){
System.err.println("Prey \"" + this.my_name + "\" has been killed!");
this.keep_going = false;
}
}
public class Driver{
public static void main(String args[]){
DataChannel dc = new DataChannel();
//instantiate the predators
Predator pred1 = new Predator("pred1", dc, 0, 0, 13);
Predator pred2 = new Predator("pred2", dc, 1, 0, 0);
Predator pred3 = new Predator("pred3", dc, 2, 0, -13);
Predator pred4 = new Predator("pred4", dc, 3, -13, -8);
Predator pred5 = new Predator("pred5", dc, 4, -13, 8);
//instantiate the prey
Prey prey1 = new Prey("prey1", dc, 5, 18, 18);
Prey prey2 = new Prey("prey2", dc, 6, 18, -18);
Prey prey3 = new Prey("prey3", dc, 7, 18, -9);
Prey prey4 = new Prey("prey4", dc, 8, 18, 9);
//start all the threads
dc.start();
pred1.start();
pred2.start();
pred3.start();
pred4.start();
pred5.start();
prey1.start();
prey2.start();
prey3.start();
prey4.start();
}
}
The predators don't actually need to communicate. They just need to locate prey, and move as close as possible to it.
So, if p1 and p2 represents predators and o1 represents prey:
A B C D
0 . . . .
1 . p2 . .
2 . . p1 .
3 . . o1 .
p1 is as close as it can get to o1, so it stays put.
p2, however, can get closer by moving to B3.
Now, in your sample code, you have 4 predators and 5 prey. This could lead to a case where there are not enough predators focused on one prey to eliminate it. For that to work, you need a heuristic like: "prefer the prey with the most predators".
You may also need to consider the case where both sides are equal. You could wind up with one predator per prey. That can be handled by having predators give up if a period elapses without their prey being eliminated. You will want to include some randomness so that not all the predators give up at the same time. Something like baseGiveUpTime + (int)(2 * numPred * Math.random())