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>