Question

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
Was it helpful?

Solution

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

OTHER TIPS

    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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top