first test
commit
96a08012c9
@ -0,0 +1,15 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"overrides": [
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
package-lock.json
|
@ -0,0 +1,53 @@
|
||||
import * as THREE from "three"
|
||||
import {OrbitControls} from "https://unpkg.com/three@0.150.1/examples/jsm/controls/OrbitControls.js"
|
||||
const container = document.querySelector('#container')
|
||||
|
||||
// Scene setup
|
||||
const scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0x88ccff)
|
||||
|
||||
|
||||
|
||||
// Light setup
|
||||
const ambientLight = new THREE.AmbientLight(0xb3c3e6);
|
||||
scene.add(ambientLight);
|
||||
|
||||
// Renderer setup
|
||||
const renderer = new THREE.WebGLRenderer({antialias: true})
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
|
||||
// Camera setup
|
||||
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
const controls = new OrbitControls( camera, renderer.domElement );
|
||||
camera.position.z = 5;
|
||||
|
||||
// Objects setup
|
||||
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
|
||||
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 } );
|
||||
const cube = new THREE.Mesh( geometry, material );
|
||||
scene.add(cube);
|
||||
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// Animation loop setup
|
||||
function animate() {
|
||||
renderer.render(scene, camera)
|
||||
controls.update()
|
||||
requestAnimationFrame(animate)
|
||||
cube.rotation.x +=0.01;
|
||||
cube.rotation.y += 0.01;
|
||||
}
|
||||
|
||||
animate()
|
||||
|
||||
// Window resize callback
|
||||
window.addEventListener("resize", ()=>{
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
})
|
||||
|
||||
export default {
|
||||
scene: scene,
|
||||
}
|
@ -0,0 +1,372 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
1
|
||||
00:00:00,000 --> 00:00:05,529
|
||||
The average prehistoric person could make a nice living
|
||||
in about a fifteen-hour work week.
|
||||
|
||||
2
|
||||
00:00:05,800 --> 00:00:10,920
|
||||
Fifteen hours a week for subsistence leaves
|
||||
a lot of time for other things
|
||||
|
||||
3
|
||||
00:00:11,514 --> 00:00:14,200
|
||||
So much time
|
||||
|
||||
4
|
||||
00:00:15,057 --> 00:00:19,100
|
||||
that maybe the restless ones
|
||||
|
||||
5
|
||||
00:00:20,171 --> 00:00:27,221
|
||||
to enliven their life, or skill in making
|
||||
or cooking or singing
|
||||
|
||||
6
|
||||
00:00:27,971 --> 00:00:33,057
|
||||
or very interesting thoughts to think
|
||||
|
||||
7
|
||||
00:00:33,629 --> 00:00:37,614
|
||||
decided to slope off and hunt mammoths
|
||||
|
||||
8
|
||||
00:00:38,157 --> 00:00:40,443
|
||||
The skillful hunters then
|
||||
|
||||
9
|
||||
00:00:40,929 --> 00:00:44,871
|
||||
would come staggering back with a load of meat
|
||||
|
||||
10
|
||||
00:00:45,186 --> 00:00:47,329
|
||||
a lot of ivory
|
||||
|
||||
11
|
||||
00:00:47,529 --> 00:00:49,514
|
||||
and a story
|
||||
|
||||
12
|
||||
00:00:49,636 --> 00:00:51,914
|
||||
It wasn't the meat that made the difference.
|
||||
|
||||
13
|
||||
00:00:52,300 --> 00:00:53,760
|
||||
It was the story
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,25 @@
|
||||
import {BoxGeometry, MeshBasicMaterial, Mesh} from "https://unpkg.com/three@0.150.1/build/three.module.js";
|
||||
import context from './3d.js'
|
||||
|
||||
|
||||
// Mapping
|
||||
export default {
|
||||
1: () => addCube(),
|
||||
2: () => addCube(),
|
||||
3: () => addCube()
|
||||
}
|
||||
|
||||
// Functions
|
||||
|
||||
|
||||
const addCube = () => {
|
||||
const geometry = new BoxGeometry(1,1,1);
|
||||
const material = new MeshBasicMaterial({color: 0xff0000})
|
||||
const cube = new Mesh(geometry,material)
|
||||
cube.position.x = Math.random() * 10 - 5;
|
||||
cube.position.y = Math.random() * 10 - 5;
|
||||
cube.position.z = Math.random() * 10 - 5;
|
||||
context.scene.add(cube)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Subtitles player</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://unpkg.com/three@0.150.1/build/three.module.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module" defer src="3d.js"></script>
|
||||
<script type="module" defer src="index.js" ></script>
|
||||
<script type="module" defer src="functions.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<div id="sub">Hello</div>
|
||||
</div>
|
||||
<audio src="assets/polypupatic.mp3" id="player" controls ></audio>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,43 @@
|
||||
// 1. Import dependencies, select DOM elements, define global var
|
||||
import srtParser2 from 'https://cdn.skypack.dev/srt-parser-2';
|
||||
|
||||
// 4. Interactive functions to attach to the subtitle id
|
||||
// see functions.js for more info
|
||||
import functions from './functions.js'
|
||||
|
||||
|
||||
const player = document.querySelector('#player')
|
||||
const sub = document.querySelector('#sub')
|
||||
|
||||
|
||||
const printText = (text) => {
|
||||
sub.innerHTML = text
|
||||
}
|
||||
|
||||
// 3. Parse .srt file and setup audio player callback
|
||||
const readSRT = (srt) => {
|
||||
let parser = new srtParser2();
|
||||
let srt_array = parser.fromSrt(srt)
|
||||
console.log(srt_array)
|
||||
|
||||
let currentId = 0
|
||||
|
||||
player.addEventListener('timeupdate', (e)=>{
|
||||
let current = srt_array.find(
|
||||
caption =>
|
||||
caption.startSeconds <= e.target.currentTime &&
|
||||
caption.endSeconds >= e.target.currentTime
|
||||
)
|
||||
if (current != undefined && currentId != parseInt(current.id)){
|
||||
currentId = parseInt(current.id)
|
||||
printText(current.text)
|
||||
// Check if the srtFunctions object has some callback for the current index
|
||||
if(Object.hasOwn( functions, current.id))
|
||||
functions[current.id]()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Open .srt file and trigger readSRT() function
|
||||
fetch('assets/mammoth.srt').then(res=>res.text()).then(data=>readSRT(data))
|
||||
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "3dsub",
|
||||
"version": "1.0.0",
|
||||
"description": "3d env with interactive sub",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"eslint": "^8.35.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
font-family: sans-serif;
|
||||
font-size: 32px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
#container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#sub {
|
||||
position: absolute;
|
||||
bottom: 25%;
|
||||
left: 50%;
|
||||
translate: -50% 0;
|
||||
color: white;
|
||||
text-shadow: 0px 0px 4px black;
|
||||
max-width: 60ch;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
audio {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 50;
|
||||
}
|
Loading…
Reference in New Issue