Explorar o código

Initial commit.

Simon %!s(int64=3) %!d(string=hai) anos
pai
achega
8e05c499da

+ 9 - 0
base.css

@@ -0,0 +1,9 @@
+body {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  background: #000000;
+  margin: 0;
+  padding: 0;
+  overscroll-behavior: none;
+}

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Three.JS Tutorial: First Person Cameras</title>
+  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+  <link rel="stylesheet" type="text/css" href="base.css">
+</head>
+<body>
+  <script src="./main.js" type="module">
+  </script>
+</body>
+</html>

+ 483 - 0
main.js

@@ -0,0 +1,483 @@
+import * as THREE from 'https://cdn.skypack.dev/[email protected]';
+
+import {FirstPersonControls} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/controls/FirstPersonControls.js';
+
+
+const KEYS = {
+  'a': 65,
+  's': 83,
+  'w': 87,
+  'd': 68,
+};
+
+function clamp(x, a, b) {
+  return Math.min(Math.max(x, a), b);
+}
+
+class InputController {
+  constructor(target) {
+    this.target_ = target || document;
+    this.initialize_();    
+  }
+
+  initialize_() {
+    this.current_ = {
+      leftButton: false,
+      rightButton: false,
+      mouseXDelta: 0,
+      mouseYDelta: 0,
+      mouseX: 0,
+      mouseY: 0,
+    };
+    this.previous_ = null;
+    this.keys_ = {};
+    this.previousKeys_ = {};
+    this.target_.addEventListener('mousedown', (e) => this.onMouseDown_(e), false);
+    this.target_.addEventListener('mousemove', (e) => this.onMouseMove_(e), false);
+    this.target_.addEventListener('mouseup', (e) => this.onMouseUp_(e), false);
+    this.target_.addEventListener('keydown', (e) => this.onKeyDown_(e), false);
+    this.target_.addEventListener('keyup', (e) => this.onKeyUp_(e), false);
+  }
+
+  onMouseMove_(e) {
+    this.current_.mouseX = e.pageX - window.innerWidth / 2;
+    this.current_.mouseY = e.pageY - window.innerHeight / 2;
+
+    if (this.previous_ === null) {
+      this.previous_ = {...this.current_};
+    }
+
+    this.current_.mouseXDelta = this.current_.mouseX - this.previous_.mouseX;
+    this.current_.mouseYDelta = this.current_.mouseY - this.previous_.mouseY;
+  }
+
+  onMouseDown_(e) {
+    this.onMouseMove_(e);
+
+    switch (e.button) {
+      case 0: {
+        this.current_.leftButton = true;
+        break;
+      }
+      case 2: {
+        this.current_.rightButton = true;
+        break;
+      }
+    }
+  }
+
+  onMouseUp_(e) {
+    this.onMouseMove_(e);
+
+    switch (e.button) {
+      case 0: {
+        this.current_.leftButton = false;
+        break;
+      }
+      case 2: {
+        this.current_.rightButton = false;
+        break;
+      }
+    }
+  }
+
+  onKeyDown_(e) {
+    this.keys_[e.keyCode] = true;
+  }
+
+  onKeyUp_(e) {
+    this.keys_[e.keyCode] = false;
+  }
+
+  key(keyCode) {
+    return !!this.keys_[keyCode];
+  }
+
+  isReady() {
+    return this.previous_ !== null;
+  }
+
+  update(_) {
+    if (this.previous_ !== null) {
+      this.current_.mouseXDelta = this.current_.mouseX - this.previous_.mouseX;
+      this.current_.mouseYDelta = this.current_.mouseY - this.previous_.mouseY;
+
+      this.previous_ = {...this.current_};
+    }
+  }
+};
+
+
+class FirstPersonCamera {
+  constructor(camera, objects) {
+    this.camera_ = camera;
+    this.input_ = new InputController();
+    this.rotation_ = new THREE.Quaternion();
+    this.translation_ = new THREE.Vector3(0, 2, 0);
+    this.phi_ = 0;
+    this.phiSpeed_ = 8;
+    this.theta_ = 0;
+    this.thetaSpeed_ = 5;
+    this.headBobActive_ = false;
+    this.headBobTimer_ = 0;
+    this.objects_ = objects;
+  }
+
+  update(timeElapsedS) {
+    this.updateRotation_(timeElapsedS);
+    this.updateCamera_(timeElapsedS);
+    this.updateTranslation_(timeElapsedS);
+    this.updateHeadBob_(timeElapsedS);
+    this.input_.update(timeElapsedS);
+  }
+
+  updateCamera_(_) {
+    this.camera_.quaternion.copy(this.rotation_);
+    this.camera_.position.copy(this.translation_);
+    this.camera_.position.y += Math.sin(this.headBobTimer_ * 10) * 1.5;
+
+    const forward = new THREE.Vector3(0, 0, -1);
+    forward.applyQuaternion(this.rotation_);
+
+    const dir = forward.clone();
+
+    forward.multiplyScalar(100);
+    forward.add(this.translation_);
+
+    let closest = forward;
+    const result = new THREE.Vector3();
+    const ray = new THREE.Ray(this.translation_, dir);
+    for (let i = 0; i < this.objects_.length; ++i) {
+      if (ray.intersectBox(this.objects_[i], result)) {
+        if (result.distanceTo(ray.origin) < closest.distanceTo(ray.origin)) {
+          closest = result.clone();
+        }
+      }
+    }
+
+    this.camera_.lookAt(closest);
+  }
+
+  updateHeadBob_(timeElapsedS) {
+    if (this.headBobActive_) {
+      const wavelength = Math.PI;
+      const nextStep = 1 + Math.floor(((this.headBobTimer_ + 0.000001) * 10) / wavelength);
+      const nextStepTime = nextStep * wavelength / 10;
+      this.headBobTimer_ = Math.min(this.headBobTimer_ + timeElapsedS, nextStepTime);
+
+      if (this.headBobTimer_ == nextStepTime) {
+        this.headBobActive_ = false;
+      }
+    }
+  }
+
+  updateTranslation_(timeElapsedS) {
+    const forwardVelocity = (this.input_.key(KEYS.w) ? 1 : 0) + (this.input_.key(KEYS.s) ? -1 : 0)
+    const strafeVelocity = (this.input_.key(KEYS.a) ? 1 : 0) + (this.input_.key(KEYS.d) ? -1 : 0)
+
+    const qx = new THREE.Quaternion();
+    qx.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.phi_);
+
+    const forward = new THREE.Vector3(0, 0, -1);
+    forward.applyQuaternion(qx);
+    forward.multiplyScalar(forwardVelocity * timeElapsedS * 10);
+
+    const left = new THREE.Vector3(-1, 0, 0);
+    left.applyQuaternion(qx);
+    left.multiplyScalar(strafeVelocity * timeElapsedS * 10);
+
+    this.translation_.add(forward);
+    this.translation_.add(left);
+
+    if (forwardVelocity != 0 || strafeVelocity != 0) {
+      this.headBobActive_ = true;
+    }
+  }
+
+  updateRotation_(timeElapsedS) {
+    const xh = this.input_.current_.mouseXDelta / window.innerWidth;
+    const yh = this.input_.current_.mouseYDelta / window.innerHeight;
+
+    this.phi_ += -xh * this.phiSpeed_;
+    this.theta_ = clamp(this.theta_ + -yh * this.thetaSpeed_, -Math.PI / 3, Math.PI / 3);
+
+    const qx = new THREE.Quaternion();
+    qx.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.phi_);
+    const qz = new THREE.Quaternion();
+    qz.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.theta_);
+
+    const q = new THREE.Quaternion();
+    q.multiply(qx);
+    q.multiply(qz);
+
+    this.rotation_.copy(q);
+  }
+}
+
+
+class FirstPersonCameraDemo {
+  constructor() {
+    this.initialize_();
+  }
+
+  initialize_() {
+    this.initializeRenderer_();
+    this.initializeLights_();
+    this.initializeScene_();
+    this.initializePostFX_();
+    this.initializeDemo_();
+
+    this.previousRAF_ = null;
+    this.raf_();
+    this.onWindowResize_();
+  }
+
+  initializeDemo_() {
+    // this.controls_ = new FirstPersonControls(
+    //     this.camera_, this.threejs_.domElement);
+    // this.controls_.lookSpeed = 0.8;
+    // this.controls_.movementSpeed = 5;
+
+    this.fpsCamera_ = new FirstPersonCamera(this.camera_, this.objects_);
+  }
+
+  initializeRenderer_() {
+    this.threejs_ = new THREE.WebGLRenderer({
+      antialias: false,
+    });
+    this.threejs_.shadowMap.enabled = true;
+    this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
+    this.threejs_.setPixelRatio(window.devicePixelRatio);
+    this.threejs_.setSize(window.innerWidth, window.innerHeight);
+    this.threejs_.physicallyCorrectLights = true;
+    this.threejs_.outputEncoding = THREE.sRGBEncoding;
+
+    document.body.appendChild(this.threejs_.domElement);
+
+    window.addEventListener('resize', () => {
+      this.onWindowResize_();
+    }, false);
+
+    const fov = 60;
+    const aspect = 1920 / 1080;
+    const near = 1.0;
+    const far = 1000.0;
+    this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
+    this.camera_.position.set(0, 2, 0);
+
+    this.scene_ = new THREE.Scene();
+
+    this.uiCamera_ = new THREE.OrthographicCamera(
+        -1, 1, 1 * aspect, -1 * aspect, 1, 1000);
+    this.uiScene_ = new THREE.Scene();
+  }
+
+  initializeScene_() {
+    const loader = new THREE.CubeTextureLoader();
+    const texture = loader.load([
+      './resources/skybox/posx.jpg',
+      './resources/skybox/negx.jpg',
+      './resources/skybox/posy.jpg',
+      './resources/skybox/negy.jpg',
+      './resources/skybox/posz.jpg',
+      './resources/skybox/negz.jpg',
+  ]);
+
+    texture.encoding = THREE.sRGBEncoding;
+    this.scene_.background = texture;
+
+    const mapLoader = new THREE.TextureLoader();
+    const maxAnisotropy = this.threejs_.capabilities.getMaxAnisotropy();
+    const checkerboard = mapLoader.load('resources/checkerboard.png');
+    checkerboard.anisotropy = maxAnisotropy;
+    checkerboard.wrapS = THREE.RepeatWrapping;
+    checkerboard.wrapT = THREE.RepeatWrapping;
+    checkerboard.repeat.set(32, 32);
+    checkerboard.encoding = THREE.sRGBEncoding;
+
+    const plane = new THREE.Mesh(
+        new THREE.PlaneGeometry(100, 100, 10, 10),
+        new THREE.MeshStandardMaterial({map: checkerboard}));
+    plane.castShadow = false;
+    plane.receiveShadow = true;
+    plane.rotation.x = -Math.PI / 2;
+    this.scene_.add(plane);
+
+    const box = new THREE.Mesh(
+      new THREE.BoxGeometry(4, 4, 4),
+      this.loadMaterial_('vintage-tile1_', 0.2));
+    box.position.set(10, 2, 0);
+    box.castShadow = true;
+    box.receiveShadow = true;
+    this.scene_.add(box);
+
+    const concreteMaterial = this.loadMaterial_('concrete3-', 4);
+
+    const wall1 = new THREE.Mesh(
+      new THREE.BoxGeometry(100, 100, 4),
+      concreteMaterial);
+    wall1.position.set(0, -40, -50);
+    wall1.castShadow = true;
+    wall1.receiveShadow = true;
+    this.scene_.add(wall1);
+
+    const wall2 = new THREE.Mesh(
+      new THREE.BoxGeometry(100, 100, 4),
+      concreteMaterial);
+    wall2.position.set(0, -40, 50);
+    wall2.castShadow = true;
+    wall2.receiveShadow = true;
+    this.scene_.add(wall2);
+
+    const wall3 = new THREE.Mesh(
+      new THREE.BoxGeometry(4, 100, 100),
+      concreteMaterial);
+    wall3.position.set(50, -40, 0);
+    wall3.castShadow = true;
+    wall3.receiveShadow = true;
+    this.scene_.add(wall3);
+
+    const wall4 = new THREE.Mesh(
+      new THREE.BoxGeometry(4, 100, 100),
+      concreteMaterial);
+    wall4.position.set(-50, -40, 0);
+    wall4.castShadow = true;
+    wall4.receiveShadow = true;
+    this.scene_.add(wall4);
+
+    // Create Box3 for each mesh in the scene so that we can
+    // do some easy intersection tests.
+    const meshes = [
+      plane, box, wall1, wall2, wall3, wall4];
+
+    this.objects_ = [];
+
+    for (let i = 0; i < meshes.length; ++i) {
+      const b = new THREE.Box3();
+      b.setFromObject(meshes[i]);
+      this.objects_.push(b);
+    }
+
+    // Crosshair
+    const crosshair = mapLoader.load('resources/crosshair.png');
+    crosshair.anisotropy = maxAnisotropy;
+
+    this.sprite_ = new THREE.Sprite(
+      new THREE.SpriteMaterial({map: crosshair, color: 0xffffff, fog: false, depthTest: false, depthWrite: false}));
+    this.sprite_.scale.set(0.15, 0.15 * this.camera_.aspect, 1)
+    this.sprite_.position.set(0, 0, -10);
+
+    this.uiScene_.add(this.sprite_);
+  }
+
+  initializeLights_() {
+    const distance = 50.0;
+    const angle = Math.PI / 4.0;
+    const penumbra = 0.5;
+    const decay = 1.0;
+
+    let light = new THREE.SpotLight(
+        0xFFFFFF, 100.0, distance, angle, penumbra, decay);
+    light.castShadow = true;
+    light.shadow.bias = -0.00001;
+    light.shadow.mapSize.width = 4096;
+    light.shadow.mapSize.height = 4096;
+    light.shadow.camera.near = 1;
+    light.shadow.camera.far = 100;
+
+    light.position.set(25, 25, 0);
+    light.lookAt(0, 0, 0);
+    this.scene_.add(light);
+
+    const upColour = 0xFFFF80;
+    const downColour = 0x808080;
+    light = new THREE.HemisphereLight(upColour, downColour, 0.5);
+    light.color.setHSL( 0.6, 1, 0.6 );
+    light.groundColor.setHSL( 0.095, 1, 0.75 );
+    light.position.set(0, 4, 0);
+    this.scene_.add(light);
+  }
+
+  loadMaterial_(name, tiling) {
+    const mapLoader = new THREE.TextureLoader();
+    const maxAnisotropy = this.threejs_.capabilities.getMaxAnisotropy();
+
+    const metalMap = mapLoader.load('resources/freepbr/' + name + 'metallic.png');
+    metalMap.anisotropy = maxAnisotropy;
+    metalMap.wrapS = THREE.RepeatWrapping;
+    metalMap.wrapT = THREE.RepeatWrapping;
+    metalMap.repeat.set(tiling, tiling);
+
+    const albedo = mapLoader.load('resources/freepbr/' + name + 'albedo.png');
+    albedo.anisotropy = maxAnisotropy;
+    albedo.wrapS = THREE.RepeatWrapping;
+    albedo.wrapT = THREE.RepeatWrapping;
+    albedo.repeat.set(tiling, tiling);
+    albedo.encoding = THREE.sRGBEncoding;
+
+    const normalMap = mapLoader.load('resources/freepbr/' + name + 'normal.png');
+    normalMap.anisotropy = maxAnisotropy;
+    normalMap.wrapS = THREE.RepeatWrapping;
+    normalMap.wrapT = THREE.RepeatWrapping;
+    normalMap.repeat.set(tiling, tiling);
+
+    const roughnessMap = mapLoader.load('resources/freepbr/' + name + 'roughness.png');
+    roughnessMap.anisotropy = maxAnisotropy;
+    roughnessMap.wrapS = THREE.RepeatWrapping;
+    roughnessMap.wrapT = THREE.RepeatWrapping;
+    roughnessMap.repeat.set(tiling, tiling);
+
+    const material = new THREE.MeshStandardMaterial({
+      metalnessMap: metalMap,
+      map: albedo,
+      normalMap: normalMap,
+      roughnessMap: roughnessMap,
+    });
+
+    return material;
+  }
+
+  initializePostFX_() {
+  }
+
+  onWindowResize_() {
+    this.camera_.aspect = window.innerWidth / window.innerHeight;
+    this.camera_.updateProjectionMatrix();
+
+    this.uiCamera_.left = -this.camera_.aspect;
+    this.uiCamera_.right = this.camera_.aspect;
+    this.uiCamera_.updateProjectionMatrix();
+
+    this.threejs_.setSize(window.innerWidth, window.innerHeight);
+  }
+
+  raf_() {
+    requestAnimationFrame((t) => {
+      if (this.previousRAF_ === null) {
+        this.previousRAF_ = t;
+      }
+
+      this.step_(t - this.previousRAF_);
+      this.threejs_.autoClear = true;
+      this.threejs_.render(this.scene_, this.camera_);
+      this.threejs_.autoClear = false;
+      this.threejs_.render(this.uiScene_, this.uiCamera_);
+      this.previousRAF_ = t;
+      this.raf_();
+    });
+  }
+
+  step_(timeElapsed) {
+    const timeElapsedS = timeElapsed * 0.001;
+
+    // this.controls_.update(timeElapsedS);
+    this.fpsCamera_.update(timeElapsedS);
+  }
+}
+
+
+let _APP = null;
+
+window.addEventListener('DOMContentLoaded', () => {
+  _APP = new FirstPersonCameraDemo();
+});

BIN=BIN
resources/checkerboard.png


BIN=BIN
resources/crosshair.png


BIN=BIN
resources/freepbr/concrete3-albedo.png


BIN=BIN
resources/freepbr/concrete3-metallic.png


BIN=BIN
resources/freepbr/concrete3-normal.png


BIN=BIN
resources/freepbr/concrete3-roughness.png


+ 2 - 0
resources/freepbr/readme.txt

@@ -0,0 +1,2 @@
+These textures were all taken from https://freepbr.com/
+

BIN=BIN
resources/freepbr/vintage-tile1_albedo.png


BIN=BIN
resources/freepbr/vintage-tile1_ao.png


BIN=BIN
resources/freepbr/vintage-tile1_height.png


BIN=BIN
resources/freepbr/vintage-tile1_metallic.png


BIN=BIN
resources/freepbr/vintage-tile1_normal.png


BIN=BIN
resources/freepbr/vintage-tile1_roughness.png


BIN=BIN
resources/skybox/negx.jpg


BIN=BIN
resources/skybox/negy.jpg


BIN=BIN
resources/skybox/negz.jpg


BIN=BIN
resources/skybox/posx.jpg


BIN=BIN
resources/skybox/posy.jpg


BIN=BIN
resources/skybox/posz.jpg


+ 13 - 0
resources/skybox/readme.txt

@@ -0,0 +1,13 @@
+Author
+======
+
+This is the work of Emil Persson, aka Humus.
+http://www.humus.name
+
+
+
+License
+=======
+
+This work is licensed under a Creative Commons Attribution 3.0 Unported License.
+http://creativecommons.org/licenses/by/3.0/