Newer
Older
EPQ-3D-renderer / src / main / java / uk / org / floop / epq3d / Player.java
@cory cory on 1 Feb 2023 9 KB Fix #14
package uk.org.floop.epq3d;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;

public class Player {

    public Plane[] frustumPlanes = new Plane[6];
    // image that represents the player's position on the board
    // current position of the player on the board grid
    private Matrix rotMatrix;
    public Matrix camMatrix;
    private Matrix invRotMatrix;
    private Matrix invCamMatrix;
    public double FOV = 100;
    protected double Fpdis;
    protected Point3D FPWorldPos;
    protected PointComp[] ScreenCornerPosS = new PointComp[4];
    private final Point3D position = new Point3D(0,0,0);
    private final Point3D rotation = new Point3D(0,Math.PI / 2, 0);
    private final Vector3D direction = new Vector3D(0,0,0);
    public Vector3D viewVector = new Vector3D(0,0,0);
    public double mouseSensitivity = 0.005; // radians per pixel
    // called when the player is initialised
    public Player(int scrX, int scrY) {
        Fpdis = 1/Math.tan(Math.toRadians(FOV)/2d);
        for(int i = 0; i < 6; i+=1){
            frustumPlanes[i] = new Plane();
        }
        double yVal = (double)scrY / (double)scrX;
        // flip x and y because reasons
        ScreenCornerPosS[0] = new PointComp(-yVal,-1,  0);
        ScreenCornerPosS[1] = new PointComp(yVal,-1,  0);
        ScreenCornerPosS[2] = new PointComp(-yVal,1,  0);
        ScreenCornerPosS[3] = new PointComp(yVal,1,  0);
    }

    // unused because the player should not be drawn
    // I'm leaving it in as a hint to buffered Images
    public void draw(Graphics g, ImageObserver observer) {
        int width = 100;
        int height = 100;

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB );

        for ( int py = 0; py < height; py++ ) {
            for ( int px = 0; px < width; px++ ) {
                // Set the pixel colour of the image n.b. x = cc, y = rc
                img.setRGB(px, py, Color.BLACK.getRGB() );
            }
        }
        g.drawImage(img, 0, 0, observer);
    }

    public void keyPressed(int key) {
        if (key == KeyEvent.VK_W && direction.x < 1) {
            direction.x += 1;
        } else if (key == KeyEvent.VK_S && -direction.x < 1) {
            direction.x += -1;
        } else if (key == KeyEvent.VK_A && direction.y < 1) {
            direction.y += 1;
        } else if (key == KeyEvent.VK_D && -direction.y < 1) {
            direction.y += -1;
        } else if (key == KeyEvent.VK_SPACE && direction.z < 1) {
            direction.z += 1;
        } else if (key == KeyEvent.VK_SHIFT && -direction.z < 1) {
            direction.z += -1;
        }
    }
    public void keyReleased(int key){
        if (key == KeyEvent.VK_W && direction.x > -1) {
            direction.x -= 1;
        } else if (key == KeyEvent.VK_S && -direction.x > -1) {
            direction.x -= -1;
        } else if (key == KeyEvent.VK_A && direction.y > -1) {
            direction.y -= 1;
        } else if (key == KeyEvent.VK_D && -direction.y > -1) {
            direction.y -= -1;
        } else if (key == KeyEvent.VK_SPACE && direction.z > -1) {
            direction.z -= 1;
        } else if (key == KeyEvent.VK_SHIFT && -direction.z > -1) {
            direction.z -= -1;
        }
    }
    public void tick(Point2D mouseRel) {
        // gets called once every tick, before the repainting process happens.
        // change rotation depending on mouse movement
        rotation.z -= mouseRel.x * mouseSensitivity;
        // force z rotation to wrap around
        if(rotation.z<-Math.PI){
            rotation.z = Math.PI;
        } else if(rotation.z>Math.PI){
            rotation.z = -Math.PI;
        }
        rotation.y += mouseRel.y * mouseSensitivity;
        // max out y rotation at 0 and -90 degrees
        if(rotation.y < 0){
            rotation.y = 0;
        } else if(rotation.y > Math.PI){
            rotation.y = Math.PI;
        }
        // define rotation and translation matrices
        Matrix zMat = new Matrix(3, 3);
        zMat.setItems(new double[][]{
                {Math.cos(rotation.z),  Math.sin(rotation.z), 0},
                {-Math.sin(rotation.z), Math.cos(rotation.z), 0},
                {0,                      0,                     1}}
        );
        Matrix yMat = new Matrix(3, 3);
        yMat.setItems(new double[][]{
                {Math.cos(rotation.y), 0, -Math.sin(rotation.y)},
                {0,                    1, 0                    },
                {Math.sin(rotation.y), 0, Math.cos(rotation.y) }}
        );
        Matrix xMat = new Matrix(3, 3);
        xMat.setItems(new double[][]{
                {1, 0,                    0                   },
                {0, Math.cos(rotation.x), Math.sin(rotation.x)},
                {0, -Math.sin(rotation.x), Math.cos(rotation.x)}}
        );
        // calculate inverse matrices
        Matrix izMat = new Matrix(3, 3);
        izMat.setItems(new double[][]{
                {Math.cos(-rotation.z),  Math.sin(-rotation.z), 0},
                {-Math.sin(-rotation.z), Math.cos(-rotation.z), 0},
                {0,                      0,                     1}}
        );
        Matrix iyMat = new Matrix(3, 3);
        iyMat.setItems(new double[][]{
                {Math.cos(-rotation.y), 0, -Math.sin(-rotation.y)},
                {0,                    1, 0                    },
                {Math.sin(-rotation.y), 0, Math.cos(-rotation.y) }}
        );
        Matrix ixMat = new Matrix(3, 3);
        ixMat.setItems(new double[][]{
                {1, 0,                    0                   },
                {0, Math.cos(-rotation.x), Math.sin(-rotation.x)},
                {0, -Math.sin(-rotation.x), Math.cos(-rotation.x)}}
        );
        // apply izMat to direction vector
        try {
            Vector3D dirvec = new Vector3D();
            izMat.multiplyVec3to(direction, dirvec);
            double dirLen = dirvec.getLength();
            if (dirLen != 0) {
                dirvec.x = dirvec.x / (dirLen * 40);
                dirvec.y = dirvec.y / (dirLen * 40);
                dirvec.z = dirvec.z / (dirLen * 40);
            }
            position.translate(dirvec);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        Matrix traMat = new Matrix(4, 3);
        traMat.setItems(new double[][]
                {{1, 0, 0, -position.x},
                 {0, 1, 0, -position.y},
                 {0, 0, 1, -position.z}}
        );
        // multiply out matrices
        rotMatrix = xMat.multiplyGetResult(yMat).multiplyGetResult(zMat);
        camMatrix = rotMatrix.multiplyGetResult(traMat);
        invRotMatrix = izMat.multiplyGetResult(iyMat).multiplyGetResult(ixMat);
        // I really don't want to do it like this, but I don't think there's any other way without messing up
        // all my other matrices :(
        invCamMatrix = new Matrix(4, 3);
        for(int x = 0; x<3; x+=1){
            for(int y = 0; y<3; y+=1){
                invCamMatrix.setItem(x, y, invRotMatrix.getItem(x, y));
            }}
        for(int y = 0; y<3; y+=1){
            invCamMatrix.setItem(3, y, -traMat.getItem(3, y));
        }

        // calculate view vector
        Point3D viewPoint = new Point3D(0,0,0);
        invRotMatrix.multiplyPoint3to(new Point3D(0,0,1), viewPoint);
        viewVector.x = viewPoint.x;viewVector.y=viewPoint.y;viewVector.z=viewPoint.z;

        // calculate camera focal point and edges in world coordinates
        FPWorldPos = new Point3D(
                position.x - viewVector.x*Fpdis,
                position.y - viewVector.y*Fpdis,
                position.z - viewVector.z*Fpdis);
        for (PointComp point:
             ScreenCornerPosS) {
            point.invalidate();
            point.setRotatedPoint(invCamMatrix);
        }
        // find frustum planes
        // near plane
        frustumPlanes[0].initFrom3Points(ScreenCornerPosS[0].getRotatedPoint(),
                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
        // far plane
        int farPlaneDis = 1000;
        frustumPlanes[1].initFromPointAndVector(
                new Point3D(
                        position.x + viewVector.x*farPlaneDis,
                        position.y + viewVector.y*farPlaneDis,
                        position.z + viewVector.z*farPlaneDis),
                // invert the view vector because the normal needs to point the other way
                new Vector3D(-viewVector.x, -viewVector.y, -viewVector.z));
        // mid planes
        // left plane
        frustumPlanes[2].initFrom3Points(FPWorldPos,
                ScreenCornerPosS[0].getRotatedPoint(), ScreenCornerPosS[1].getRotatedPoint());
        // right plane
        frustumPlanes[3].initFrom3Points(FPWorldPos,
                ScreenCornerPosS[3].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
        // top plane
        frustumPlanes[4].initFrom3Points(FPWorldPos,
                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[3].getRotatedPoint());
        // bottom plane
        frustumPlanes[5].initFrom3Points(FPWorldPos,
                ScreenCornerPosS[2].getRotatedPoint(), ScreenCornerPosS[0].getRotatedPoint());
    }

    // returnValue Functions
    public Point3D getPos() {
        return position;
    }
    public Point3D getRot() {
        return rotation;
    }

}