> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.xvjqNbay51Z/rev.7338
 * 
 * authors: 
 *   unnamed
 *   The Awesome TNT
 *   Ewen
 *   

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



/* @pjs preload="/static/uploaded_resources/p.13415/sky.jpg"; */
import processing.core.*;
import processing.opengl.*;
import java.util.*;
import java.io.*;

int oldMouseX=200, oldMouseY=200;
final static int R = 1000; // scale
PMatrix3D cam; // the cam
PFont font; // the font

HashMap<String, Object> settings;
int tickSpeed = 1000/60; // 60 fps
int delta = 0;
long lastUpdateTime;
Sky sky;
double camXRot=0, camYRot=0;
double camX=0, camY=0, camZ=0;
PVector x, y, d, d2, d3;
//  x = x direction (relative to cam)
//  y = y direction (relative to cam)
//  d = cam directon * 100
// d2 = forward move vector
// d3 = strafe vector
double moveSpeed=10; // the cam move speed

boolean wpress=false, apress=false, spress=false, dpress=false;

// extra
int tx=100, ty=200, tvx=0.3, tvy=0.7;
int extraFrame=0;

Node root; // the root node, this is where to put all the stuff
Node bulletNode;
Node particlesNode;

// BEGIN NATIVE JS CODE, COMMENT OUT IN REAL PROCESSING
// Don't use these functions, use the non __ wrappers
int __lastX=200, __lastY=200;
boolean allowLocking=true;
s=null;
boolean __hasPointerLock(){
    return ('pointerLockElement' in document ||
    'mozPointerLockElement' in document ||
    'webkitPointerLockElement' in document);
}
void __requestPointerLock(){
    if(!allowLocking)return;
    s.requestPointerLock();
}
void __releasePointerLock(){
    if(!allowLocking)return;
    document.exitPointerLock();
}

boolean __isPointerLocked(){
    document.pointerLockElement = document.pointerLockElement    ||
                              document.mozPointerLockElement ||
                              document.webkitPointerLockElement;
    return !!document.pointerLockElement;
}

void __initLocking(){
    var havePointerLock = __hasPointerLock();
    if(!havePointerLock){
        allowLocking=false;
        println("no pointer lock");
        return;
    }
    s=document.getElementById("pjsCanvas");
    
    s.requestPointerLock = s.requestPointerLock ||
                             s.mozRequestPointerLock ||
                             s.webkitRequestPointerLock;
    document.exitPointerLock = document.exitPointerLock ||
                           document.mozExitPointerLock ||
                           document.webkitExitPointerLock;
    
    document.addEventListener("mousemove", function(e) {
        var movementX = e.movementX ||
            e.mozMovementX          ||
            e.webkitMovementX       ||
            0,
            movementY = e.movementY ||
            e.mozMovementY      ||
            e.webkitMovementY   ||
            0;
        __lastX+=movementX;
        __lastY+=movementY;
        mouseX=__lastX;
        mouseY=__lastY;
    }, false);
}
// END NATIVE JS CODE

// PROCESSING NATIVE WRAPPER
// wraps the native __ functions so it won't crash if they don't exist
// may slow stuff down with all the try/catch
void requestPointerLock(){
    try {
        __requestPointerLock();
    } catch(Exception e){}
}
void releasePointerLock(){
    try {
        __releasePointerLock();
    } catch(Exception e){}
}

boolean isPointerLocked(){
    try {
        return __isPointerLocked();
    } catch(Exception e){
        return false;
    }
}

void initLocking(){
    try {
        __initLocking();
    } catch(Exception e){}
}
// END NATIVE WRAPPER

void setup() { // set it up
    size(400, 400, OPENGL);
    frameRate(120);
    cam = new PMatrix3D();
    hint(ENABLE_OPENGL_4X_SMOOTH);
    ///////// ENABLE FOR ERROR REPORTING AND WORSE PERFORMANCE
    hint(DISABLE_OPENGL_ERROR_REPORT);
    
    settings=new HashMap<String, Object>();
    
    sky=new Sky();
    sky.init();
    
    root=new Node(null);
    bulletNode=new Node(root);
    bulletNode.type="bulletnode";
    root.shapes.add(bulletNode);
    particlesNode=new Node(root);
    particlesNode.type="particlesnode";
    root.shapes.add(particlesNode);
    
    //root.shapes.add(new EpicTestPlane(root));
    root.shapes.add(new EpicTestBox(root));
    //root.shapes.add(new MapRendererNode(root, mapShape));
    
    
    initLocking();
    
    extraFrame=random(300);
    tx=random(-50, 50);
    ty=random(-50, 50);
    tvx=random(-1.0, 1.0);
    tvy=random(-1.0, 1.0);

    lastUpdateTime=millis();
}


void draw() {
    if(!focused && frameCount!=1){
        long currentTime=millis();
        delta+=(currentTime-lastUpdateTime);
        lastUpdateTime=currentTime;
        if(delta>0){
            delta-=1000/25;
            extraFrame++;
            if(extraFrame>300){
                extraFrame=0;
            }
            pushMatrix();
        
            tx+=tvx;
            ty+=tvy;
            if(tx<-50 || tx>50){tvx=-tvx;}
            if(ty<-50 || ty>50){tvy=-tvy;}

            translate(200+tx, 200+ty);
            rotateY((extraFrame)*(TWO_PI/300));
            rotateX((extraFrame%150)*(TWO_PI/150));
            background(0);
            stroke(255);

            if(extraFrame<100){
                fill(lerpColor(color(255, 0, 0), color(0, 255, 0), extraFrame/100));
            } else if(extraFrame<200){
                fill(lerpColor(color(0, 255, 0), color(0, 0, 255), (extraFrame-100)/100));
            } else {
                fill(lerpColor(color(0, 0, 255), color(255, 0, 0), (extraFrame-200)/100));
            } 
            textSize(20);
            text("Epic FPS Game\nClick to focus", -50, -20);
            popMatrix();
        }
        return;
    }
    
    // draw it
    noLights();
    // BACKGROUND / CAM
    background(0);
    int deltaX=oldMouseX-mouseX;
    int deltaY=oldMouseY-mouseY;
    oldMouseX=mouseX;
    oldMouseY=mouseY;
    camXRot+=deltaX/100.0;
    camYRot+=deltaY/100.0;
    camXRot=camXRot%TWO_PI;
    if(camYRot>HALF_PI){camYRot=HALF_PI;}
    if(camYRot<-HALF_PI){camYRot=-HALF_PI;}
    cam.reset();
    cam.rotateY(camXRot);
    cam.rotateX(camYRot);


    x = cam.mult(new PVector(1, 0, 0), new PVector(0, 0, 0));
    y = cam.mult(new PVector(0, 1, 0), new PVector(0, 0, 0));
    d = x.cross(y);
    d.normalize();
    d2 = new PVector();
    d2.set(d);
    d.mult(R);
    d2.mult(moveSpeed);
    d3=new PVector();
    d3.set(x);
    d3.normalize();
    d3.mult(moveSpeed);
    
    // update
    long currentTime=millis();
    delta+=(currentTime-lastUpdateTime);
    lastUpdateTime=currentTime;
    while(delta>0){
        delta-=tickSpeed;
        updateWorld(d);
    }
    
    
    // -- set up perspective
    float fov = PI/3.0;
    float cameraZ = (height/2.0) / tan(fov/2.0);
    perspective(fov, float(width)/float(height), cameraZ/10.0, cameraZ*1000.0);
    
    camera(0, 0, 0, d.x, d.y, d.z, y.x, y.y, y.z);
    // RENDER SKYBOX
    noStroke();
    hint(DISABLE_DEPTH_TEST);
    sky.render();
    hint(ENABLE_DEPTH_TEST);
    
    // TRANSLATE to player's position here
    translate(camX, camY, camZ);
    
    // LIGHTS
    lightSpecular(200, 30, 30);
    directionalLight(249, 250, 91, -3, 4, -4);
    ambientLight(255, 255, 255);
    shininess(5);
    specular(200, 200, 200);
    emissive(20,20,20);
    
    // RENDER WORLD
    root.render();
    
    // RENDER HUD
    hint(DISABLE_DEPTH_TEST); // disable z-buffering (causes stuff to go in front of hud)
    renderHud(d);
    hint(ENABLE_DEPTH_TEST); // enable it again for the next render
} 

void renderHud(PVector d){
    camera(); // CROSSHAIR & text
    stroke(255);
    fill(255);
    textSize(16);
    text("HINT: hold right click for epicness\ndebug stuff: \nbullets:"+bulletNode.shapes.size()+",\nd="+d+"\nfps:"+frameRate+"\nrot:\n"+camXRot+"\n"+camYRot+"\npos:\n"+camX+"\n"+camY+"\n"+camZ, 0, 0);
    line(width / 2 - 9, height / 2 - 0, width / 2 + 8, height / 2 + 0);
    line(width / 2 - 0, height / 2 - 9, width / 2 + 0, height / 2 + 8);
    if(frameCount==1){
        background(0);
    }
}

void updateWorld(PVector d){
    if(Bullet.count>0){
        Bullet.count--;
    }
    
     if(wpress){
        camX-=d2.x;
        camY-=d2.y;
        camZ-=d2.z;
    }
    if(apress){
        camX-=d3.x;
        camY-=d3.y;
        camZ-=d3.z;
    }
    if(spress){
        camX+=d2.x;
        camY+=d2.y;
        camZ+=d2.z;
    }
    if(dpress){
        camX+=d3.x;
        camY+=d3.y;
        camZ+=d3.z;
    }
    
    root.update();
    
    if((Bullet.fireGun) && Bullet.count==0){
        Bullet.count=10;
        bulletNode.shapes.add(new Bullet(d, bulletNode));
        
        for(int i=0;i<30;i++){
            particlesNode.shapes.add(new FireParticle(d.x+random(-80,80),d.y+random(-80,80),d.z+random(-80,80),5,particlesNode));
            }
        if(Bullet.shotGun){
            for(int i=0;i<10;i++)bulletNode.shapes.add(new Bullet(new PVector(random(-R, R), random(-R, R), random(-R, R)), bulletNode));
        }
    }
    for(int i=0;i<bulletNode.shapes.size();i++) {
        if(bulletNode.shapes.get(i).v.dist(d)>1000)
            bulletNode.shapes.remove(i);
    }
    for(int i=0;i<particlesNode.shapes.size();i++) {
        if(particlesNode.shapes.get(i).age<=0)
            particlesNode.shapes.remove(i);
        // eventually, make this into a generalized particle system
        // where all particles have ages
    }
}

void mousePressed() {
    requestPointerLock();
    Bullet.fireGun=true;
    if(mouseButton==RIGHT) {
        Bullet.shotGun=true;
    }
}
void mouseReleased() {
   Bullet.fireGun=false;
   Bullet.shotGun=false;
}
void keyPressed(){
    if(key=='r'){
        camXRot=0;
        camYRot=0;
        camX=0;
        camY=0;
        camZ=0;
    }
    if(key=='w'){
        wpress=true;
    }
    if(key=='a'){
        apress=true;
    }
    if(key=='s'){
        spress=true;
    }
    if(key=='d'){
        dpress=true;
    }
}
void keyReleased(){
    if(key=='w'){
        wpress=false;
    }
    if(key=='a'){
        apress=false;
    }
    if(key=='s'){
        spress=false;
    }
    if(key=='d'){
        dpress=false;
    }
}

class Object3D { // extend
    Node parent;
    String type="object3d"; // USE ME TO CHECK OBJECT TYPE
    float x=0, y=0, z=0, rx=0, ry=0, rz=0;
    float sc=1;
    Object3D(Node p){
        this.parent=p;
    }
    // put stuffs here
    void render(){
        pushMatrix();
        __transform();
        renderObject();
        popMatrix();
    }
    void renderObject(){} // extend me
    void update(){}       // me too
    
    void __transform() { // __ = private inner function
        translate(x, y, z);
        rotateX(rx);
        rotateY(ry);
        rotateZ(rz);
        scale(sc);
    }
}

class Node extends Object3D {
    ArrayList<Node> shapes;
    Node(Node p){
        super(p);
        type="node";
        shapes=new ArrayList<Node>();
    }
    void render() {
        pushMatrix();
        __transform();
        for(int i=0;i<shapes.size();i++)shapes.get(i).render();
        popMatrix();
    }
    void update(){
        for(int i=0;i<shapes.size();i++)shapes.get(i).update(delta);
    }
}

class Bullet extends Object3D {
    static int count=0;
    static boolean fireGun=false, shotGun=false;
    PVector v;
    Bullet(PVector d, Node p){
        super(p);
        v = d;
        x=-camX;
        y=-camY;
        z=-camZ;
        type="bullet";
    }
    void update() {
        x+=v.x;
        y+=v.y;
        z+=v.z;
    }
    void renderObject(){
        fill(50);
        noStroke();
        sphere(30);
    }
}

class EpicTestPlane extends Object3D {
    EpicTestPlane(Node p){
        super(p);
        type="epictestplane";
        y=100;
        sc=10000;
    }
    void renderObject(){
        fill(0, 100, 0);
        stroke(0);
        beginShape(QUADS);
        
        vertex(-0.5,  0.5,  0.5);
        vertex( 0.5,  0.5,  0.5);
        vertex( 0.5,  0.5, -0.5);
        vertex(-0.5,  0.5, -0.5);
        
        endShape();
    }
}

class EpicTestBox extends Object3D {
    EpicTestBox(Node p){
        super(p);
        type="epictestplane";
        y=100;
        x=100;
        rx=1;
        rz=0.44;
        sc=100;
    }
    void renderObject(){
        fill(255, 0, 0);
        stroke(0);
        box(1);
    }
}

class MapRendererNode extends Object3D {
    PShape map;
    MapRendererNode(Node p, PShape s){
        super(p);
        type="maprenderernode";
        y=100;
        sc=100;
        map=s;
    }
    void renderObject(){
        shape(map, 0, 0);
    }
}

class FireParticle extends Object3D {
    int age, vx, vy;
    int r, g, b;
    FireParticle(int x, int y, int z, int age, Node p){
        super(p);
        this.age = age;
        this.x=x-camX;
        this.y=y-camY;
        this.z=z-camZ;
        this.vx= random(-20,20);
        this.vy = random(-20,20);
        this.r=random(160, 255);
        this.g=random(50, 150);
        this.b=random(100);
        type="fireparticle";
    }
    void update(){
        age--;
        x+=vx;
        y+=vy;
        y++;
    }
    void renderObject(){
        fill(r, g, b);
        sphere(5);
    }
}
    
class Sky {
    PImage skytex;
    void init(){
        skytex = loadImage("/static/uploaded_resources/p.13415/sky.jpg");
    }
    void render(){
        pushMatrix();
        textureMode(NORMALIZED);
        scale(100);
        fill(255);
        beginShape(QUADS);
        texture(skytex);
        
        // +Z "front" face
        vertex(-1, -1,  1, 0, 0);
        vertex( 1, -1,  1, 1, 0);
        vertex( 1,  1,  1, 1, 1);
        vertex(-1,  1,  1, 0, 1);
        
        // -Z "back" face
        vertex( 1, -1, -1, 0, 0);
        vertex(-1, -1, -1, 1, 0);
        vertex(-1,  1, -1, 1, 1);
        vertex( 1,  1, -1, 0, 1);

        // +Y "bottom" face
        vertex(-1,  1,  1, 0, 0);
        vertex( 1,  1,  1, 1, 0);
        vertex( 1,  1, -1, 1, 1);
        vertex(-1,  1, -1, 0, 1);

        // -Y "top" face
        vertex(-1, -1, -1, 0, 0);
        vertex( 1, -1, -1, 1, 0);
        vertex( 1, -1,  1, 1, 1);
        vertex(-1, -1,  1, 0, 1);

        // +X "right" face
        vertex( 1, -1,  1, 0, 0);
        vertex( 1, -1, -1, 1, 0);
        vertex( 1,  1, -1, 1, 1);
        vertex( 1,  1,  1, 0, 1);

        // -X "left" face
        vertex(-1, -1, -1, 0, 0);
        vertex(-1, -1,  1, 1, 0);
        vertex(-1,  1,  1, 1, 1);
        vertex(-1,  1, -1, 0, 1);

        endShape();
        popMatrix();
    }
}