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