Newer
Older
about-Leaflet / hureai.js
import * as THREE from "https://unpkg.com/three@0.126.1/build/three.module.js";
import { PointerLockControls } from "https://unpkg.com/three@0.126.1/examples/jsm/controls/PointerLockControls.js";
import { GLTFLoader } from "https://unpkg.com/three@0.126.1/examples/jsm/loaders/GLTFLoader.js";
import { sendGhostPosition, sendUserName } from "./client.js";


let camera, scene, renderer, controls, mixer; // カメラ、シーン、レンダラー、コントロール、ミキサーの追加
let moveForward = false,
    moveBackward = false,
    moveLeft = false,
    moveRight = false;

let ghostLoaded = false,
    TalkRoom,
    WorldStick,
    ghost;  // ghostモデル本体
let object; // メインの3Dオブジェクト参照用(通常、ghostと同じ 感情モーフ等)

let lastSentPosition = { x: null, y: null, z: null }; //ghostの場所
let connections = 3;


let headPartLogged = false;

// ユーザー名を取得してサーバーに送信
let userName = prompt("あなたの名前を入力してください:");
alert(`こんにちは、${userName}さん!`);
sendUserName(userName);


// アニメーション再生関数
function playAnimation(animationName) {
    if (!mixer || !ghost || !ghost.animations) return;
    const clip = THREE.AnimationClip.findByName(ghost.animations, animationName);
    if (clip) {
        const action = mixer.clipAction(clip);
        action.reset().play();
    }
}

function init() {
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xffffff);

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.set(1, 5, 5);
    camera.lookAt(new THREE.Vector3(0, 0, 0));

    const isMobile = window.innerWidth < 768;
    if (isMobile) {
        camera.position.set(50, 100, 100);
    } else {
        camera.position.set(1, 5, 5);
    }

    const light = new THREE.AmbientLight(0xffffff, 0.8);
    light.position.set(100, 500, 500);
    scene.add(light);
    light.castShadow = true;

    const loader = new GLTFLoader();

    loader.load("talkroom.glb", (gltf) => {
        TalkRoom = gltf.scene;
        scene.add(TalkRoom);
    });

    loader.load("guidestick.glb", (gltf) => {
        WorldStick = gltf.scene;
        scene.add(WorldStick);
    });

    for (let i = 0; i < connections; i++) {
    loader.load("ghost.glb", (gltf) => {
        ghost = gltf.scene;
        object = ghost;
        scene.add(ghost);
        ghostLoaded = true;

        // 座標をランダムで決定
        ghost.position.x = Math.round(Math.random() * 80) - 30;
        ghost.position.y = Math.random() * 0 - 0; // 高さ指定
        ghost.position.z = Math.round(Math.random() * 50);

        const pos = ghost.position;
        console.log(pos.x, pos.y, pos.z);

        const headPart = ghost.getObjectByName("head-part");
        if (headPart && !headPartLogged) {
            console.log("head-partが見つかりました:", headPart);
            headPartLogged = true; // これで次からは表示されなくなる
        } else if (!headPart && !headPartLogged) {
            console.warn("head-partは見つかりませんでした。");
            headPartLogged = true;
        }

        mixer = new THREE.AnimationMixer(ghost);
        if (gltf.animations && gltf.animations.length > 0) {
            gltf.animations.forEach((clip) => {
                mixer.clipAction(clip).play();
            });
        }
        ghost.animations = gltf.animations;
    });
    }

    controls = new PointerLockControls(camera, document.body);
    scene.add(controls.getObject());
    document.getElementById("WebGL-output").addEventListener("click", () => controls.lock());

    document.getElementById("overlay").addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
    });

    document.addEventListener("keydown", handleKeyDown);
    document.addEventListener("keyup", handleKeyUp);

    renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.8);
    document.getElementById("WebGL-output").appendChild(renderer.domElement);
}

// キーボード操作処理
function handleKeyDown(event) {
    switch (event.key) {
        // 空間操作
        case "w":
            moveForward = true;
            break;
        case "s":
            moveBackward = true;
            break;
        case "a":
            moveLeft = true;
            break;
        case "d":
            moveRight = true;
            break;
        // アバター操作
        case "g":
        case "ArrowUp":
            if (ghostLoaded) ghost.position.z -= 0.5;
            break;
        case "b":
        case "ArrowDown":
            if (ghostLoaded) ghost.position.z += 0.5;
            break;
        case "h":
        case "ArrowRight":
            if (ghostLoaded) ghost.position.x += 0.5;
            break;
        case "f":
        case "ArrowLeft":
            if (ghostLoaded) ghost.position.x -= 0.5;
            break;
        case "r":
            if (ghostLoaded) ghost.rotation.y += Math.PI / 16;
            break;
    }
}

function handleKeyUp(event) {
    switch (event.key) {
        case "w":
        case "ArrowUp":
            moveForward = false;
            break;
        case "s":
        case "ArrowDown":
            moveBackward = false;
            break;
        case "a":
        case "ArrowLeft":
            moveLeft = false;
            break;
        case "d":
        case "ArrowRight":
            moveRight = false;
            break;
    }
}

// 移動ボタン
const moveforBtn = document.getElementById('forward');
moveforBtn.addEventListener('click', function () {
    if (ghostLoaded) ghost.position.z -= 2;
});

const movebackBtn = document.getElementById('back');
movebackBtn.addEventListener('click', function () {
    if (ghostLoaded) ghost.position.z += 2;
});

const moverightBtn = document.getElementById('right');
moverightBtn.addEventListener('click', function () {
    if (ghostLoaded) ghost.position.x += 2;
});

const moveleftBtn = document.getElementById('left');
moveleftBtn.addEventListener('click', function () {
    if (ghostLoaded) ghost.position.x -= 2;
});

const moveturn = document.getElementById('turn');
moveturn.addEventListener('click', function () {
    if (ghostLoaded) ghost.rotation.y += Math.PI / 12;
});

// カメラ位置更新
function updateCameraPosition() {
    const speed = 10;
    const direction = new THREE.Vector3();
    direction.x = Number(moveRight) - Number(moveLeft);
    direction.z = Number(moveBackward) - Number(moveForward);
    direction.normalize();
    const delta = speed * 0.1;
    controls.getObject().translateX(direction.x * delta);
    controls.getObject().translateZ(direction.z * delta);
}

// アニメーションループ
function animate() {
    requestAnimationFrame(animate);
    updateCameraPosition();
    if (mixer) mixer.update(0.01);
    renderer.render(scene, camera);
}

function logGhostPosition() {
    if (ghostLoaded && ghost) {
        const pos = new THREE.Vector3();
        ghost.getWorldPosition(pos);
        console.log("Ghost位置:", pos.x.toFixed(2), pos.y.toFixed(2), pos.z.toFixed(2));
    }
}
setInterval(logGhostPosition, 150); // 0.15秒ごとに出力

// クリック時の処理
document.addEventListener("dblclick", function (event) {
    event.preventDefault();
    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    const rect = renderer.domElement.getBoundingClientRect();
    mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
    raycaster.setFromCamera(mouse, camera);

    const intersects = raycaster.intersectObjects(scene.children, true);
    if (intersects.length > 0) {
        if (intersects[0].object.name == "円柱001_1") {
            window.open("choose.html");
        }
        console.log(intersects[0]);
    }
});

// 感情名とmorphTarget名のマッピング
const emotionMorphMap = {
    smile: "smile",
    sad: "sad",
    surprised: "surprised"
};

function handleEmotionButtonClick(emotion) {
    console.log(`${emotion}ボタンがクリックされました!`);
    playAnimation(emotion);

    const morphName = emotionMorphMap[emotion];
    if (!morphName) return;

    if (!object) {
        console.warn("objectが未ロードです。");
        return;
    };

    const headPartGroup = object.getObjectByName("head-part");
    if (!headPartGroup) {
        console.warn("head-partグループが見つかりませんでした。");
        return;
    }
    headPartGroup.traverse((child) => {
        if (child.isMesh && child.morphTargetDictionary && child.morphTargetInfluences) {
            const morphIndex = child.morphTargetDictionary[morphName];
            if (morphIndex !== undefined) {
                let current = child.morphTargetInfluences[morphIndex];
                let target = (current > 0.5) ? 0.0 : 1.0;
                let speed = 0.05;

                function updateMorph() {
                    if (Math.abs(current - target) > 0.01) {
                        current += (target - current) * speed;
                        child.morphTargetInfluences[morphIndex] = current;
                        requestAnimationFrame(updateMorph);
                    } else {
                        child.morphTargetInfluences[morphIndex] = target;
                    }
                }
                updateMorph();
            }
        }
    });
}

// 感情ボタンイベント登録
document.querySelectorAll('.emotion-button').forEach(button => {
    button.addEventListener('click', function () {
        const emotion = this.dataset.emotion;
        handleEmotionButtonClick(emotion);
    });
});

// 初期化
init();
animate();

// 位置が変わったかどうか判定
function hasPositionChanged(current, last) {
    return current.x !== last.x || current.y !== last.y || current.z !== last.z;
}

// 位置が変わったときだけ送信
setInterval(() => {
    if (ghostLoaded && ghost) {
        const current = {
            x: ghost.position.x,
            y: ghost.position.y,
            z: ghost.position.z
        };
        if (hasPositionChanged(current, lastSentPosition)) {
            sendGhostPosition(current.x, current.y, current.z);
            lastSentPosition = { ...current };
        }
        // 別モデルの位置情報にしたがって動かせるようにしたい
        ghost.position.set(current.x, current.y, current.z);
    }
}, 300);