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
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;
|
|
}
|
|
}
|
|
|