anycoder-562c1b6e / index.html
Karmastudios's picture
Upload folder using huggingface_hub
f554ce2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Scene Editor</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-tertiary: #1a1a24;
--bg-panel: #14141c;
--accent-primary: #6366f1;
--accent-secondary: #818cf8;
--accent-glow: rgba(99, 102, 241, 0.3);
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--border-color: #2a2a3a;
--success: #22c55e;
--warning: #f59e0b;
--danger: #ef4444;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
overflow: hidden;
height: 100vh;
}
.app-container {
display: grid;
grid-template-columns: 280px 1fr 300px;
grid-template-rows: 60px 1fr 200px;
height: 100vh;
gap: 1px;
background: var(--border-color);
}
/* Header */
.header {
grid-column: 1 / -1;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
border-bottom: 1px solid var(--border-color);
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-weight: 600;
font-size: 18px;
}
.logo-icon {
width: 36px;
height: 36px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.header-actions {
display: flex;
gap: 12px;
}
.btn {
padding: 10px 18px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 500;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.btn-primary {
background: var(--accent-primary);
color: white;
}
.btn-primary:hover {
background: var(--accent-secondary);
box-shadow: 0 0 20px var(--accent-glow);
}
.btn-secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--bg-panel);
border-color: var(--accent-primary);
}
.btn-danger {
background: var(--danger);
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
/* Sidebar - Object Library */
.sidebar-left {
background: var(--bg-secondary);
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
font-weight: 600;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 10px;
}
.object-library {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.object-category {
margin-bottom: 24px;
}
.category-title {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 12px;
font-weight: 500;
}
.object-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.object-item {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 16px 12px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
}
.object-item:hover {
border-color: var(--accent-primary);
background: var(--bg-panel);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.object-item i {
font-size: 28px;
margin-bottom: 8px;
color: var(--accent-secondary);
}
.object-item span {
display: block;
font-size: 12px;
color: var(--text-secondary);
}
/* Canvas Area */
.canvas-container {
background: var(--bg-primary);
position: relative;
overflow: hidden;
}
#three-canvas {
width: 100%;
height: 100%;
display: block;
}
.canvas-overlay {
position: absolute;
top: 16px;
left: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.overlay-info {
background: rgba(20, 20, 28, 0.9);
backdrop-filter: blur(10px);
padding: 10px 14px;
border-radius: 8px;
font-size: 12px;
color: var(--text-secondary);
border: 1px solid var(--border-color);
}
.overlay-info span {
color: var(--accent-secondary);
font-weight: 500;
}
/* Right Panel - Properties */
.sidebar-right {
background: var(--bg-secondary);
display: flex;
flex-direction: column;
overflow: hidden;
}
.properties-panel {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.property-group {
margin-bottom: 24px;
}
.property-group-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.property-group-title i {
color: var(--accent-primary);
}
.property-row {
margin-bottom: 14px;
}
.property-label {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 6px;
display: block;
}
.property-input {
width: 100%;
padding: 10px 12px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 8px;
color: var(--text-primary);
font-size: 13px;
transition: border-color 0.2s;
}
.property-input:focus {
outline: none;
border-color: var(--accent-primary);
}
.property-input-row {
display: flex;
gap: 8px;
}
.property-input-group {
flex: 1;
}
.property-input-group label {
font-size: 10px;
color: var(--text-muted);
display: block;
margin-bottom: 4px;
text-align: center;
}
.property-input-group input {
width: 100%;
padding: 8px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--text-primary);
font-size: 12px;
text-align: center;
}
.property-input-group input:focus {
outline: none;
border-color: var(--accent-primary);
}
.color-input-wrapper {
display: flex;
gap: 8px;
align-items: center;
}
.color-input-wrapper input[type="color"] {
width: 40px;
height: 36px;
border: none;
border-radius: 6px;
cursor: pointer;
background: transparent;
}
.color-input-wrapper input[type="text"] {
flex: 1;
}
.no-selection {
text-align: center;
padding: 40px 20px;
color: var(--text-muted);
}
.no-selection i {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.3;
}
.no-selection p {
font-size: 14px;
}
/* Bottom Panel - Scene Hierarchy */
.bottom-panel {
grid-column: 1 / -1;
background: var(--bg-secondary);
display: flex;
flex-direction: column;
}
.scene-objects {
flex: 1;
overflow-x: auto;
overflow-y: hidden;
padding: 12px 16px;
display: flex;
gap: 8px;
align-items: center;
}
.scene-object-card {
min-width: 140px;
max-width: 140px;
height: 100px;
background: var(--bg-tertiary);
border: 2px solid var(--border-color);
border-radius: 12px;
padding: 12px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.scene-object-card:hover {
border-color: var(--text-muted);
}
.scene-object-card.selected {
border-color: var(--accent-primary);
background: rgba(99, 102, 241, 0.1);
}
.scene-object-card .obj-icon {
font-size: 24px;
color: var(--accent-secondary);
}
.scene-object-card .obj-name {
font-size: 12px;
font-weight: 500;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.scene-object-card .obj-type {
font-size: 10px;
color: var(--text-muted);
}
.add-object-card {
min-width: 140px;
max-width: 140px;
height: 100px;
background: transparent;
border: 2px dashed var(--border-color);
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: var(--text-muted);
}
.add-object-card:hover {
border-color: var(--accent-primary);
color: var(--accent-primary);
}
.add-object-card i {
font-size: 28px;
margin-bottom: 8px;
}
.add-object-card span {
font-size: 12px;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(4px);
}
.modal-overlay.active {
display: flex;
}
.modal {
background: var(--bg-secondary);
border-radius: 16px;
border: 1px solid var(--border-color);
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow: hidden;
animation: modalIn 0.3s ease;
}
@keyframes modalIn {
from {
opacity: 0;
transform: scale(0.95) translateY(20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.modal-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-header h3 {
font-size: 18px;
font-weight: 600;
}
.modal-close {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 20px;
padding: 4px;
}
.modal-close:hover {
color: var(--text-primary);
}
.modal-body {
padding: 24px;
overflow-y: auto;
max-height: 400px;
}
.modal-footer {
padding: 16px 24px;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* Code Display */
.code-display {
background: var(--bg-primary);
border-radius: 8px;
padding: 16px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
color: var(--accent-secondary);
overflow-x: auto;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--border-color);
}
/* Responsive */
@media (max-width: 1200px) {
.app-container {
grid-template-columns: 240px 1fr 260px;
}
}
@media (max-width: 900px) {
.app-container {
grid-template-columns: 1fr;
grid-template-rows: 60px 1fr 200px 300px;
}
.sidebar-left,
.sidebar-right {
display: none;
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- Header -->
<header class="header">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-cube"></i>
</div>
<span>3D Scene Editor</span>
</div>
<div class="header-actions">
<button class="btn btn-secondary" onclick="exportScene()">
<i class="fas fa-code"></i> Export Code
</button>
<button class="btn btn-secondary" onclick="importCode()">
<i class="fas fa-file-import"></i> Import
</button>
<button class="btn btn-danger" onclick="clearScene()">
<i class="fas fa-trash"></i> Clear
</button>
</div>
</header>
<!-- Left Sidebar - Object Library -->
<aside class="sidebar-left">
<div class="panel-header">
<i class="fas fa-shapes"></i>
Object Library
</div>
<div class="object-library">
<div class="object-category">
<div class="category-title">Primitives</div>
<div class="object-grid">
<div class="object-item" onclick="addObject('box')">
<i class="fas fa-cube"></i>
<span>Box</span>
</div>
<div class="object-item" onclick="addObject('sphere')">
<i class="fas fa-circle"></i>
<span>Sphere</span>
</div>
<div class="object-item" onclick="addObject('cylinder')">
<i class="fas fa-database"></i>
<span>Cylinder</span>
</div>
<div class="object-item" onclick="addObject('cone')">
<i class="fas fa-caret-up"></i>
<span>Cone</span>
</div>
<div class="object-item" onclick="addObject('torus')">
<i class="fas fa-ring"></i>
<span>Torus</span>
</div>
<div class="object-item" onclick="addObject('plane')">
<i class="fas fa-square"></i>
<span>Plane</span>
</div>
</div>
</div>
<div class="object-category">
<div class="category-title">Advanced</div>
<div class="object-grid">
<div class="object-item" onclick="addObject('torusKnot')">
<i class="fas fa-knot"></i>
<span>Torus Knot</span>
</div>
<div class="object-item" onclick="addObject('icosahedron')">
<i class="fas fa-gem"></i>
<span>Icosahedron</span>
</div>
<div class="object-item" onclick="addObject('octahedron')">
<i class="fas fa-diamond"></i>
<span>Octahedron</span>
</div>
<div class="object-item" onclick="addObject('dodecahedron')">
<i class="fas fa-cubes"></i>
<span>Dodecahedron</span>
</div>
</div>
</div>
<div class="object-category">
<div class="category-title">Environment</div>
<div class="object-grid">
<div class="object-item" onclick="addLight('ambient')">
<i class="fas fa-lightbulb"></i>
<span>Ambient Light</span>
</div>
<div class="object-item" onclick="addLight('directional')">
<i class="fas fa-sun"></i>
<span>Directional</span>
</div>
<div class="object-item" onclick="addLight('point')">
<i class="fas fa-lightning"></i>
<span>Point Light</span>
</div>
<div class="object-item" onclick="addLight('spot')">
<i class="fas fa flashlight"></i>
<span>Spot Light</span>
</div>
</div>
</div>
</div>
</aside>
<!-- Canvas Container -->
<main class="canvas-container">
<canvas id="three-canvas"></canvas>
<div class="canvas-overlay">
<div class="overlay-info">
Objects: <span id="object-count">0</span> | Selected: <span id="selected-name">None</span>
</div>
<div class="overlay-info">
FPS: <span id="fps-counter">60</span>
</div>
</div>
</main>
<!-- Right Sidebar - Properties -->
<aside class="sidebar-right">
<div class="panel-header">
<i class="fas fa-sliders-h"></i>
Properties
</div>
<div class="properties-panel" id="properties-panel">
<div class="no-selection" id="no-selection">
<i class="fas fa-mouse-pointer"></i>
<p>Select an object to edit its properties</p>
</div>
<div id="object-properties" style="display: none;">
<div class="property-group">
<div class="property-group-title">
<i class="fas fa-tag"></i>
Object Name
</div>
<div class="property-row">
<label class="property-label">Name</label>
<input type="text" class="property-input" id="prop-name" onchange="updateSelectedProperty('name', this.value)">
</div>
</div>
<div class="property-group">
<div class="property-group-title">
<i class="fas fa-arrows-alt"></i>
Position
</div>
<div class="property-row">
<div class="property-input-row">
<div class="property-input-group">
<label>X</label>
<input type="number" step="0.1" id="prop-pos-x" onchange="updateSelectedProperty('position', {x: parseFloat(this.value)})">
</div>
<div class="property-input-group">
<label>Y</label>
<input type="number" step="0.1" id="prop-pos-y" onchange="updateSelectedProperty('position', {y: parseFloat(this.value)})">
</div>
<div class="property-input-group">
<label>Z</label>
<input type="number" step="0.1" id="prop-pos-z" onchange="updateSelectedProperty('position', {z: parseFloat(this.value)})">
</div>
</div>
</div>
</div>
<div class="property-group">
<div class="property-group-title">
<i class="fas fa-sync-alt"></i>
Rotation (degrees)
</div>
<div class="property-row">
<div class="property-input-row">
<div class="property-input-group">
<label>X</label>
<input type="number" step="1" id="prop-rot-x" onchange="updateSelectedProperty('rotation', {x: parseFloat(this.value) * Math.PI / 180})">
</div>
<div class="property-input-group">
<label>Y</label>
<input type="number" step="1" id="prop-rot-y" onchange="updateSelectedProperty('rotation', {y: parseFloat(this.value) * Math.PI / 180})">
</div>
<div class="property-input-group">
<label>Z</label>
<input type="number" step="1" id="prop-rot-z" onchange="updateSelectedProperty('rotation', {z: parseFloat(this.value) * Math.PI / 180})">
</div>
</div>
</div>
</div>
<div class="property-group">
<div class="property-group-title">
<i class="fas fa-expand-arrows-alt"></i>
Scale
</div>
<div class="property-row">
<div class="property-input-row">
<div class="property-input-group">
<label>X</label>
<input type="number" step="0.1" id="prop-scale-x" onchange="updateSelectedProperty('scale', {x: parseFloat(this.value)})">
</div>
<div class="property-input-group">
<label>Y</label>
<input type="number" step="0.1" id="prop-scale-y" onchange="updateSelectedProperty('scale', {y: parseFloat(this.value)})">
</div>
<div class="property-input-group">
<label>Z</label>
<input type="number" step="0.1" id="prop-scale-z" onchange="updateSelectedProperty('scale', {z: parseFloat(this.value)})">
</div>
</div>
</div>
</div>
<div class="property-group">
<div class="property-group-title">
<i class="fas fa-palette"></i>
Material
</div>
<div class="property-row">
<label class="property-label">Color</label>
<div class="color-input-wrapper">
<input type="color" id="prop-color" onchange="updateSelectedProperty('color', this.value)">
<input type="text" class="property-input" id="prop-color-hex" onchange="document.getElementById('prop-color').value = this.value; updateSelectedProperty('color', this.value)">
</div>
</div>
<div class="property-row">
<label class="property-label">Metalness</label>
<input type="range" min="0" max="1" step="0.1" class="property-input" id="prop-metalness" onchange="updateSelectedProperty('metalness', parseFloat(this.value))" style="padding: 0;">
</div>
<div class="property-row">
<label class="property-label">Roughness</label>
<input type="range" min="0" max="1" step="0.1" class="property-input" id="prop-roughness" onchange="updateSelectedProperty('roughness', parseFloat(this.value))" style="padding: 0;">
</div>
</div>
<div class="property-group">
<div class="property-group-title">
<i class="fas fa-cog"></i>
Actions
</div>
<div class="property-row" style="display: flex; gap: 8px;">
<button class="btn btn-secondary" style="flex: 1;" onclick="duplicateSelected()">
<i class="fas fa-copy"></i> Duplicate
</button>
<button class="btn btn-danger" style="flex: 1;" onclick="deleteSelected()">
<i class="fas fa-trash"></i> Delete
</button>
</div>
<div class="property-row">
<button class="btn btn-primary" style="width: 100%;" onclick="replaceSelected()">
<i class="fas fa-exchange-alt"></i> Replace Object
</button>
</div>
</div>
</div>
</div>
</aside>
<!-- Bottom Panel - Scene Hierarchy -->
<div class="bottom-panel">
<div class="panel-header">
<i class="fas fa-layer-group"></i>
Scene Objects
</div>
<div class="scene-objects" id="scene-objects">
<div class="add-object-card" onclick="showAddMenu()">
<i class="fas fa-plus"></i>
<span>Add Object</span>
</div>
</div>
</div>
</div>
<!-- Export Modal -->
<div class="modal-overlay" id="export-modal">
<div class="modal">
<div class="modal-header">
<h3>Export 3D Scene Code</h3>
<button class="modal-close" onclick="closeModal('export-modal')">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<p style="margin-bottom: 16px; color: var(--text-secondary); font-size: 14px;">
Copy this HTML code to use your 3D scene:
</p>
<div class="code-display" id="export-code"></div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="copyCode()">
<i class="fas fa-copy"></i> Copy to Clipboard
</button>
<button class="btn btn-primary" onclick="closeModal('export-modal')">
Done
</button>
</div>
</div>
</div>
<!-- Import Modal -->
<div class="modal-overlay" id="import-modal">
<div class="modal">
<div class="modal-header">
<h3>Import 3D Scene</h3>
<button class="modal-close" onclick="closeModal('import-modal')">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<p style="margin-bottom: 16px; color: var(--text-secondary); font-size: 14px;">
Paste your exported 3D scene code below:
</p>
<textarea class="property-input" id="import-code" rows="10" placeholder="Paste your HTML code here..."></textarea>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('import-modal')">
Cancel
</button>
<button class="btn btn-primary" onclick="processImport()">
<i class="fas fa-file-import"></i> Import
</button>
</div>
</div>
</div>
<!-- Replace Object Modal -->
<div class="modal-overlay" id="replace-modal">
<div class="modal">
<div class="modal-header">
<h3>Replace Object</h3>
<button class="modal-close" onclick="closeModal('replace-modal')">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<p style="margin-bottom: 16px; color: var(--text-secondary); font-size: 14px;">
Select a new object type to replace the current one:
</p>
<div class="object-library" style="max-height: 300px;">
<div class="object-category">
<div class="category-title">Replace With</div>
<div class="object-grid">
<div class="object-item" onclick="performReplace('box')">
<i class="fas fa-cube"></i>
<span>Box</span>
</div>
<div class="object-item" onclick="performReplace('sphere')">
<i class="fas fa-circle"></i>
<span>Sphere</span>
</div>
<div class="object-item" onclick="performReplace('cylinder')">
<i class="fas fa-database"></i>
<span>Cylinder</span>
</div>
<div class="object-item" onclick="performReplace('cone')">
<i class="fas fa-caret-up"></i>
<span>Cone</span>
</div>
<div class="object-item" onclick="performReplace('torus')">
<i class="fas fa-ring"></i>
<span>Torus</span>
</div>
<div class="object-item" onclick="performReplace('torusKnot')">
<i class="fas fa-knot"></i>
<span>Torus Knot</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Three.js Setup
let scene, camera, renderer, controls;
let sceneObjects = [];
let selectedObject = null;
let raycaster, mouse;
let clock, frameCount = 0, lastTime = 0;
// Object type icons mapping
const objectIcons = {
'box': 'fa-cube',
'sphere': 'fa-circle',
'cylinder': 'fa-database',
'cone': 'fa-caret-up',
'torus': 'fa-ring',
'plane': 'fa-square',
'torusKnot': 'fa-knot',
'icosahedron': 'fa-gem',
'octahedron': 'fa-diamond',
'dodecahedron': 'fa-cubes',
'ambientLight': 'fa-lightbulb',
'directionalLight': 'fa-sun',
'pointLight': 'fa-lightning',
'spotLight': 'fa-expand'
};
// Initialize Three.js
function init() {
const canvas = document.getElementById('three-canvas');
const container = canvas.parentElement;
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0f);
// Camera
camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(5, 5, 5);
// Renderer
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Raycaster for selection
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
// Clock for FPS
clock = new THREE.Clock();
// Add Grid Helper
const gridHelper = new THREE.GridHelper(20, 20, 0x2a2a3a, 0x1a1a24);
scene.add(gridHelper);
// Add Ambient Light
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
// Add Directional Light
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);
// Event Listeners
canvas.addEventListener('click', onCanvasClick);
window.addEventListener('resize', onWindowResize);
// Start Animation
animate();
}
// Animation Loop
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
frameCount++;
// Update FPS counter
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
document.getElementById('fps-counter').textContent = frameCount;
frameCount = 0;
lastTime = currentTime;
}
controls.update();
renderer.render(scene, camera);
}
// Window Resize Handler
function onWindowResize() {
const container = document.querySelector('.canvas-container');
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
// Canvas Click Handler for Object Selection
function onCanvasClick(event) {
const container = document.querySelector('.canvas-container');
const rect = container.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / container.clientWidth) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / container.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const meshes = sceneObjects.filter(obj => obj.mesh && obj.type !== 'ambientLight' && obj.type !== 'directionalLight' && obj.type !== 'pointLight' && obj.type !== 'spotLight');
const intersects = raycaster.intersectObjects(meshes.map(o => o.mesh));
if (intersects.length > 0) {
const clickedMesh = intersects[0].object;
const obj = sceneObjects.find(o => o.mesh === clickedMesh);
if (obj) {
selectObject(obj);
}
} else {
deselectObject();
}
}
// Create Geometry based on type
function createGeometry(type) {
switch(type) {
case 'box':
return new THREE.BoxGeometry(1, 1, 1);
case 'sphere':
return new THREE.SphereGeometry(0.5, 32, 32);
case 'cylinder':
return new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
case 'cone':
return new THREE.ConeGeometry(0.5, 1, 32);
case 'torus':
return new THREE.TorusGeometry(0.5, 0.2, 16, 100);
case 'plane':
return new THREE.PlaneGeometry(2, 2);
case 'torusKnot':
return new THREE.TorusKnotGeometry(0.4, 0.15, 100, 16);
case 'icosahedron':
return new THREE.IcosahedronGeometry(0.5);
case 'octahedron':
return new THREE.OctahedronGeometry(0.5);
case 'dodecahedron':
return new THREE.DodecahedronGeometry(0.5);
default:
return new THREE.BoxGeometry(1, 1, 1);
}
}
// Add Object to Scene
function addObject(type) {
const geometry = createGeometry(type);
const material = new THREE.MeshStandardMaterial({
color: getRandomColor(),
metalness: 0.3,
roughness: 0.7
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = 0.5;
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
const obj = {
id: Date.now(),
name: `${type}_${sceneObjects.length + 1}`,
type: type,
mesh: mesh,
color: '#' + material.color.getHexString(),
metalness: 0.3,
roughness: 0.7
};
sceneObjects.push(obj);
updateSceneObjectsPanel();
selectObject(obj);
}
// Add Light to Scene
function addLight(type) {
let light;
const color = getRandomColor();
switch(type) {
case 'ambient':
light = new THREE.AmbientLight(color, 0.5);
break;
case 'directional':
light = new THREE.DirectionalLight(color, 1);
light.position.set(5, 10, 5);
break;
case 'point':
light = new THREE.PointLight(color, 1, 10);
break;
case 'spot':
light = new THREE.SpotLight(color, 1);
light.position.set(5, 10, 5);
break;
}
scene.add(light);
const obj = {
id: Date.now(),
name: `${type}_light_${sceneObjects.length + 1}`,
type: type + 'Light',
light: light,
color: '#' + light.color.getHexString(),
intensity: light.intensity || 1
};
sceneObjects.push(obj);
updateSceneObjectsPanel();
selectObject(obj);
}
// Get Random Color
function getRandomColor() {
const colors = [
0x6366f1, 0x818cf8, 0xa5b4fc,
0x22c55e, 0x4ade80, 0x86efac,
0xf59e0b, 0xfbbf24, 0xfcd34d,
0xef4444, 0xf87171, 0xfca5a5,
0x3b82f6, 0x60a5fa, 0x93c5fd,
0xec4899, 0xf472b6, 0xf9a8d4
];
return colors[Math.floor(Math.random() * colors.length)];
}
// Select Object
function selectObject(obj) {
// Deselect previous
if (selectedObject && selectedObject.mesh) {
selectedObject.mesh.material.emissive.setHex(0x000000);
}
selectedObject = obj;
// Highlight selected
if (obj.mesh) {
obj.mesh.material.emissive.setHex(0x333333);
}
// Update UI
document.getElementById('no-selection').style.display = 'none';
document.getElementById('object-properties').style.display = 'block';
document.getElementById('selected-name').textContent = obj.name;
// Update property inputs
document.getElementById('prop-name').value = obj.name;
if (obj.mesh) {
document.getElementById('prop-pos-x').value = obj.mesh.position.x.toFixed(2);
document.getElementById('prop-pos-y').value = obj.mesh.position.y.toFixed(2);
document.getElementById('prop-pos-z').value = obj.mesh.position.z.toFixed(2);
document.getElementById('prop-rot-x').value = (obj.mesh.rotation.x * 180 / Math.PI).toFixed(1);
document.getElementById('prop-rot-y').value = (obj.mesh.rotation.y * 180 / Math.PI).toFixed(1);
document.getElementById('prop-rot-z').value = (obj.mesh.rotation.z * 180 / Math.PI).toFixed(1);
document.getElementById('prop-scale-x').value = obj.mesh.scale.x.toFixed(2);
document.getElementById('prop-scale-y').value = obj.mesh.scale.y.toFixed(2);
document.getElementById('prop-scale-z').value = obj.mesh.scale.z.toFixed(2);
document.getElementById('prop-color').value = obj.color;
document.getElementById('prop-color-hex').value = obj.color;
document.getElementById('prop-metalness').value = obj.metalness;
document.getElementById('prop-roughness').value = obj.roughness;
}
updateSceneObjectsPanel();
}
// Deselect Object
function deselectObject() {
if (selectedObject && selectedObject.mesh) {
selectedObject.mesh.material.emissive.setHex(0x000000);
}
selectedObject = null;
document.getElementById('no-selection').style.display = 'block';
document.getElementById('object-properties').style.display = 'none';
document.getElementById('selected-name').textContent = 'None';
updateSceneObjectsPanel();
}
// Update Selected Property
function updateSelectedProperty(prop, value) {
if (!selectedObject) return;
if (prop === 'name') {
selectedObject.name = value;
document.getElementById('selected-name').textContent = value;
} else if (prop === 'position' && selectedObject.mesh) {
if (value.x !== undefined) selectedObject.mesh.position.x = value.x;
if (value.y !== undefined) selectedObject.mesh.position.y = value.y;
if (value.z !== undefined) selectedObject.mesh.position.z = value.z;
} else if (prop === 'rotation' && selectedObject.mesh) {
if (value.x !== undefined) selectedObject.mesh.rotation.x = value.x;
if (value.y !== undefined) selectedObject.mesh.rotation.y = value.y;
if (value.z !== undefined) selectedObject.mesh.rotation.z