Loading 3D viewer…

Add Shapes

Tools

Color

Initializing… Please wait.

Working 1

<div class="tinkercad-clone-container">
  <div id="modeling-area" style="width: 100%; height: 600px; border: 1px solid #ccc; background: #f0f0f0;">
    <div id="loading-message" style="padding: 20px; text-align: center;">Loading 3D viewer...</div>
    <div id="error-message" style="color: red; padding: 20px; display: none;"></div>
  </div>
  
  <div class="controls">
    <h3>Add Shapes</h3>
    <button onclick="addCube()">Cube</button>
    <button onclick="addSphere()">Sphere</button>
    <button onclick="addCylinder()">Cylinder</button>
    <button onclick="addCone()">Cone</button>
    
    <h3>Tools</h3>
    <button onclick="toggleMoveMode()">Move (M)</button>
    <button onclick="toggleRotateMode()">Rotate (R)</button>
    <button onclick="toggleScaleMode()">Scale (S)</button>
    <button onclick="deleteSelected()">Delete (Del)</button>
    
    <h3>Color</h3>
    <input type="color" id="object-color" value="#2194ce" onchange="changeSelectedColor(this.value)">
  </div>
  
  <div id="status">Initializing... Please wait.</div>
</div>

<!-- Load Three.js from CDN with fallback -->
<script>
  function loadScript(src, onSuccess, onError) {
    var script = document.createElement('script');
    script.src = src;
    script.onload = onSuccess;
    script.onerror = onError;
    document.head.appendChild(script);
  }

  function initAfterLoad() {
    // Check if Three.js is loaded
    if (typeof THREE === 'undefined') {
      showError("Three.js failed to load. Please try refreshing the page.");
      return;
    }
    
    // Now initialize our application
    init3DViewer();
  }

  function showError(message) {
    document.getElementById('loading-message').style.display = 'none';
    document.getElementById('error-message').style.display = 'block';
    document.getElementById('error-message').innerHTML = message;
    document.getElementById('status').textContent = 'Error: ' + message;
  }

  // Load Three.js with OrbitControls
  loadScript(
    'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js',
    function() {
      loadScript(
        'https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js',
        initAfterLoad,
        function() { showError("OrbitControls failed to load."); }
      );
    },
    function() { showError("Three.js failed to load."); }
  );
</script>

<script>
  // Global variables
  let scene, camera, renderer, controls;
  let objects = [];
  let selectedObject = null;
  let interactionMode = 'move';
  
  function init3DViewer() {
    try {
      document.getElementById('status').textContent = 'Setting up 3D viewer...';
      
      // Create scene
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0xf0f0f0);
      
      // Create camera
      const container = document.getElementById('modeling-area');
      camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
      camera.position.set(5, 5, 10);
      camera.lookAt(0, 0, 0);
      
      // Create renderer
      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(container.clientWidth, container.clientHeight);
      container.insertBefore(renderer.domElement, container.firstChild);
      
      // Add orbit controls
      controls = new THREE.OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.25;
      
      // Add lights
      const ambientLight = new THREE.AmbientLight(0x404040);
      scene.add(ambientLight);
      
      const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
      directionalLight.position.set(1, 1, 1);
      scene.add(directionalLight);
      
      // Add grid helper
      const gridHelper = new THREE.GridHelper(20, 20);
      scene.add(gridHelper);
      
      // Add axes helper
      const axesHelper = new THREE.AxesHelper(5);
      scene.add(axesHelper);
      
      // Add event listeners
      renderer.domElement.addEventListener('click', onCanvasClick, false);
      window.addEventListener('resize', onWindowResize, false);
      document.addEventListener('keydown', onKeyDown, false);
      
      // Hide loading message
      document.getElementById('loading-message').style.display = 'none';
      
      // Start animation loop
      animate();
      
      document.getElementById('status').textContent = 'Ready. Click "Add Shapes" to begin.';
      
      // Add a test cube to verify it's working
      addCube();
      
    } catch (error) {
      showError("Error initializing 3D viewer: " + error.message);
      console.error(error);
    }
  }
  
  function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
  }
  
  function onWindowResize() {
    const container = document.getElementById('modeling-area');
    camera.aspect = container.clientWidth / container.clientHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(container.clientWidth, container.clientHeight);
  }
  
  function onKeyDown(event) {
    if (!selectedObject) return;
    
    const moveStep = 0.5;
    const rotateStep = Math.PI / 16;
    const scaleStep = 0.1;
    
    switch (event.key.toLowerCase()) {
      case 'm':
        toggleMoveMode();
        break;
      case 'r':
        toggleRotateMode();
        break;
      case 's':
        toggleScaleMode();
        break;
      case 'delete':
      case 'backspace':
        deleteSelected();
        break;
      case 'arrowup':
        if (interactionMode === 'move') selectedObject.position.y += moveStep;
        if (interactionMode === 'rotate') selectedObject.rotation.y += rotateStep;
        if (interactionMode === 'scale') selectedObject.scale.y += scaleStep;
        break;
      case 'arrowdown':
        if (interactionMode === 'move') selectedObject.position.y -= moveStep;
        if (interactionMode === 'rotate') selectedObject.rotation.y -= rotateStep;
        if (interactionMode === 'scale') selectedObject.scale.y = Math.max(0.1, selectedObject.scale.y - scaleStep);
        break;
      case 'arrowleft':
        if (interactionMode === 'move') selectedObject.position.x -= moveStep;
        if (interactionMode === 'rotate') selectedObject.rotation.x -= rotateStep;
        if (interactionMode === 'scale') selectedObject.scale.x = Math.max(0.1, selectedObject.scale.x - scaleStep);
        break;
      case 'arrowright':
        if (interactionMode === 'move') selectedObject.position.x += moveStep;
        if (interactionMode === 'rotate') selectedObject.rotation.x += rotateStep;
        if (interactionMode === 'scale') selectedObject.scale.x += scaleStep;
        break;
    }
  }

  function onCanvasClick(event) {
    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;
    
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(mouse, camera);
    
    const intersects = raycaster.intersectObjects(objects, false);
    
    if (intersects.length > 0) {
      selectObject(intersects[0].object);
    } else {
      deselectObject();
    }
  }

  function selectObject(object) {
    deselectObject();
    selectedObject = object;
    
    const outlineGeometry = object.geometry.clone();
    const outlineMaterial = new THREE.MeshBasicMaterial({ 
      color: 0x00ff00, 
      side: THREE.BackSide,
      transparent: true,
      opacity: 0.5
    });
    
    const outlineMesh = new THREE.Mesh(outlineGeometry, outlineMaterial);
    outlineMesh.position.copy(object.position);
    outlineMesh.rotation.copy(object.rotation);
    outlineMesh.scale.multiplyScalar(1.1);
    outlineMesh.userData.isOutline = true;
    object.userData.outline = outlineMesh;
    scene.add(outlineMesh);
    
    document.getElementById('object-color').value = '#' + object.material.color.getHexString();
    updateStatus(`Selected: ${object.userData.type}`);
  }

  function deselectObject() {
    if (selectedObject && selectedObject.userData.outline) {
      scene.remove(selectedObject.userData.outline);
      selectedObject.userData.outline = null;
    }
    selectedObject = null;
    updateStatus('Ready. Click on objects to select them.');
  }

  function deleteSelected() {
    if (selectedObject) {
      scene.remove(selectedObject);
      if (selectedObject.userData.outline) {
        scene.remove(selectedObject.userData.outline);
      }
      objects = objects.filter(obj => obj !== selectedObject);
      selectedObject = null;
      updateStatus('Object deleted.');
    }
  }

  function toggleMoveMode() {
    interactionMode = 'move';
    updateStatus('Move mode activated. Use arrow keys to move.');
  }

  function toggleRotateMode() {
    interactionMode = 'rotate';
    updateStatus('Rotate mode activated. Use arrow keys to rotate.');
  }

  function toggleScaleMode() {
    interactionMode = 'scale';
    updateStatus('Scale mode activated. Use arrow keys to scale.');
  }

  function changeSelectedColor(color) {
    if (selectedObject) {
      selectedObject.material.color.setStyle(color);
    }
  }

  function addCube() {
    const geometry = new THREE.BoxGeometry(2, 2, 2);
    const material = new THREE.MeshPhongMaterial({ 
      color: document.getElementById('object-color').value,
      transparent: true,
      opacity: 0.9
    });
    const cube = new THREE.Mesh(geometry, material);
    cube.position.y = 1;
    cube.userData.type = 'Cube';
    scene.add(cube);
    objects.push(cube);
    selectObject(cube);
  }

  function addSphere() {
    const geometry = new THREE.SphereGeometry(1, 32, 32);
    const material = new THREE.MeshPhongMaterial({ 
      color: document.getElementById('object-color').value,
      transparent: true,
      opacity: 0.9
    });
    const sphere = new THREE.Mesh(geometry, material);
    sphere.position.y = 1;
    sphere.userData.type = 'Sphere';
    scene.add(sphere);
    objects.push(sphere);
    selectObject(sphere);
  }

  function addCylinder() {
    const geometry = new THREE.CylinderGeometry(1, 1, 2, 32);
    const material = new THREE.MeshPhongMaterial({ 
      color: document.getElementById('object-color').value,
      transparent: true,
      opacity: 0.9
    });
    const cylinder = new THREE.Mesh(geometry, material);
    cylinder.position.y = 1;
    cylinder.userData.type = 'Cylinder';
    scene.add(cylinder);
    objects.push(cylinder);
    selectObject(cylinder);
  }

  function addCone() {
    const geometry = new THREE.ConeGeometry(1, 2, 32);
    const material = new THREE.MeshPhongMaterial({ 
      color: document.getElementById('object-color').value,
      transparent: true,
      opacity: 0.9
    });
    const cone = new THREE.Mesh(geometry, material);
    cone.position.y = 1;
    cone.userData.type = 'Cone';
    scene.add(cone);
    objects.push(cone);
    selectObject(cone);
  }

  function updateStatus(message) {
    document.getElementById('status').textContent = message;
  }
</script>

<style>
  .tinkercad-clone-container {
    display: flex;
    flex-direction: column;
    gap: 20px;
    font-family: Arial, sans-serif;
  }
  
  .controls {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 10px;
    padding: 15px;
    background: #f8f8f8;
    border-radius: 5px;
  }
  
  .controls h3 {
    grid-column: 1 / -1;
    margin: 0 0 10px 0;
    padding-bottom: 5px;
    border-bottom: 1px solid #ddd;
  }
  
  .controls button {
    padding: 8px 12px;
    background: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.3s;
  }
  
  .controls button:hover {
    background: #45a049;
  }
  
  #status {
    padding: 10px;
    background: #e9e9e9;
    border-radius: 4px;
    font-style: italic;
  }
  
  #modeling-area {
    position: relative;
  }
  
  #modeling-area canvas {
    display: block;
  }
</style>