<!DOCTYPE html>
<html lang="ja" >
<head>
<meta charset="UTF-8" >
<title>Cyber Stream Navigation 1.1 </title>
<style>
body { margin: 0 ; overflow: hidden; background-color: #050500; font-family: 'Segoe UI', sans-serif; }
#ui-layer { position: absolute; top: 20px; left: 20px; z-index: 10; pointer-events: none; }
button {
pointer-events: auto;
background: rgba(50 , 40 , 0 , 0.6 );
border: 1 px solid #ffcc00;
color: #ffcc00;
padding: 12 px 24 px;
font-size: 14 px;
font-weight: bold;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 2 px;
box-shadow: 0 0 15 px rgba (255 , 200 , 0 , 0.3 ) ;
backdrop-filter: blur(4 px);
transition: all 0.2 s;
}
button:hover { background: rgba(255 , 200 , 0 , 0.2 ); box-shadow: 0 0 25 px rgba (255 , 200 , 0 , 0.8 ) ; }
#loading { display: none; color: #ffaa00; margin-top: 15px; font-weight: bold; text-shadow: 0 0 10px orange; }
.instructions {
color: rgba(255 , 240 , 200 , 0.8 );
font-size: 13 px;
margin-top: 15 px;
line-height: 1.6 ;
background: rgba(20 , 15 , 0 , 0.8 );
padding: 15 px;
border-radius: 4 px;
border-left: 3 px solid #ffcc00;
}
.key { color: #fff; border: 1px solid #555; padding: 2px 4px; border-radius: 3px; background: #222; }
</style>
<script type="importmap" >
{
"imports" : {
"three" : "https://unpkg.com/three@0.160.0/build/three.module.js" ,
"three/addons/" : "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
</head>
<body>
<div id="ui-layer" >
<button id="selectFolderBtn" >Initialize System</button>
<div id="loading" >System Scanning...</div>
<div class ="instructions" >
<b>KEYBOARD NAV:</b><br>
<span class ="key" >↑</span> / <span class ="key" >PgUp</span> : Dive to Target<br>
<span class ="key" >↓</span> / <span class ="key" >PgDn</span> : Ascend Parent<br>
<span class ="key" >←</span> <span class ="key" >→</span> : Switch Target (Rotate )<br>
<span class ="key" >Home</span> : Return to Root<br>
<br>
<b>MOUSE:</b><br>
CLICK: Select / Expand<br>
DRAG: Move & Rotate
</div>
</div>
<script type="module" >
import * as THREE from 'three' ;
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js' ;
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js' ;
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js' ;
import TWEEN from 'three/addons/libs/tween.module.js' ;
const CONFIG = {
layerDepth: 450 ,
baseRadius: 120 ,
nodeVisualWidth: 80 ,
spacingMultiplier: 2.8 ,
colorMain: 0xffcc00 ,
colorDim: 0x443300 ,
colorTarget: 0xff3300 ,
laserThickness: 3.0 ,
laserColorDefault: 0x664400 ,
laserColorActive: 0xffaa00 ,
bloomStrength: 2.5 ,
bloomRadius: 0.6 ,
bloomThreshold: 0.15 ,
camHoverHeight: 250 ,
camFollowDist: 550 ,
camLookAhead: 750 ,
scaleSelected: 2.5 ,
liftSelected: 60 ,
scaleTarget: 2.0 ,
liftTarget: 30
};
let scene, camera, renderer, composer;
let worldContainer = new THREE.Group();
let cursorMesh;
let targetMesh;
let fullTreeData = null ;
let visibleNodes = [];
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
let calculatedSlope = 0.5 ;
let focusPointZ = 0 ;
let targetFocusZ = 0 ;
let currentRotation = 0 ;
let targetRotation = 0 ;
let selectedNodeData = null ;
let targetChildIndex = 0 ;
let isDragging = false ;
let dragAxis = null ;
let lastMouse = { x: 0 , y: 0 };
let particles = [];
let arrowTexture;
init();
animate();
function init ( ) {
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x050500 , 0.0004 );
camera = new THREE.PerspectiveCamera(50 , window.innerWidth / window.innerHeight, 1 , 40000 );
renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2 ));
document.body.appendChild(renderer.domElement);
const renderScene = new RenderPass(scene, camera);
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
CONFIG.bloomStrength, CONFIG.bloomRadius, CONFIG.bloomThreshold
);
composer = new EffectComposer(renderer);
composer.addPass(renderScene);
composer.addPass(bloomPass);
const gridHelper = new THREE.GridHelper(50000 , 400 , 0x332200 , 0x110d00 );
gridHelper.position.y = -1500 ;
scene.add (gridHelper);
scene.add (worldContainer);
createCursors();
createArrowTexture();
window.addEventListener('resize' , onWindowResize);
document.getElementById('selectFolderBtn' ).addEventListener('click' , handleFolderSelect);
document.addEventListener('mousedown' , onMouseDown);
document.addEventListener('mousemove' , onMouseMove);
document.addEventListener('mouseup' , onMouseUp);
document.addEventListener('click' , onMouseClick);
document.addEventListener('dblclick' , onMouseDoubleClick);
document.addEventListener('keydown' , onKeyDown);
}
function createCursors ( ) {
const geo1 = new THREE.TorusGeometry(60 , 2 , 16 , 100 );
const mat1 = new THREE.MeshBasicMaterial({ color: CONFIG.colorMain, transparent: true , opacity: 0.9 });
cursorMesh = new THREE.Mesh(geo1, mat1);
cursorMesh.visible = false ;
scene.add (cursorMesh);
const geo2 = new THREE.TorusGeometry(30 , 2 , 8 , 50 );
const mat2 = new THREE.MeshBasicMaterial({ color: CONFIG.colorTarget, transparent: true , opacity: 0.8 });
targetMesh = new THREE.Mesh(geo2, mat2);
targetMesh.visible = false ;
scene.add (targetMesh);
}
function createArrowTexture ( ) {
const canvas = document.createElement('canvas' );
canvas.width = 64 ;
canvas.height = 64 ;
const ctx = canvas.getContext('2d' );
ctx.fillStyle = '#ffaa00' ;
ctx.beginPath();
ctx.moveTo(32 , 10 );
ctx.lineTo(54 , 54 );
ctx.lineTo(10 , 54 );
ctx.closePath();
ctx.fill();
arrowTexture = new THREE.CanvasTexture(canvas);
}
function spawnArrow (startPos, endPos ) {
const material = new THREE.SpriteMaterial({
map: arrowTexture,
color: 0xffffff ,
transparent: true ,
opacity: 1.0 ,
depthTest: false
});
const sprite = new THREE.Sprite(material);
sprite.scale.set (15 , 15 , 1 );
const p = {
mesh: sprite,
start: startPos.clone(),
end: endPos.clone(),
progress: 0 ,
speed: 0.03 + Math.random() * 0.01
};
sprite.position.copy(startPos);
scene.add (sprite);
particles.push(p);
}
function updateParticles ( ) {
if (selectedNodeData && selectedNodeData.children && selectedNodeData.children.length > 0 ) {
const targetNode = selectedNodeData.children[targetChildIndex];
if (selectedNodeData.isExpanded && targetNode && targetNode.visualObj && selectedNodeData.visualObj) {
const startPos = new THREE.Vector3();
const endPos = new THREE.Vector3();
selectedNodeData.visualObj.getWorldPosition(startPos);
targetNode.visualObj.getWorldPosition(endPos);
if (Math.random() < 0.25 ) spawnArrow(startPos, endPos);
}
}
for (let i = particles.length - 1 ; i >= 0 ; i--) {
const p = particles[i];
p.progress += p.speed;
if (p.progress >= 1.0 ) {
scene.remove (p.mesh);
particles.splice(i, 1 );
} else {
p.mesh.position.lerpVectors(p.start, p.end, p.progress);
if (p.progress > 0.8 ) p.mesh.material.opacity = (1.0 - p.progress) * 5 ;
}
}
}
function onKeyDown (e ) {
if (!selectedNodeData) return ;
switch (e.key) {
case 'ArrowUp' :
case 'PageUp' :
e.preventDefault();
navigateDownToChild();
break ;
case 'ArrowDown' :
case 'PageDown' :
e.preventDefault();
navigateUpToParent();
break ;
case 'ArrowLeft' :
e.preventDefault();
changeTarget(1 );
break ;
case 'ArrowRight' :
e.preventDefault();
changeTarget(-1 );
break ;
case 'Home' :
e.preventDefault();
navigateHome();
break ;
}
}
function navigateHome ( ) {
if (fullTreeData) {
selectNode(fullTreeData);
targetChildIndex = 0 ;
}
}
function navigateDownToChild ( ) {
if (!selectedNodeData.children || selectedNodeData.children.length === 0 ) return ;
if (!selectedNodeData.isExpanded) {
selectedNodeData.isExpanded = true ;
refreshTree();
return ;
}
const nextNode = selectedNodeData.children[targetChildIndex];
if (nextNode) {
selectNode(nextNode);
targetChildIndex = 0 ;
}
}
function navigateUpToParent ( ) {
if (selectedNodeData.parent) {
const current = selectedNodeData;
const parent = selectedNodeData.parent;
const myIndex = parent.children.indexOf(current);
selectNode(parent);
targetChildIndex = Math.max(0 , myIndex);
}
}
function changeTarget (dir ) {
if (!selectedNodeData.children || selectedNodeData.children.length === 0 ) return ;
const len = selectedNodeData.children.length;
targetChildIndex = (targetChildIndex + dir + len) % len;
updateRotationToFaceTarget();
}
function updateRotationToFaceTarget ( ) {
if (selectedNodeData && selectedNodeData.children && selectedNodeData.children.length > 0 ) {
const targetNode = selectedNodeData.children[targetChildIndex];
if (targetNode) {
const targetRot = (Math.PI / 2 ) - targetNode.angle;
new TWEEN.Tween({ rot: targetRotation })
.to({ rot: targetRot }, 500 )
.easing(TWEEN.Easing.Cubic.Out)
.onUpdate((obj) => { targetRotation = obj.rot; })
.start();
}
}
}
function selectNode (node ) {
selectedNodeData = node;
if (node.children && node.children.length > 0 && !node.isExpanded) {
node.isExpanded = true ;
refreshTree();
} else {
updateHighlights();
}
focusNode(node);
}
async function handleFolderSelect ( ) {
try {
const dirHandle = await window.showDirectoryPicker();
document.getElementById('loading' ).style.display = 'block' ;
fullTreeData = await scanDirectory(dirHandle);
fullTreeData.isExpanded = true ;
selectNode(fullTreeData);
refreshTree();
document.getElementById('loading' ).style.display = 'none' ;
} catch (err) {
console.error(err);
document.getElementById('loading' ).innerText = "Aborted" ;
}
}
async function scanDirectory (handle, depth = 0 , parent = null ) {
const node = {
name: handle.name,
kind: handle.kind,
children: [],
depth: depth,
parent: parent,
isExpanded: false ,
requiredArc: 0 ,
angle: 0 , x: 0 , y: 0 , z: 0 ,
visualObj: null ,
visualText: null ,
visualLaser: null ,
currentScale: 1.0 ,
currentLift: 0.0
};
if (depth > 15 ) return node;
if (handle.kind === 'directory' ) {
for await (const entry of handle.values( )) {
node.children.push(await scanDirectory(entry, depth + 1 , node));
}
}
return node;
}
function refreshTree ( ) {
while (worldContainer.children.length > 0 ){
worldContainer.remove (worldContainer.children[0 ]);
}
visibleNodes = [];
adjustConeSize(fullTreeData);
calculateLayout(fullTreeData);
renderTree(fullTreeData);
updateHighlights();
}
function adjustConeSize (rootNode ) {
const countsPerDepth = {};
function countVisible (node ) {
if (!countsPerDepth[node.depth]) countsPerDepth[node.depth] = 0 ;
countsPerDepth[node.depth]++;
if (node.isExpanded && node.children) {
node.children.forEach(countVisible);
}
}
countVisible(rootNode);
let maxNodesInLayer = 0 ;
let busiestDepth = 0 ;
for (const d in countsPerDepth) {
if (countsPerDepth[d] > maxNodesInLayer) {
maxNodesInLayer = countsPerDepth[d];
busiestDepth = parseInt(d);
}
}
const requiredCircumference = maxNodesInLayer * (CONFIG.nodeVisualWidth * CONFIG.spacingMultiplier);
const requiredRadius = requiredCircumference / (2 * Math.PI);
if (busiestDepth > 0 ) {
let slope = (requiredRadius - CONFIG.baseRadius) / (busiestDepth * CONFIG.layerDepth);
if (slope < 0.25 ) slope = 0.25 ;
calculatedSlope = slope;
} else {
calculatedSlope = 0.6 ;
}
}
function getConeRadius (z ) {
return CONFIG.baseRadius + Math.abs(z) * calculatedSlope;
}
function calculateLayout (node ) {
function calcRequiredArc (n ) {
const selfArc = CONFIG.nodeVisualWidth * CONFIG.spacingMultiplier;
if (!n.isExpanded || !n.children || n.children.length === 0 ) {
n.requiredArc = selfArc;
} else {
let childrenSum = 0 ;
n.children.forEach(child => {
calcRequiredArc(child);
childrenSum += child.requiredArc;
});
n.requiredArc = Math.max(selfArc, childrenSum);
}
}
calcRequiredArc(node);
function assignPositions (n, startAngle, angleRange ) {
n.z = -n.depth * CONFIG.layerDepth;
const radius = getConeRadius(n.z);
const myAngle = startAngle + (angleRange / 2 );
n.angle = myAngle;
n.x = Math.cos(myAngle) * radius;
n.y = Math.sin(myAngle) * radius;
if (!n.isExpanded || !n.children || n.children.length === 0 ) return ;
const totalChildrenArc = n.children.reduce((sum, c) => sum + c.requiredArc, 0 );
let currentStart = startAngle;
n.children.forEach(child => {
const ratio = child.requiredArc / totalChildrenArc;
const childAngleRange = angleRange * ratio;
assignPositions(child, currentStart, childAngleRange);
currentStart += childAngleRange;
});
}
node.z = 0 ; node.x = 0 ; node.y = 0 ; node.angle = -Math.PI/2 ;
if (node.isExpanded && node.children.length > 0 ) {
const totalArc = node.children.reduce((sum,c)=>sum+c.requiredArc, 0 );
let currentStart = 0 ;
node.children.forEach(child => {
const ratio = child.requiredArc / totalArc;
const range = Math.PI * 2 * ratio;
assignPositions(child, currentStart, range);
currentStart += range;
});
}
}
function renderTree (node ) {
createNodeVisual(node);
if (node.isExpanded && node.children) {
node.children.forEach(renderTree);
}
}
function createNodeVisual (node ) {
const hasChildren = node.children && node.children.length > 0 ;
const group = new THREE.Group();
group .position.set (node.x, node.y, node.z);
const w = CONFIG.nodeVisualWidth;
const h = 40 ;
const scale = 6 ;
const canvas = document.createElement('canvas' );
canvas.width = w * scale;
canvas.height = h * scale;
const ctx = canvas.getContext('2d' );
const fontSize = 14 * scale;
ctx.font = `bold ${fontSize}px "Segoe UI" , Arial`;
ctx.textAlign = 'center' ;
ctx.textBaseline = 'middle' ;
const textMetrics = ctx.measureText(node.name);
const maxWidth = canvas.width * 0.9 ;
ctx.save();
ctx.translate(canvas.width/2 , canvas.height/2 );
if (textMetrics.width > maxWidth) ctx.scale(maxWidth / textMetrics.width, 1 );
ctx.strokeStyle = '#000000' ;
ctx.lineWidth = 5 * (scale / 2 );
ctx.strokeText(node.name, 0 , 0 );
ctx.fillStyle = '#ffffff' ;
ctx.fillText(node.name, 0 , 0 );
ctx.restore();
const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.LinearFilter;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
const textMat = new THREE.SpriteMaterial({ map: texture, transparent: true , depthWrite: false });
const textSprite = new THREE.Sprite(textMat);
textSprite.scale.set (w, h, 1 );
node.visualText = textSprite;
let mainMesh;
if (hasChildren) {
const boxGeo = new THREE.BoxGeometry(w, w, w);
const edges = new THREE.EdgesGeometry(boxGeo);
const lineMat = new THREE.LineBasicMaterial({ color: CONFIG.colorMain });
const cube = new THREE.LineSegments(edges, lineMat);
textSprite.position.set (0 , 0 , 0 );
group .add (cube);
group .add (textSprite);
group .userData.isCube = true ;
group .userData.cubeMesh = cube;
const hitBox = new THREE.Mesh(boxGeo, new THREE.MeshBasicMaterial({ visible: false }));
group .add (hitBox);
hitBox.userData.node = node;
visibleNodes.push(hitBox);
mainMesh = group ;
} else {
const plateCanvas = document.createElement('canvas' );
plateCanvas.width = w * 2 ;
plateCanvas.height = h * 2 ;
const pCtx = plateCanvas.getContext('2d' );
pCtx.fillStyle = 'rgba(20, 15, 0, 0.8)' ;
pCtx.fillRect(0 ,0 ,plateCanvas.width, plateCanvas.height);
pCtx.strokeStyle = '#aa8800' ;
pCtx.lineWidth = 4 ;
pCtx.strokeRect(0 ,0 ,plateCanvas.width, plateCanvas.height);
const plateTex = new THREE.CanvasTexture(plateCanvas);
const plateMat = new THREE.SpriteMaterial({ map: plateTex, transparent: true });
const bgSprite = new THREE.Sprite(plateMat);
bgSprite.scale.set (w, h, 1 );
group .add (bgSprite);
textSprite.position.z = 1 ;
group .add (textSprite);
mainMesh = bgSprite;
bgSprite.userData.node = node;
visibleNodes.push(bgSprite);
}
worldContainer.add (group );
node.visualObj = group ;
if (node.parent) {
createLaserConnection(node.parent, node);
}
}
function createLaserConnection (pNode, cNode ) {
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(2 * 3 );
geometry.setAttribute('position' , new THREE.BufferAttribute(positions, 3 ));
const material = new THREE.LineBasicMaterial({
color: CONFIG.laserColorDefault,
transparent: true ,
opacity: 0.4 ,
linewidth: 2
});
const line = new THREE.Line(geometry, material);
worldContainer.add (line);
cNode.visualLaser = line;
}
function updateVisuals ( ) {
visibleNodes.forEach(obj => {
const node = obj.userData.node;
let targetScale = 1.0 ;
let targetLift = 0.0 ;
if (node === selectedNodeData) {
targetScale = CONFIG.scaleSelected;
targetLift = CONFIG.liftSelected;
}
else if (selectedNodeData && selectedNodeData.isExpanded && selectedNodeData.children) {
const targetChild = selectedNodeData.children[targetChildIndex];
if (targetChild === node) {
targetScale = CONFIG.scaleTarget;
targetLift = CONFIG.liftTarget;
}
}
node.currentScale += (targetScale - node.currentScale) * 0.1 ;
node.currentLift += (targetLift - node.currentLift) * 0.1 ;
node.visualObj.scale.set (node.currentScale, node.currentScale, node.currentScale);
const baseR = getConeRadius(node.z);
const finalR = baseR + node.currentLift;
const newX = Math.cos(node.angle) * finalR;
const newY = Math.sin(node.angle) * finalR;
node.visualObj.position.set (newX, newY, node.z);
if (obj.userData.isCube) {
obj.userData.cubeMesh.rotation.x += 0.01 ;
obj.userData.cubeMesh.rotation.y += 0.02 ;
}
});
visibleNodes.forEach(obj => {
const node = obj.userData.node;
if (node.parent && node.visualLaser && node.parent.visualObj) {
const positions = node.visualLaser.geometry.attributes.position.array;
positions[0 ] = node.parent.visualObj.position.x;
positions[1 ] = node.parent.visualObj.position.y;
positions[2 ] = node.parent.visualObj.position.z;
positions[3 ] = node.visualObj.position.x;
positions[4 ] = node.visualObj.position.y;
positions[5 ] = node.visualObj.position.z;
node.visualLaser.geometry.attributes.position.needsUpdate = true ;
}
});
}
function updateHighlights ( ) {
visibleNodes.forEach(obj => {
const node = obj.userData.node;
if (node.visualLaser) {
node.visualLaser.material.color.setHex(CONFIG.laserColorDefault);
node.visualLaser.material.opacity = 0.3 ;
}
});
if (!selectedNodeData) return ;
let cursor = selectedNodeData;
while (cursor) {
if (cursor.visualLaser) {
cursor.visualLaser.material.color.setHex(CONFIG.laserColorActive);
cursor.visualLaser.material.opacity = 1.0 ;
}
cursor = cursor.parent;
}
}
function updateCursors ( ) {
if (selectedNodeData && selectedNodeData.visualObj) {
cursorMesh.visible = true ;
const targetPos = new THREE.Vector3();
selectedNodeData.visualObj.getWorldPosition(targetPos);
new TWEEN.Tween(cursorMesh.position)
.to({ x: targetPos.x, y: targetPos.y, z: targetPos.z }, 200 )
.easing(TWEEN.Easing.Cubic.Out)
.start();
} else {
cursorMesh.visible = false ;
}
if (selectedNodeData && selectedNodeData.children && selectedNodeData.children.length > 0 ) {
const targetNode = selectedNodeData.children[targetChildIndex];
if (targetNode && targetNode.visualObj && selectedNodeData.isExpanded) {
targetMesh.visible = true ;
const tPos = new THREE.Vector3();
targetNode.visualObj.getWorldPosition(tPos);
new TWEEN.Tween(targetMesh.position)
.to({ x: tPos.x, y: tPos.y, z: tPos.z }, 100 )
.start();
} else {
targetMesh.visible = false ;
}
} else {
targetMesh.visible = false ;
}
}
let clickTimeout = null ;
function onMouseClick (e ) {
if (isDragging) return ;
if (clickTimeout !== null ) {
clearTimeout(clickTimeout);
clickTimeout = null ;
return ;
}
clickTimeout = setTimeout(() => {
const node = getIntersectedNode();
if (node) {
selectNode(node);
if (node.parent) {
const idx = node.parent.children.indexOf(node);
}
targetChildIndex = 0 ;
}
clickTimeout = null ;
}, 250 );
}
function onMouseDoubleClick (e ) {
if (clickTimeout !== null ) {
clearTimeout(clickTimeout);
clickTimeout = null ;
}
const node = getIntersectedNode();
if (node) {
if (node.children && node.children.length > 0 ) {
node.isExpanded = !node.isExpanded;
refreshTree();
}
selectNode(node);
}
}
function getIntersectedNode ( ) {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(visibleNodes);
if (intersects.length > 0 ) return intersects[0 ].object .userData.node;
return null ;
}
function onMouseDown (e ) {
isDragging = true ;
dragAxis = null ;
lastMouse.x = e.clientX;
lastMouse.y = e.clientY;
}
function onMouseUp ( ) { setTimeout(() => { isDragging = false ; }, 50 ); }
function onMouseMove (e ) {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1 ;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1 ;
if (isDragging) {
const dx = e.clientX - lastMouse.x;
const dy = e.clientY - lastMouse.y;
if (!dragAxis) {
dragAxis = (Math.abs(dx) > Math.abs(dy)) ? 'x' : 'y' ;
}
if (dragAxis === 'y' ) {
targetFocusZ += dy * 4.0 ;
if (targetFocusZ > 0 ) targetFocusZ = 0 ;
} else if (dragAxis === 'x' ) {
targetRotation += dx * 0.005 ;
}
lastMouse.x = e.clientX;
lastMouse.y = e.clientY;
}
}
function updateCamera ( ) {
const camZ = focusPointZ + CONFIG.camFollowDist;
const lookZ = focusPointZ - CONFIG.camLookAhead;
const camRadius = getConeRadius(camZ);
const lookRadius = getConeRadius(lookZ);
camera.position.set (0 , camRadius + CONFIG.camHoverHeight, camZ);
camera.lookAt(0 , lookRadius, lookZ);
}
function focusNode (node ) {
const targetZ = node.z + CONFIG.camLookAhead;
const targetRot = (Math.PI / 2 ) - node.angle;
new TWEEN.Tween({ z: targetFocusZ, rot: targetRotation })
.to({ z: targetZ, rot: targetRot }, 800 )
.easing(TWEEN.Easing.Cubic.Out)
.onUpdate((obj) => {
targetFocusZ = obj.z;
targetRotation = obj.rot;
})
.start();
}
function onWindowResize ( ) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
}
function animate ( ) {
requestAnimationFrame(animate);
TWEEN.update();
focusPointZ += (targetFocusZ - focusPointZ) * 0.1 ;
currentRotation += (targetRotation - currentRotation) * 0.1 ;
worldContainer.rotation.z = currentRotation;
updateVisuals();
updateCursors();
if (cursorMesh.visible) {
const s = 1.0 + Math.sin(Date.now() * 0.005 ) * 0.1 ;
cursorMesh.scale.set (s, s, s);
const worldCameraPos = new THREE.Vector3();
camera.getWorldPosition(worldCameraPos);
cursorMesh.lookAt(worldCameraPos);
}
if (targetMesh.visible) {
const worldCameraPos = new THREE.Vector3();
camera.getWorldPosition(worldCameraPos);
targetMesh.lookAt(worldCameraPos);
targetMesh.rotateZ(Date.now() * 0.005 );
}
updateParticles();
updateCamera();
composer.render();
}
</script>
</body>
</html>
copy
コメント