You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

373 lines
9.1 KiB
JavaScript

import * as THREE from "https://cdn.skypack.dev/three@0.129.0";
import Stats from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/libs/stats.module.js";
import { GLTFLoader } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/loaders/DRACOLoader.js";
import { Octree } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/math/Octree.js";
import { Capsule } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/math/Capsule.js";
import { DeviceOrientationControls } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/controls/DeviceOrientationControls.js";
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
let mixer;
let controls;
const clock = new THREE.Clock();
const extScene = new THREE.Scene();
const intScene = new THREE.Scene();
let scene = extScene;
let intSceneisLoaded = false;
extScene.background = new THREE.Color(0x88ccff);
intScene.background = new THREE.Color(0xffcc88);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.rotation.order = "YXZ";
if (isMobile) {
controls = new DeviceOrientationControls(camera);
}
// LIGHTS
const ambientlight = new THREE.AmbientLight(0xb3c3e6);
extScene.add(ambientlight);
intScene.add(ambientlight.clone());
const fillLight1 = new THREE.DirectionalLight(0xffffee, 0.2);
fillLight1.position.set(-1, 2, 2);
extScene.add(fillLight1);
intScene.add(fillLight1.clone());
const fillLight2 = new THREE.DirectionalLight(0xffffee, 0.2);
fillLight2.position.set(0, 3, 0);
extScene.add(fillLight2);
intScene.add(fillLight2.clone());
const directionalLight = new THREE.DirectionalLight(0xffffaa, 0.5);
directionalLight.position.set(0, 5, 5);
directionalLight.castShadow = true;
directionalLight.shadow.camera.near = 0.01;
directionalLight.shadow.camera.far = 500;
directionalLight.shadow.camera.right = 30;
directionalLight.shadow.camera.left = -30;
directionalLight.shadow.camera.top = 30;
directionalLight.shadow.camera.bottom = -30;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.radius = 4;
directionalLight.shadow.bias = -0.00006;
extScene.add(directionalLight);
intScene.add(directionalLight.clone());
// RENDERER
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.VSMShadowMap;
const container = document.getElementById("container");
container.appendChild(renderer.domElement);
const stats = new Stats();
stats.domElement.classList.add("stats");
container.appendChild(stats.domElement);
const GRAVITY = 30;
let worldOctree = null;
let intWorldOctree = new Octree();
let extWorldOctree = new Octree();
const playerCollider = new Capsule(
new THREE.Vector3(0, 0.35, 0),
new THREE.Vector3(0, 1.5, 0),
0.35
);
const playerVelocity = new THREE.Vector3();
const playerDirection = new THREE.Vector3();
let playerOnFloor = false;
// mobile controls
const buttonStates = {};
// document.getElementById("forward").addEventListener("touchstart", (event) => {
// buttonStates.forward = true;
// });
// document.getElementById("forward").addEventListener("touchend", (event) => {
// buttonStates.forward = false;
// });
// document.getElementById("back").addEventListener("touchstart", (event) => {
// buttonStates["back"] = true;
// });
// document.getElementById("back").addEventListener("touchend", (event) => {
// buttonStates["back"] = false;
// });
// keyboard controls
const keyStates = {};
document.addEventListener("keydown", (event) => {
keyStates[event.code] = true;
});
document.addEventListener("keyup", (event) => {
keyStates[event.code] = false;
});
if (!isMobile) {
document.addEventListener("mousedown", () => {
document.body.requestPointerLock();
});
document.body.addEventListener("mousemove", (event) => {
if (document.pointerLockElement === document.body) {
camera.rotation.y -= event.movementX / 500;
camera.rotation.x -= event.movementY / 500;
}
});
}
window.addEventListener("resize", onWindowResize);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function playerCollitions() {
let result = worldOctree.capsuleIntersect(playerCollider);
playerOnFloor = false;
if (result) {
playerOnFloor = result.normal.y > 0;
if (!playerOnFloor) {
playerVelocity.addScaledVector(result.normal, -result.normal.dot(playerVelocity));
}
playerCollider.translate(result.normal.multiplyScalar(result.depth));
}
}
function updatePlayer(deltaTime) {
if (playerOnFloor) {
const damping = Math.exp(-3 * deltaTime) - 1;
playerVelocity.addScaledVector(playerVelocity, damping);
} else {
playerVelocity.y -= GRAVITY * deltaTime;
}
const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime);
playerCollider.translate(deltaPosition);
playerCollitions();
camera.position.copy(playerCollider.end);
}
function getForwardVector() {
camera.getWorldDirection(playerDirection);
playerDirection.y = 0;
playerDirection.normalize();
return playerDirection;
}
function getSideVector() {
camera.getWorldDirection(playerDirection);
playerDirection.y = 0;
playerDirection.normalize();
playerDirection.cross(camera.up);
return playerDirection;
}
function keyControls(deltaTime) {
const speed = 25;
if (playerOnFloor) {
if (keyStates["KeyW"]) {
playerVelocity.add(getForwardVector().multiplyScalar(speed * deltaTime));
}
if (keyStates["KeyS"]) {
playerVelocity.add(getForwardVector().multiplyScalar(-speed * deltaTime));
}
if (keyStates["KeyA"]) {
playerVelocity.add(getSideVector().multiplyScalar(-speed * deltaTime));
}
if (keyStates["KeyD"]) {
playerVelocity.add(getSideVector().multiplyScalar(speed * deltaTime));
}
if (keyStates["Space"]) {
playerVelocity.y = 10;
}
}
}
function mobileControls(deltaTime) {
const speed = 5;
if (playerOnFloor) {
if (buttonStates["forward"]) {
playerVelocity.add(getForwardVector().multiplyScalar(speed * deltaTime));
}
if (buttonStates["back"]) {
playerVelocity.add(getForwardVector().multiplyScalar(-speed * deltaTime));
}
}
}
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.4.1/");
const loader = new GLTFLoader().setPath("./assets/");
loader.setDRACOLoader(dracoLoader);
// SCENE 1
loader.load("model.glb", (gltf) => {
const model = gltf.scene;
extScene.add(model);
extWorldOctree.fromGraphNode(model);
worldOctree = extWorldOctree;
model.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
if (child.material.map) {
child.material.map.anisotropy = 8;
}
if (child.name === "Door") {
child.geometry.computeBoundingBox();
extDoor.copy(child.geometry.boundingBox).applyMatrix4(child.matrixWorld);
extDoorMesh = child;
door = extDoor;
}
}
});
mixer = new THREE.AnimationMixer(model);
gltf.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
animate();
});
// SCENE 2
// loader.load("modelCompress2.glb", (gltf) => {
// const model = gltf.scene;
// intScene.add(model);
// intWorldOctree.fromGraphNode(model);
// model.traverse((child) => {
// if (child.isMesh) {
// child.castShadow = true;
// child.receiveShadow = true;
// if (child.material.map) {
// child.material.map.anisotropy = 8;
// }
// if (child.name === "Door") {
// child.geometry.computeBoundingBox();
// intDoor.copy(child.geometry.boundingBox).applyMatrix4(child.matrixWorld);
// intDoorMesh = child;
// }
// }
// });
// intSceneisLoaded = true;
// });
function animate() {
const deltaTime = Math.min(0.1, clock.getDelta());
mixer.update(deltaTime);
if (isMobile) {
controls.update();
mobileControls(deltaTime);
} else keyControls(deltaTime);
let doorDistance = door.distanceToPoint(camera.position);
if (doorDistance < 1.5) {
testDoor();
}
if (isDoorActive == false && doorDistance > 2) {
isDoorActive = true;
}
updatePlayer(deltaTime);
renderer.render(scene, camera);
stats.update();
requestAnimationFrame(animate);
}
let door = null;
let isDoorActive = true;
let isInside = false;
let intDoor = new THREE.Box3();
let extDoor = new THREE.Box3();
let intDoorMesh = null;
let extDoorMesh = null;
function testDoor() {
if (intSceneisLoaded && isDoorActive) {
playerVelocity.y = 5;
scene = isInside ? extScene : intScene;
door = isInside ? extDoor : intDoor;
worldOctree = isInside ? extWorldOctree : intWorldOctree;
playerCollider.translate(
isInside
? new THREE.Vector3(0, 0, 0).copy(intDoorMesh.position).negate()
: new THREE.Vector3(0, 0, 0).copy(extDoorMesh.position).negate()
);
playerCollider.translate(isInside ? extDoorMesh.position : intDoorMesh.position);
camera.position.copy(playerCollider.end);
isInside = !isInside;
isDoorActive = false;
}
}