Jelajahi Sumber

Working on controls.

Cixo Develop 9 bulan lalu
induk
melakukan
f0401fdf17
8 mengubah file dengan 436 tambahan dan 35 penghapusan
  1. 114 0
      assets/actor.js
  2. 70 10
      assets/coordinates.js
  3. 30 0
      assets/directions.js
  4. 48 0
      assets/position.js
  5. 5 2
      assets/push.js
  6. 58 9
      assets/render-engine.js
  7. 105 9
      assets/scene-ui.js
  8. 6 5
      assets/scene.js

+ 114 - 0
assets/actor.js

@@ -0,0 +1,114 @@
+import { coordinates } from "./coordinates.js";
+import { position } from "./position.js";
+import { moving_directions } from "./directions.js";
+import { rotating_directions } from "./directions.js";
+import { is_moving_direction } from "./directions.js";
+import { is_rotating_direction } from "./directions.js";
+
+class actor extends coordinates {
+    #moving;
+    #rotating;
+    #speed;
+
+    constructor() {
+        super();
+
+        this.#speed = 0.25;    
+        this.#moving = moving_directions.stop;
+        this.#rotating = moving_directions.stop;
+    }
+
+    set speed(target) {
+        if (typeof(target) !== "number") {
+            throw new TypeError("Speed must be an number.");
+        }
+
+        this.#speed = target;
+    }
+
+    get speed() {
+        return this.#speed;
+    }
+
+    rotate_cursor(cursor, size) {
+        if (!(cursor instanceof position)) {
+            throw new TypeError("Cursor compare must be an position object.");
+        }
+
+        if (!(size instanceof position)) {
+            throw new TypeError("Size must be an position object.");
+        }
+
+        const rotate = cursor.x / size.x * 360;        
+        this.rotate_clockwise(rotate);
+    }
+
+    set moving(target) {
+        if (!is_moving_direction(target)) {
+            throw new TypeError("Direction must be in directions.");
+        }
+
+        this.#moving = target;
+    }
+
+    get moving() {
+        return this.#moving;
+    }
+
+    set rotating(target) {
+        if (!is_rotating_direction(target)) {
+            throw new TypeError("Direction must be in directions.");
+        }
+
+        this.#rotating = target;
+    }
+
+    get rotating() {
+        return this.#rotating;
+    }
+
+    update() {
+        this.#update_position();
+        this.#update_rotation();
+    }
+
+    #update_rotation() {
+        switch (this.#rotating) {
+            case rotating_directions.clockwise:
+                this.rotate_clockwise(this.#speed * 10);
+                return;
+
+            case rotating_directions.counterclockwise:
+                this.rotate_counterclockwise(this.#speed * 10);
+                return;
+
+            default:
+                return;
+        }
+    }
+
+    #update_position() {
+        switch (this.#moving) {
+            case moving_directions.front:
+                this.move_front(this.#speed);
+                return;
+
+            case moving_directions.back:
+                this.move_back(this.#speed);
+                return;
+
+            case moving_directions.left:
+                this.move_left(this.#speed);
+                return;
+
+            case moving_directions.right:
+                this.move_right(this.#speed);
+                return;
+
+            default:
+                return;
+        }
+    }
+}
+
+export { actor };

+ 70 - 10
assets/coordinates.js

@@ -3,12 +3,14 @@ class coordinates {
     #y;
     #z;
     #rotate;
+    #head_rotate;
 
     constructor() {
         this.#x = 0;
         this.#y = 0;
         this.#z = 0;
         this.#rotate = 0;
+        this.#head_rotate = 0;
     }
 
     get x() {
@@ -24,6 +26,8 @@ class coordinates {
     }
 
     get rotate() {
+        this.#check_rotate();
+        
         return this.#rotate;
     }
 
@@ -37,7 +41,7 @@ class coordinates {
 
     set y(target) {
         if (typeof(target) !== "number") {
-            throw new TypeError("Coordinate must be numer.");
+            throw new TypeError("Coordinate must be number.");
         }
 
         this.#y = target;
@@ -45,7 +49,7 @@ class coordinates {
 
     set z(target) {
         if (typeof(target) !== "number") {
-            throw new TypeError("Coordinate must be numer.");
+            throw new TypeError("Coordinate must be number.");
         }
 
         this.#z = target;
@@ -53,10 +57,39 @@ class coordinates {
 
     set rotate(target) {
         if (typeof(target) !== "number") {
-            throw new TypeError("Coordinate must be numer.");
+            throw new TypeError("Coordinate must be number.");
         }
 
-        this.#rotate = target % 360;
+        this.#rotate = target;
+        this.#check_rotate();
+    }
+
+    #check_rotate() {
+        while (this.#rotate < 0) {
+            this.#rotate += 360;
+        }
+
+        this.#rotate = this.#rotate % 360;
+    }
+
+    set head_rotate(target) {
+        if (typeof(target) !== "number") {
+            throw new TypeError("Coordinate must be number.");
+        }
+
+        this.#head_rotate = target % 90;
+        this.#check_head_rotate();
+    }   
+
+    #check_head_rotate() {
+        if (this.#head_rotate > 90) this.#head_rotate = 90;
+        if (this.#head_rotate < -90) this.#head_rotate = -90;
+    }
+
+    get head_rotate() {
+        this.#check_head_rotate();
+
+        return this.#head_rotate;
     }
 
     #radians(change = 0) {
@@ -68,8 +101,8 @@ class coordinates {
             throw new TypeError("Steps must be number.");
         }
     
-        this.#y += Math.sin(this.#radians()) * target;
-        this.#x += Math.cos(this.#radians()) * target;
+        this.#y -= Math.cos(this.#radians()) * target;
+        this.#x -= Math.sin(this.#radians()) * target;
     }
 
     move_back(target = 1) {
@@ -85,8 +118,8 @@ class coordinates {
             throw new TypeError("Steps must be number.");
         }
         
-        this.#y += Math.sin(this.#radians(-90)) * target;
-        this.#x += Math.cos(this.#radians(-90)) * target;
+        this.#y += Math.cos(this.#radians(-90)) * target;
+        this.#x += Math.sin(this.#radians(-90)) * target;
     }
 
     move_right(target = 1) {
@@ -97,7 +130,7 @@ class coordinates {
         this.move_left(-target);
     }
 
-    rotate_clockwise(target = 0) {
+    rotate_clockwise(target = 1) {
         if (typeof(target) !== "number") {
             throw new TypeError("Degrees must be number.");
         }
@@ -105,13 +138,40 @@ class coordinates {
         this.#rotate -= target;
     }
 
-    rotate_counterclockwise(target = 0) {
+    rotate_counterclockwise(target = 1) {
         if (typeof(target) !== "number") {
             throw new TypeError("Degrees must be number.");
         }
 
         this.#rotate += target;
     }
+
+    rotate_top(target = 1) {
+        if (typeof(target) !== "number") {
+            throw new TypeError("Degrees must be number.");
+        }
+
+        this.#rotate + target;
+    }
+
+    rotate_bottom(target = 1) {
+        if (typeof(target) !== "number") {
+            throw new TypeError("Degrees must be number.");
+        }
+
+        this.#rotate -= target;
+    }
+
+    get as_string() {
+        let dump = "";
+        dump += "X: " + this.x + "\n";
+        dump += "Y: " + this.y + "\n";
+        dump += "Z: " + this.z + "\n";
+        dump += "ROTATE: " + this.rotate;
+        dump += "HEAD ROTATE: " + this.head_rotate;
+
+        return dump;
+    }
 }
 
 export { coordinates };

+ 30 - 0
assets/directions.js

@@ -0,0 +1,30 @@
+const moving_directions = Object.freeze({
+    front: "front",
+    back: "back",
+    left: "left",
+    right: "right",
+    stop: "stop"
+});
+
+const rotating_directions = Object.freeze({
+    clockwise: "clockwise",
+    counterclockwise: "counterclockwise",
+    top: "top",
+    bottom: "bottom",
+    stop: "stop"
+});
+
+const is_moving_direction = (target) => {
+    return Object.values(moving_directions).includes(target);
+}
+
+const is_rotating_direction = (target) => {
+    return Object.values(rotating_directions).includes(target);
+}   
+
+export { 
+    moving_directions, 
+    is_moving_direction,  
+    rotating_directions, 
+    is_rotating_direction
+};

+ 48 - 0
assets/position.js

@@ -0,0 +1,48 @@
+class position {
+    #x;
+    #y;
+
+    constructor(x = 0, y = 0) {
+        this.x = x;
+        this.y = y;
+    }
+
+    set x(target) {
+        if (typeof(target) !== "number") {
+            throw new TypeError("X cord must be an number.");
+        }
+
+        this.#x = target;
+    }
+
+    set y(target) {
+        if (typeof(target) !== "number") {
+            throw new TypeError("Y cord must be an number.");
+        }
+
+        this.#y = target;
+    }
+
+    get x() {
+        return this.#x;
+    }
+
+    get y() {
+        return this.#y;
+    }
+
+    compare(target) {
+        if (!(target instanceof position)) {
+            throw new TypeError("Can only compare position with position.");   
+        }
+
+        const result = new position();
+
+        result.x = target.x - this.x;
+        result.y = target.y - this.y;
+
+        return result;
+    }
+}
+
+export { position };

+ 5 - 2
assets/push.js

@@ -1,5 +1,5 @@
 const push = (name, action, customisation = null) => {
-    if (typeof(action) !== "function") {
+    if (action !== null && typeof(action) !== "function") {
         throw new TypeError("Action must be an function.");
     }
 
@@ -23,7 +23,10 @@ const push = (name, action, customisation = null) => {
         .replaceAll("_", " ");
 
     target.classList.add("push");
-    target.addEventListener("click", action);
+    
+    if (action !== null) {
+        target.addEventListener("click", action);
+    }
 
     if (customisation) {
         customisation(target);

+ 58 - 9
assets/render-engine.js

@@ -1,4 +1,5 @@
 import * as three from "three-js";
+import { actor } from "./actor.js";
 
 class render_engine {
     #renderer;
@@ -6,28 +7,38 @@ class render_engine {
     #camera;
     #canvas;
     #actors;
+    #player;
 
-    constructor(canvas, context) {
+    constructor(canvas, context, player) {
         if (!(canvas instanceof HTMLCanvasElement)) {
-            throw TypeError("Canvas must be an HTMLCanvasElement.");
+            throw new TypeError("Canvas must be an HTMLCanvasElement.");
         }
 
         if (!(context instanceof WebGL2RenderingContext)) {
-            throw TypeError("Context must be WebGL2 RenderingContext.");
+            throw new TypeError("Context must be WebGL2 RenderingContext.");
+        }
+
+        if (!(player instanceof actor)) {
+            throw new TypeError("Player must be an actor.");
         }
 
         this.#actors = {};
+        this.#player = player;
         this.#canvas = canvas;
         this.#scene = new three.Scene();
+        this.#scene.background = new three.Color(0x101010);
         this.#camera = new three.PerspectiveCamera(75, 1, 0.1, 1000);
 
         this.#renderer = new three.WebGLRenderer({ 
             canvas: this.#canvas,
             context: context
         });
+
+        this.#renderer.shadowMap.enable = true;
+        this.#renderer.shadowMap.type = three.BasicShadowMap;
    
-        this.#update_camera();
         this.#resize_canvas();
+        this.#update_camera();
 
         window.addEventListener("resize", () => {
             this.#resize_canvas();
@@ -39,29 +50,67 @@ class render_engine {
         this.#canvas.width = window.innerWidth;
         this.#canvas.height = window.innerHeight;
 
-        this.#renderer.setSize(window.innerWidth, window.innerHeight);
+        this.#renderer.setSize(this.#canvas.width, this.#canvas.height);
     }
 
     #update_camera() {
         this.#camera.aspect = this.#canvas.width / this.#canvas.height;
+        this.#camera.updateProjectionMatrix();
     }
 
     run() {
         this.#init();
-        this.#renderer.setAnimationLoop(() => { this.#render(); });
+        //this.#renderer.setAnimationLoop(() => { this.#render(); });
+        this.#loop();
+    }
+
+    #loop() {
+        const start = performance.now();
+        this.#render();
+        const stop = performance.now();
+        const tooked = stop - start;
+        const new_frame = 1000 / 60 - tooked;
+
+        if (new_frame <= 0) {
+            setTimeout(() => { this.#loop(); }, 1);
+            return;
+        } 
+
+        setTimeout(() => { this.#loop(); }, new_frame);
     }
 
     #init() {
         const geometry = new three.BoxGeometry(1, 1, 1);
-        const material = new three.MeshBasicMaterial({ color: 0x004000 });
+        const material = new three.MeshStandardMaterial({ color: 0x009000 });
         this.#actors.cube = new three.Mesh(geometry, material);
+        this.#actors.cube.position.x = 10;
+        this.#actors.cube.position.y = 0;
+        this.#actors.cube.position.z = 10;
+
+        const light = new three.HemisphereLight(0x707070);
+        light.position.x = 0;
+        light.position.y = 10;
+        light.position.z = 0;
 
         this.#scene.add(this.#actors.cube);
+        this.#scene.add(light);
+
+        this.#camera.position.y = 0;
+        this.#player.rotate = 270;
+    }
+
+    #sync_camera() {
+        this.#player.update();
 
-        this.#camera.position.z = 5;
+        this.#camera.position.x = this.#player.x;
+        this.#camera.position.z = this.#player.y;
+        
+        this.#camera.rotation.y 
+        = three.MathUtils.degToRad(this.#player.rotate);
     }
 
     #render() {
+        this.#sync_camera();
         this.#actors.cube.rotation.x += 0.01;
         this.#actors.cube.rotation.y += 0.01;
 
@@ -69,4 +118,4 @@ class render_engine {
     }
 }
 
-export { render_engine };
+export { render_engine };

+ 105 - 9
assets/scene-ui.js

@@ -2,10 +2,16 @@ import { scene } from "./scene.js";
 import { push } from "./push.js";
 import { material_icon } from "./icons.js";
 import { container } from "./container.js";
+import { moving_directions } from "./directions.js";
+import { rotating_directions } from "./directions.js";
+import { is_moving_direction } from "./directions.js";
+import { is_rotating_direction } from "./directions.js";
+import { position } from "./position.js";
 
 class scene_ui {
     #box;
     #worker;
+    #last_mouse_position;
     #step_left;
     #step_right;
     #step_front;
@@ -18,34 +24,39 @@ class scene_ui {
             throw new TypeError("Worker must be instance of scene.");
         }
 
+        this.#last_mouse_position = undefined;
         this.#worker = worker;
 
         this.#step_front = this.#create_push(
-            "front", "arrow_drop_up", position => { position.move_front(); }
+            "front", "arrow_drop_up", moving_directions.front
         );
         
         this.#step_back = this.#create_push(
-            "back", "arrow_drop_down", position => { position.move_back(); }
+            "back", "arrow_drop_down", moving_directions.back
         );
 
         this.#step_left = this.#create_push(
-            "left", "arrow_left", position => { position.move_left(); }
+            "left", "arrow_left", moving_directions.left
         );
 
         this.#step_right = this.#create_push(
-            "right", "arrow_right", position => { position.move_right(); }
+            "right", "arrow_right", moving_directions.right
         );
 
         this.#rotate_clockwise = this.#create_push(
             "clockwise", "rotate_right", 
-            position => { position.rotate_clockwise(); }
+            rotating_directions.clockwise
         );
 
         this.#rotate_countclockwise = this.#create_push(
             "countclockwise", "rotate_left", 
-            position => { position.rotate_countclockwise(); }
+            rotating_directions.counterclockwise
         );
 
+        this.#setup_stopers();
+        this.#setup_keybind();
+        this.#setup_mousebind();
+
         this.#box = container("controls", (root) => {
             root.appendChild(container("top", (top) => {
                 top.appendChild(this.#rotate_countclockwise);
@@ -65,12 +76,97 @@ class scene_ui {
         return this.#box;
     }
 
+    #setup_keybind() {
+        document.addEventListener("keydown", (action) => {
+            switch (action.key) {
+                case "w":
+                    this.#worker.player.moving = moving_directions.front;
+                    break;
+                
+                case "s":
+                    this.#worker.player.moving = moving_directions.back;
+                    break;
+
+                case "d":
+                    this.#worker.player.moving = moving_directions.right;
+                    break;
+
+                case "a":
+                    this.#worker.player.moving = moving_directions.left;
+                    break;
+
+                default:
+                    break;
+            }
+        });
+    
+        document.addEventListener("keyup", (action) => {
+            switch (action.key) {
+                case "w":
+                case "s":
+                case "a":
+                case "d":
+                    this.#worker.player.moving = moving_directions.stop;
+                    break;
+
+                default:
+                    break;
+            }
+        });
+    }
+
+    #setup_mousebind() {
+        document.addEventListener("mouseout", (action) => {
+            this.#last_mouse_position = undefined;
+        });
+
+        document.addEventListener("mousemove", (action) => {
+            const from_mouse = () => {
+                return new position(action.clientX, action.clientY);
+            };
+
+            if (this.#last_mouse_position === undefined) {
+                this.#last_mouse_position = from_mouse();
+                return;
+            }
+
+            const current = from_mouse();
+            const difference = current.compare(this.#last_mouse_position);
+            const size = new position(window.innerWidth, window.innerHeight);
+
+            this.#last_mouse_position = current;
+            this.#worker.player.rotate_cursor(difference, size);
+        });
+    }
+
+    #setup_stopers() {
+        const stoper = () => {
+            this.#worker.player.moving = moving_directions.stop;
+            this.#worker.player.rotating = rotating_directions.stop;
+        };
+
+        document.addEventListener("mouseup", stoper);
+        document.addEventListener("mouseover", stoper);
+    }
+
     #create_push(name, icon, move) {
-        return push(name, () => {
-            move(this.#worker.position);
-        }, (target) => {
+        if (!is_moving_direction(move) && !is_rotating_direction(move)) {
+            throw new TypeError("Move muse be an direction.");
+        }
+
+        return push(name, null, (target) => {
             target.innerText = "";
             target.appendChild(material_icon(icon));
+            
+            if (is_moving_direction(move)) {
+                target.addEventListener("mousedown", () => {
+                    this.#worker.player.moving = move;
+                });
+            } else if (is_rotating_direction(move)) {
+                target.addEventListener("mousedown", () => {
+                    this.#worker.player.rotating = move;
+                });
+            }
         });
     }
 }

+ 6 - 5
assets/scene.js

@@ -1,13 +1,14 @@
 import { coordinates } from "./coordinates.js";
+import { actor } from "./actor.js";
 import { render_engine } from "./render-engine.js"
 
 class scene {
     #canvas;
-    #position;
+    #player;
     #engine;
 
     constructor() {
-        this.#position = new coordinates();
+        this.#player = new actor();
         this.#canvas = document.createElement("canvas");
         const context = this.#canvas.getContext("webgl2");
 
@@ -15,12 +16,12 @@ class scene {
             throw new TypeError("Browser does not support WebGL.");
         }
 
-        this.#engine = new render_engine(this.#canvas, context);
+        this.#engine = new render_engine(this.#canvas, context, this.#player);
         this.#canvas.classList.add("space-render");
     }
 
-    get position() {
-        return this.#position;
+    get player() {
+        return this.#player;
     }
 
     get canvas() {