Peeling the onion

<!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?

I’ve added your code to a live example however it is not yet possible to load your glTF asset: https://jsfiddle.net/gq0Lzd3w/

Do you think you can host the asset e.g. at GitHub so it’s possible to load it in the fiddle? In this way, it’s easier to see what you are working at and what you are trying to solve.

1 Like