<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>3D 모델 뷰어</title>
<style>
/* 스타일 설정 */
body { margin: 0; overflow: hidden; }
/* 모델 컨테이너 스타일 */
#model-container {
width: 100%;
height: 100vh;
}
.progress-bar-container {
/* 로딩 바 컨테이너 스타일 */
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#progress-bar {
/* 로딩 바 스타일 */
width: 30%;
margin-top: 5px;
height: 2%;
}
label {
/* 텍스트 레이블 스타일 */
color: white;
font-size: 1rem;
}
#model-viewer{
position: absolute;
top:0px;
}
</style>
</head>
<body>
<!-- 모델을 표시할 컨테이너 -->
<div id="model-container"></div>
<!-- 로딩 표시용 컨테이너 -->
<div class="progress-bar-container">
<label for="progress-bar">Loading...</label>
<progress id="progress-bar" value="0" max="100"></progress>
</div>
<!-- 모델 뷰어 컨트롤 버튼 -->
<div id="model-viewer">
<button id="back-view-button">배면</button>
<button id="front-view-button">정면</button>
<button id="right-view-button">우측면</button>
<button id="left-view-button">좌측면</button>
<button id="top-view-button">평면</button>
<button id="bottom-view-button">저면</button>
<button id="delete-button">삭제</button>
<button id="recover-button">모두복구</button>
<button id="undo-button">되돌리기</button>
<button id="strip-button">벗김</button>
</div>
<!-- 메시 정보를 표시할 오버레이 -->
<div id="info-overlay" style="position: fixed; top: 10px; left: 10px; background-color: rgba(0, 0, 0, 0.5); color: white; padding: 10px; z-index: 1000; display: none;"></div>
<!-- 스크립트 섹션 시작 -->
<script type="module">
// 필요한 모듈 및 변수 가져오기
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.js';
import { GLTFLoader } from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/controls/OrbitControls.js';
// 카메라, 씬, 렌더러, 컨트롤스, 모델 및 관련 변수들 선언
let camera, scene, renderer, controls, model, highlightedObject, originalMaterials;
// HTML 요소들을 가져옴
// HTML 요소들을 가져옴
const modelContainer = document.getElementById('model-container');
const infoOverlay = document.getElementById('info-overlay');
const deleteButton = document.getElementById('delete-button');
const recoverButton = document.getElementById('recover-button'); // Add this line
const undoButton = document.getElementById('undo-button');
const deletedMeshStack = []; // Array to store deleted meshes for undo
// '삭제' 버튼 클릭 이벤트 핸들러 등록
deleteButton.addEventListener('click', onDeleteButtonClick);
// 'Recover' 버튼 클릭 이벤트 핸들러 등록
recoverButton.addEventListener('click', onRecoverButtonClick); // Add this line
// 'Recover' button click event handler
document.getElementById('recover-button').addEventListener('click', onRecoverButtonClick);
// 'Undo' 버튼 클릭 이벤트 핸들러 등록
undoButton.addEventListener('click', onUndoButtonClick);
// Array to store deleted meshes
const deletedMeshes = [];
// Get the "Strip" button element
const stripButton = document.getElementById('strip-button');
// Add click event listener to the "Strip" button
stripButton.addEventListener('click', onStripButtonClick);
// "Strip" button click event handler
function onStripButtonClick() {
// Get the first child mesh of the model
const firstMesh = model.children.find(child => child.isMesh);
if (firstMesh) {
// Add the removed mesh to the deleted meshes array for potential recovery
deletedMeshes.push(firstMesh);
// Remove the first mesh from the model
model.remove(firstMesh);
// Restore original materials and clear highlighted object
restoreOriginalMaterials();
highlightedObject = null;
// Hide the info overlay
infoOverlay.style.display = 'none';
}
}
// 초기화 함수 호출
init();
// 애니메이션 루프 시작
animate();
//버튼 호출
setupViewButton();
function init() {
// 씬 생성
scene = new THREE.Scene();
// 카메라 생성
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 5);
// 렌더러 생성 및 설정
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
modelContainer.appendChild(renderer.domElement);
const loadingManager = new THREE.LoadingManager(); // 로딩 매니저 생성
const progressBar = document.getElementById('progress-bar'); // 로딩 바 엘리먼트 가져오기
const label = document.querySelector('label'); // 텍스트를 표시할 label 엘리먼트 가져오기
const progressBarContainer = document.querySelector('.progress-bar-container'); // 로딩 컨테이너 엘리먼트 가져오기
// 로딩 진행 상황 갱신 함수 등록
loadingManager.onProgress = function(url, loaded, total) {
progressBar.value = (loaded / total) * 100; // 로딩 진행도 설정
label.textContent = `Loading... ${Math.round((loaded / total) * 100)}%`; // 텍스트 업데이트
};
// 로딩이 완료되었을 때 처리 함수 등록
loadingManager.onLoad = function() {
progressBarContainer.style.display = 'none'; // 로딩 컨테이너 숨김
};
// 카메라 컨트롤 생성
controls = new OrbitControls(camera, renderer.domElement);
controls.enableRotate = true;
// GLTF 로더 생성
const loader = new GLTFLoader(loadingManager);
// 모델 로딩 및 초기화
loader.load(
'./5555.gltf',
(gltf) => {
model = gltf.scene;
// 모델 중심을 원점으로 이동
const bbox = new THREE.Box3().setFromObject(model);
const center = bbox.getCenter(new THREE.Vector3());
model.position.sub(center);
// 씬에 모델 추가
scene.add(model);
// 모델의 원래 머티리얼 저장
originalMaterials = new Map();
model.traverse((child) => {
if (child.isMesh) {
originalMaterials.set(child, child.material);
}
});
},
);
// 조명 추가
const directionalLight = new THREE.DirectionalLight(0xffffff, 3); // 직사광 생성 (밝기 3)
directionalLight.position.set(1, 2, 3); // 위치 설정
scene.add(directionalLight); // 씬에 추가
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 주변광 생성 (밝기 0.5)
scene.add(ambientLight); // 씬에 추가
const backLight = new THREE.DirectionalLight(0xffffff, 3); // 반대 방향에서 비추는 빛 생성
backLight.position.set(0, 0, -1); // 카메라 반대 방향으로 설정
scene.add(backLight); // 씬에 추가
// 창 크기 조정 시 카메라 및 렌더러 크기 업데이트
window.addEventListener('resize', () => {
const newWidth = window.innerWidth;
const newHeight = window.innerHeight;
camera.aspect = newWidth / newHeight;
camera.updateProjectionMatrix();
renderer.setSize(newWidth, newHeight);
});
// 클릭 이벤트 핸들링 함수 등록
document.addEventListener('click', onClick);
}
// 애니메이션 루프
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
// 배면보기 버튼 등록 및 이벤트 설정
function setupViewButton() {
const backViewButton = document.getElementById('back-view-button');
backViewButton.addEventListener('click', () => {
camera.position.set(0, 0, -40);
controls.update();
});
const frontViewButton = document.getElementById('front-view-button');
frontViewButton.addEventListener('click', () => {
camera.position.set(0, 0, 40);
controls.update();
});
const rightViewButton = document.getElementById('right-view-button');
rightViewButton.addEventListener('click', () => {
camera.position.set(40, 0, 0);
controls.update();
});
const leftViewButton = document.getElementById('left-view-button');
leftViewButton.addEventListener('click', () => {
camera.position.set(-40, 0, 0);
controls.update();
});
const topViewButton = document.getElementById('top-view-button');
topViewButton.addEventListener('click', () => {
camera.position.set(0, 40, 0);
controls.update();
});
const bottomViewButton = document.getElementById('bottom-view-button');
bottomViewButton.addEventListener('click', () => {
camera.position.set(0, -40, 0);
controls.update();
});
}
// 클릭 이벤트 핸들링 함수
function onClick(event) {
// 마우스 클릭 위치를 정규화된 좌표로 변환
const mouse = new THREE.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
- (event.clientY / window.innerHeight) * 2 + 1
);
// 레이캐스터 생성 및 설정
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// 모델과 교차하는 메시를 찾음
const intersects = raycaster.intersectObjects(model.children, true);
// 이전에 하이라이트된 메시의 머티리얼 복원
if (highlightedObject) {
restoreOriginalMaterials();
}
if (intersects.length > 0) {
// 클릭한 메시 하이라이트 처리
highlightedObject = intersects[0].object;
setHighlightedMaterials();
// 클릭한 메시의 정보를 상단에 표시
let meshInfo = `
영문: ${highlightedObject.name}
`;
// 추가 정보 표시
if (highlightedObject.name === 'Left_Pectoralis_Major') {
meshInfo += "<br>한자식: 대흉근<br>한글식: 큰가슴근";
} else if (highlightedObject.name === 'Object413') {
meshInfo += "<br>한자식: 광배근<br>한글식: 몰라0";
} else if (highlightedObject.name === 'Object398') {
meshInfo += "<br>한자식: 복직근<br>한글식: 몰라3";
}
// 정보 오버레이에 내용 설정 및 표시
infoOverlay.innerHTML = meshInfo;
infoOverlay.style.display = 'block';
} else {
// 메시 클릭이 아니라면 정보 영역 숨김
infoOverlay.style.display = 'none';
}
}
// 하이라이트된 메시의 머티리얼을 설정
function setHighlightedMaterials() {
model.traverse((child) => {
if (child.isMesh && child !== highlightedObject) {
child.material = child.material.clone();
child.material.transparent = true;
child.material.opacity = 3;
}
});
}
// 원래 머티리얼로 복원
function restoreOriginalMaterials() {
model.traverse((child) => {
if (child.isMesh) {
child.material = originalMaterials.get(child);
}
});
highlightedObject = null;
}
// 클릭 이벤트 핸들링 함수 등록
document.addEventListener('click', onClick);
// 키 다운 이벤트 핸들링 함수 등록
document.addEventListener('keydown', onKeyDown);
function onKeyDown(event) {
if (event.key === 'Delete') {
onDeleteButtonClick();
} else if (event.key === '*') { // Check for the '*' key press
onRecoverButtonClick();
}else if (event.key === 'z') { // Check for the 'z' key press
onUndoButtonClick();
}
else if (event.key === '-') { // Check for the '-' key press
onStripButtonClick();
}
}
function onDeleteButtonClick() {
if (highlightedObject) {
// Add the deleted mesh to the array for potential recovery
deletedMeshes.push(highlightedObject);
// Remove the selected mesh from the scene
model.remove(highlightedObject);
highlightedObject = null; // Clear highlighted mesh
infoOverlay.style.display = 'none'; // Hide information overlay
restoreOriginalMaterials();
}
}
function onRecoverButtonClick() {
// Check if there are any deleted meshes to recover
if (deletedMeshes.length > 0) {
// Add all deleted meshes back to the scene
for (const deletedMesh of deletedMeshes) {
model.add(deletedMesh);
}
deletedMeshes.length = 0; // Clear the array
}
}
// 'Undo' 버튼 클릭 이벤트 핸들러
function onUndoButtonClick() {
if (deletedMeshes.length > 0) {
// Pop the last deleted mesh from the array
const meshToUndo = deletedMeshes.pop();
// Add the mesh back to the scene
model.add(meshToUndo);
restoreOriginalMaterials();
// Optionally, you might want to re-highlight the mesh
highlightedObject = meshToUndo;
setHighlightedMaterials();
}
}
</script>
<!-- 스크립트 섹션 종료 -->
</body>
</html>
Like peeling an onion,
Outside → Inside
Like peeling an onion,
How do I delete the mesh every time I click the strip-button?