Jesse Afolabi commited on
Commit
3a7a5f3
·
verified ·
1 Parent(s): 793200a

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1408 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Coolbro Testing
3
- emoji: 👁
4
- colorFrom: purple
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: coolbro-testing
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1408 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Babylon.js 8 Advanced 3D Application</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.babylonjs.com/babylon.js"></script>
9
+ <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
10
+ <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>
11
+ <script src="https://cdn.babylonjs.com/proceduralTexturesLibrary/babylon.proceduralTextures.min.js"></script>
12
+ <script src="https://cdn.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
13
+ <style>
14
+ #renderCanvas {
15
+ width: 100%;
16
+ height: 100%;
17
+ touch-action: none;
18
+ outline: none;
19
+ }
20
+
21
+ .loading-overlay {
22
+ position: fixed;
23
+ top: 0;
24
+ left: 0;
25
+ width: 100%;
26
+ height: 100%;
27
+ background: rgba(0, 0, 0, 0.9);
28
+ display: flex;
29
+ flex-direction: column;
30
+ align-items: center;
31
+ justify-content: center;
32
+ z-index: 1000;
33
+ transition: opacity 0.5s;
34
+ }
35
+
36
+ .progress-bar {
37
+ width: 300px;
38
+ height: 4px;
39
+ background: #444;
40
+ margin-top: 20px;
41
+ overflow: hidden;
42
+ }
43
+
44
+ .progress-fill {
45
+ height: 100%;
46
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6);
47
+ width: 0%;
48
+ transition: width 0.3s;
49
+ }
50
+
51
+ @keyframes pulse {
52
+ 0%, 100% { opacity: 1; }
53
+ 50% { opacity: 0.5; }
54
+ }
55
+
56
+ .pulse {
57
+ animation: pulse 2s infinite;
58
+ }
59
+
60
+ .tooltip {
61
+ position: relative;
62
+ display: inline-block;
63
+ }
64
+
65
+ .tooltip .tooltip-text {
66
+ visibility: hidden;
67
+ width: 200px;
68
+ background-color: rgba(0, 0, 0, 0.8);
69
+ color: #fff;
70
+ text-align: center;
71
+ border-radius: 6px;
72
+ padding: 8px;
73
+ position: absolute;
74
+ z-index: 1;
75
+ bottom: 125%;
76
+ left: 50%;
77
+ transform: translateX(-50%);
78
+ opacity: 0;
79
+ transition: opacity 0.3s;
80
+ }
81
+
82
+ .tooltip:hover .tooltip-text {
83
+ visibility: visible;
84
+ opacity: 1;
85
+ }
86
+
87
+ .panel-slide {
88
+ transition: transform 0.3s ease, opacity 0.3s ease;
89
+ }
90
+
91
+ .panel-slide.hidden {
92
+ transform: translateY(100%);
93
+ opacity: 0;
94
+ }
95
+ </style>
96
+ </head>
97
+ <body class="bg-gray-900 text-gray-100 overflow-hidden">
98
+ <!-- Loading Screen -->
99
+ <div id="loadingOverlay" class="loading-overlay">
100
+ <div class="text-4xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
101
+ Loading 3D World
102
+ </div>
103
+ <div class="text-gray-400 mb-2">Initializing Babylon.js 8...</div>
104
+ <div class="progress-bar">
105
+ <div id="progressFill" class="progress-fill"></div>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- Main Container -->
110
+ <div class="flex flex-col h-screen">
111
+ <!-- Top Status Bar -->
112
+ <div class="bg-gray-800 py-2 px-4 flex justify-between items-center">
113
+ <div class="text-xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
114
+ Babylon.js 8 Advanced Demo
115
+ </div>
116
+ <div class="flex space-x-2">
117
+ <div id="fpsCounter" class="px-3 py-1 bg-gray-700 rounded-full text-xs">0 FPS</div>
118
+ <div id="objectCount" class="px-3 py-1 bg-gray-700 rounded-full text-xs">0 Objects</div>
119
+ <div id="sceneInfo" class="px-3 py-1 bg-gray-700 rounded-full text-xs">Default Scene</div>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- Main Content Area -->
124
+ <div class="flex flex-1 overflow-hidden">
125
+ <!-- Side Panel (Collapsible) -->
126
+ <div id="sidePanel" class="w-64 bg-gray-800 border-r border-gray-700 flex flex-col transition-all duration-300 ease-in-out" style="min-width: 16rem;">
127
+ <div class="p-4 border-b border-gray-700 flex justify-between items-center">
128
+ <h2 class="text-lg font-semibold">Scene Controls</h2>
129
+ <button id="togglePanel" class="text-gray-400 hover:text-white">
130
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
131
+ <path fill-rule="evenodd" d="M15.707 15.707a1 1 0 01-1.414 0l-5-5a1 1 0 010-1.414l5-5a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
132
+ </svg>
133
+ </button>
134
+ </div>
135
+
136
+ <div class="flex-1 overflow-y-auto">
137
+ <!-- Scene Management -->
138
+ <div class="p-4 border-b border-gray-700">
139
+ <h3 class="text-sm font-semibold text-gray-400 uppercase mb-3">Scene Management</h3>
140
+ <div class="space-y-2">
141
+ <button id="newScene" class="w-full px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm flex items-center">
142
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
143
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
144
+ </svg>
145
+ New Scene
146
+ </button>
147
+ <button id="saveScene" class="w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm flex items-center">
148
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
149
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
150
+ </svg>
151
+ Save Scene
152
+ </button>
153
+ </div>
154
+ </div>
155
+
156
+ <!-- Environment Settings -->
157
+ <div class="p-4 border-b border-gray-700">
158
+ <h3 class="text-sm font-semibold text-gray-400 uppercase mb-3">Environment</h3>
159
+ <div class="space-y-3">
160
+ <div>
161
+ <label class="block text-xs text-gray-400 mb-1">Skybox</label>
162
+ <select id="skyboxSelect" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
163
+ <option value="default">Default</option>
164
+ <option value="sunset">Sunset</option>
165
+ <option value="night">Night</option>
166
+ <option value="studio">Studio</option>
167
+ <option value="park">Park</option>
168
+ <option value="none">None</option>
169
+ </select>
170
+ </div>
171
+ <div>
172
+ <label class="block text-xs text-gray-400 mb-1">Environment Intensity</label>
173
+ <input id="envIntensity" type="range" min="0" max="2" step="0.1" value="1" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
174
+ </div>
175
+ <div class="flex items-center justify-between">
176
+ <label class="text-xs text-gray-400">Fog</label>
177
+ <button id="toggleFog" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
178
+ Toggle
179
+ </button>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Lighting -->
185
+ <div class="p-4 border-b border-gray-700">
186
+ <h3 class="text-sm font-semibold text-gray-400 uppercase mb-3">Lighting</h3>
187
+ <div class="space-y-3">
188
+ <div>
189
+ <label class="block text-xs text-gray-400 mb-1">Hemispheric Light</label>
190
+ <div class="flex space-x-2">
191
+ <button id="toggleHemiLight" class="flex-1 bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
192
+ Toggle
193
+ </button>
194
+ <button id="adjustHemiLight" class="flex-1 bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
195
+ Adjust
196
+ </button>
197
+ </div>
198
+ </div>
199
+ <div>
200
+ <label class="block text-xs text-gray-400 mb-1">Directional Light</label>
201
+ <div class="flex space-x-2">
202
+ <button id="toggleDirLight" class="flex-1 bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
203
+ Toggle
204
+ </button>
205
+ <button id="adjustDirLight" class="flex-1 bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
206
+ Adjust
207
+ </button>
208
+ </div>
209
+ </div>
210
+ <div>
211
+ <label class="block text-xs text-gray-400 mb-1">Light Intensity</label>
212
+ <input id="lightIntensity" type="range" min="0" max="2" step="0.1" value="1" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- Objects -->
218
+ <div class="p-4 border-b border-gray-700">
219
+ <h3 class="text-sm font-semibold text-gray-400 uppercase mb-3">Objects</h3>
220
+ <div class="grid grid-cols-2 gap-2">
221
+ <button id="addSphere" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-2 text-xs flex flex-col items-center">
222
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
223
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
224
+ </svg>
225
+ Sphere
226
+ </button>
227
+ <button id="addBox" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-2 text-xs flex flex-col items-center">
228
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
229
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
230
+ </svg>
231
+ Box
232
+ </button>
233
+ <button id="addCylinder" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-2 text-xs flex flex-col items-center">
234
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
235
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18.5V20m0 0v1.5m0-1.5h-1.5m1.5 0h1.5M5 10v4h14v-4H5z" />
236
+ </svg>
237
+ Cylinder
238
+ </button>
239
+ <button id="addPlane" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-2 text-xs flex flex-col items-center">
240
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
241
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
242
+ </svg>
243
+ Plane
244
+ </button>
245
+ </div>
246
+ <div class="mt-3">
247
+ <button id="clearObjects" class="w-full bg-red-600 hover:bg-red-700 rounded px-3 py-2 text-sm flex items-center justify-center">
248
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
249
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
250
+ </svg>
251
+ Clear All
252
+ </button>
253
+ </div>
254
+ </div>
255
+
256
+ <!-- Materials -->
257
+ <div class="p-4 border-b border-gray-700">
258
+ <h3 class="text-sm font-semibold text-gray-400 uppercase mb-3">Materials</h3>
259
+ <div class="space-y-3">
260
+ <div>
261
+ <label class="block text-xs text-gray-400 mb-1">Material Type</label>
262
+ <select id="materialType" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
263
+ <option value="standard">Standard</option>
264
+ <option value="pbr">PBR</option>
265
+ <option value="gradient">Gradient</option>
266
+ <option value="grid">Grid</option>
267
+ <option value="brick">Brick</option>
268
+ </select>
269
+ </div>
270
+ <div class="grid grid-cols-3 gap-2">
271
+ <button id="colorRed" class="bg-red-600 hover:bg-red-700 h-6 rounded"></button>
272
+ <button id="colorGreen" class="bg-green-600 hover:bg-green-700 h-6 rounded"></button>
273
+ <button id="colorBlue" class="bg-blue-600 hover:bg-blue-700 h-6 rounded"></button>
274
+ <button id="colorYellow" class="bg-yellow-600 hover:bg-yellow-700 h-6 rounded"></button>
275
+ <button id="colorPurple" class="bg-purple-600 hover:bg-purple-700 h-6 rounded"></button>
276
+ <button id="colorGray" class="bg-gray-600 hover:bg-gray-700 h-6 rounded"></button>
277
+ </div>
278
+ <div>
279
+ <label class="block text-xs text-gray-400 mb-1">Texture Scale</label>
280
+ <input id="textureScale" type="range" min="0.1" max="2" step="0.1" value="1" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
281
+ </div>
282
+ </div>
283
+ </div>
284
+
285
+ <!-- Post Processing -->
286
+ <div class="p-4">
287
+ <h3 class="text-sm font-semibold text-gray-400 uppercase mb-3">Effects</h3>
288
+ <div class="space-y-2">
289
+ <div class="flex items-center justify-between">
290
+ <label class="text-xs">Bloom</label>
291
+ <button id="toggleBloom" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
292
+ Enable
293
+ </button>
294
+ </div>
295
+ <div class="flex items-center justify-between">
296
+ <label class="text-xs">Depth of Field</label>
297
+ <button id="toggleDOF" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
298
+ Disable
299
+ </button>
300
+ </div>
301
+ <div class="flex items-center justify-between">
302
+ <label class="text-xs">Grain</label>
303
+ <button id="toggleGrain" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
304
+ Enable
305
+ </button>
306
+ </div>
307
+ <div class="flex items-center justify-between">
308
+ <label class="text-xs">SSAO</label>
309
+ <button id="toggleSSAO" class="bg-gray-700 hover:bg-gray-600 rounded px-2 py-1 text-xs">
310
+ Enable
311
+ </button>
312
+ </div>
313
+ </div>
314
+ </div>
315
+ </div>
316
+ </div>
317
+
318
+ <!-- 3D Viewport -->
319
+ <div class="flex-1 relative">
320
+ <canvas id="renderCanvas"></canvas>
321
+
322
+ <!-- Viewport Controls -->
323
+ <div class="absolute bottom-4 right-4 flex space-x-2">
324
+ <button id="resetCamera" class="p-2 bg-gray-800 hover:bg-gray-700 rounded-full shadow-lg tooltip">
325
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
326
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
327
+ </svg>
328
+ <span class="tooltip-text">Reset Camera</span>
329
+ </button>
330
+ <button id="screenshot" class="p-2 bg-gray-800 hover:bg-gray-700 rounded-full shadow-lg tooltip">
331
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
332
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
333
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
334
+ </svg>
335
+ <span class="tooltip-text">Take Screenshot</span>
336
+ </button>
337
+ <button id="toggleFullscreen" class="p-2 bg-gray-800 hover:bg-gray-700 rounded-full shadow-lg tooltip">
338
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
339
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
340
+ </svg>
341
+ <span class="tooltip-text">Toggle Fullscreen</span>
342
+ </button>
343
+ </div>
344
+
345
+ <!-- Mini Stats -->
346
+ <div class="absolute top-4 left-4 bg-black bg-opacity-70 rounded-lg p-2 text-xs">
347
+ <div>Objects: <span id="miniObjCount">0</span></div>
348
+ <div>FPS: <span id="miniFps">0</span></div>
349
+ <div>Draw Calls: <span id="miniDrawCalls">0</span></div>
350
+ </div>
351
+
352
+ <!-- Selection Info -->
353
+ <div id="selectionInfo" class="absolute top-4 right-4 bg-black bg-opacity-70 rounded-lg p-3 max-w-xs hidden">
354
+ <div class="flex justify-between items-start mb-2">
355
+ <h3 class="font-semibold">Selected Object</h3>
356
+ <button id="closeSelection" class="text-gray-400 hover:text-white">
357
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
358
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
359
+ </svg>
360
+ </button>
361
+ </div>
362
+ <div class="text-sm">
363
+ <div id="selectedName" class="truncate mb-1">Name:</div>
364
+ <div id="selectedType" class="text-gray-400 text-xs mb-2">Type:</div>
365
+ <div class="grid grid-cols-2 gap-2 text-xs">
366
+ <div>Position: <span id="selectedPosition">0,0,0</span></div>
367
+ <div>Rotation: <span id="selectedRotation">0,0,0</span></div>
368
+ <div>Scaling: <span id="selectedScaling">1,1,1</span></div>
369
+ <div>Material: <span id="selectedMaterial">Standard</span></div>
370
+ </div>
371
+ </div>
372
+ <div class="mt-3 pt-2 border-t border-gray-700">
373
+ <button id="deleteSelected" class="w-full bg-red-600 hover:bg-red-700 rounded px-2 py-1 text-xs">
374
+ Delete
375
+ </button>
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+ </div>
381
+
382
+ <!-- WebGPU Warning Modal -->
383
+ <div id="webgpuModal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50 hidden">
384
+ <div class="bg-gray-800 rounded-lg p-6 max-w-md mx-4">
385
+ <div class="text-xl font-bold mb-4 text-yellow-400 flex items-center">
386
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
387
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
388
+ </svg>
389
+ WebGPU Not Available
390
+ </div>
391
+ <p class="mb-4 text-gray-300">
392
+ Your browser doesn't support WebGPU or it's not enabled. The application will fall back to WebGL, which may affect performance.
393
+ </p>
394
+ <div class="flex justify-end space-x-3">
395
+ <button id="continueButton" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded">
396
+ Continue Anyway
397
+ </button>
398
+ <button id="learnButton" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded">
399
+ Learn More
400
+ </button>
401
+ </div>
402
+ </div>
403
+ </div>
404
+
405
+ <!-- Screenshot Preview -->
406
+ <div id="screenshotPreview" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50 hidden">
407
+ <div class="bg-gray-900 rounded-lg p-2 shadow-2xl max-w-5xl">
408
+ <div class="flex justify-between items-center mb-2">
409
+ <h3 class="text-lg font-semibold">Screenshot Preview</h3>
410
+ <button id="closePreview" class="text-gray-400 hover:text-white p-1">
411
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
412
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
413
+ </svg>
414
+ </button>
415
+ </div>
416
+ <img id="previewImage" class="max-w-full max-h-[80vh]" src="" alt="Screenshot preview">
417
+ <div class="flex justify-between mt-3">
418
+ <button id="downloadScreenshot" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded">
419
+ Download
420
+ </button>
421
+ <button id="copyScreenshot" class="px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded">
422
+ Copy to Clipboard
423
+ </button>
424
+ </div>
425
+ </div>
426
+ </div>
427
+
428
+ <script>
429
+ // Immediately Invoked Function Expression (IIFE) to encapsulate the application
430
+ const BabylonApp = (() => {
431
+ // Private variables
432
+ let engine, scene, canvas;
433
+ let hemisphericLight, directionalLight, shadowGenerator;
434
+ let advancedTexture, fpsLabel, objectCountLabel;
435
+ let bloomEffect, depthOfFieldEffect, grainEffect, ssaoEffect;
436
+ let currentSelection = null;
437
+
438
+ // Configuration
439
+ const config = {
440
+ defaultScene: {
441
+ groundSize: 20,
442
+ groundSegments: 64,
443
+ skyboxPath: 'https://assets.babylonjs.com/environments/skybox',
444
+ skyboxFiles: ['px.png', 'py.png', 'pz.png', 'nx.png', 'ny.png', 'nz.png']
445
+ },
446
+ materials: {
447
+ colors: {
448
+ red: new BABYLON.Color3(0.8, 0.2, 0.2),
449
+ green: new BABYLON.Color3(0.2, 0.8, 0.2),
450
+ blue: new BABYLON.Color3(0.2, 0.2, 0.8),
451
+ yellow: new BABYLON.Color3(0.8, 0.8, 0.2),
452
+ purple: new BABYLON.Color3(0.6, 0.2, 0.8),
453
+ gray: new BABYLON.Color3(0.5, 0.5, 0.5)
454
+ }
455
+ },
456
+ performance: {
457
+ updateInterval: 500 // ms
458
+ }
459
+ };
460
+
461
+ // State management
462
+ const state = {
463
+ currentMaterial: 'standard',
464
+ currentColor: config.materials.colors.red,
465
+ postProcessing: {
466
+ bloom: false,
467
+ depthOfField: false,
468
+ grain: false,
469
+ ssao: false
470
+ },
471
+ lights: {
472
+ hemispheric: true,
473
+ directional: true,
474
+ intensity: 1
475
+ },
476
+ environment: {
477
+ skybox: 'default',
478
+ intensity: 1,
479
+ fog: false
480
+ }
481
+ };
482
+
483
+ // DOM elements
484
+ const dom = {
485
+ // Loading screen
486
+ loadingOverlay: document.getElementById('loadingOverlay'),
487
+ progressFill: document.getElementById('progressFill'),
488
+
489
+ // Panels
490
+ sidePanel: document.getElementById('sidePanel'),
491
+ togglePanel: document.getElementById('togglePanel'),
492
+
493
+ // Status bars
494
+ fpsCounter: document.getElementById('fpsCounter'),
495
+ objectCount: document.getElementById('objectCount'),
496
+ sceneInfo: document.getElementById('sceneInfo'),
497
+ miniFps: document.getElementById('miniFps'),
498
+ miniObjCount: document.getElementById('miniObjCount'),
499
+ miniDrawCalls: document.getElementById('miniDrawCalls'),
500
+
501
+ // Selection info
502
+ selectionInfo: document.getElementById('selectionInfo'),
503
+ closeSelection: document.getElementById('closeSelection'),
504
+ selectedName: document.getElementById('selectedName'),
505
+ selectedType: document.getElementById('selectedType'),
506
+ selectedPosition: document.getElementById('selectedPosition'),
507
+ selectedRotation: document.getElementById('selectedRotation'),
508
+ selectedScaling: document.getElementById('selectedScaling'),
509
+ selectedMaterial: document.getElementById('selectedMaterial'),
510
+ deleteSelected: document.getElementById('deleteSelected'),
511
+
512
+ // Modals
513
+ webgpuModal: document.getElementById('webgpuModal'),
514
+ continueButton: document.getElementById('continueButton'),
515
+ learnButton: document.getElementById('learnButton'),
516
+ screenshotPreview: document.getElementById('screenshotPreview'),
517
+ closePreview: document.getElementById('closePreview'),
518
+ previewImage: document.getElementById('previewImage'),
519
+ downloadScreenshot: document.getElementById('downloadScreenshot'),
520
+ copyScreenshot: document.getElementById('copyScreenshot'),
521
+
522
+ // Scene management
523
+ newScene: document.getElementById('newScene'),
524
+ saveScene: document.getElementById('saveScene'),
525
+
526
+ // Environment
527
+ skyboxSelect: document.getElementById('skyboxSelect'),
528
+ envIntensity: document.getElementById('envIntensity'),
529
+ toggleFog: document.getElementById('toggleFog'),
530
+
531
+ // Lights
532
+ toggleHemiLight: document.getElementById('toggleHemiLight'),
533
+ toggleDirLight: document.getElementById('toggleDirLight'),
534
+ adjustHemiLight: document.getElementById('adjustHemiLight'),
535
+ adjustDirLight: document.getElementById('adjustDirLight'),
536
+ lightIntensity: document.getElementById('lightIntensity'),
537
+
538
+ // Objects
539
+ addSphere: document.getElementById('addSphere'),
540
+ addBox: document.getElementById('addBox'),
541
+ addCylinder: document.getElementById('addCylinder'),
542
+ addPlane: document.getElementById('addPlane'),
543
+ clearObjects: document.getElementById('clearObjects'),
544
+
545
+ // Materials
546
+ materialType: document.getElementById('materialType'),
547
+ colorRed: document.getElementById('colorRed'),
548
+ colorGreen: document.getElementById('colorGreen'),
549
+ colorBlue: document.getElementById('colorBlue'),
550
+ colorYellow: document.getElementById('colorYellow'),
551
+ colorPurple: document.getElementById('colorPurple'),
552
+ colorGray: document.getElementById('colorGray'),
553
+ textureScale: document.getElementById('textureScale'),
554
+
555
+ // Effects
556
+ toggleBloom: document.getElementById('toggleBloom'),
557
+ toggleDOF: document.getElementById('toggleDOF'),
558
+ toggleGrain: document.getElementById('toggleGrain'),
559
+ toggleSSAO: document.getElementById('toggleSSAO'),
560
+
561
+ // Viewport controls
562
+ resetCamera: document.getElementById('resetCamera'),
563
+ screenshot: document.getElementById('screenshot'),
564
+ toggleFullscreen: document.getElementById('toggleFullscreen')
565
+ };
566
+
567
+ // ==============================================
568
+ // Utility Functions
569
+ // ==============================================
570
+
571
+ // Create a loading progress updater using currying
572
+ const createProgressUpdater = (totalSteps) => {
573
+ let currentStep = 0;
574
+
575
+ return (increment = 1) => {
576
+ currentStep += increment;
577
+ const percentage = (currentStep / totalSteps) * 100;
578
+ dom.progressFill.style.width = `${percentage}%`;
579
+
580
+ // Hide loading screen when complete
581
+ if (currentStep >= totalSteps) {
582
+ setTimeout(() => {
583
+ dom.loadingOverlay.style.opacity = '0';
584
+ setTimeout(() => {
585
+ dom.loadingOverlay.style.display = 'none';
586
+ }, 500);
587
+ }, 300);
588
+ }
589
+ };
590
+ };
591
+
592
+ // Get a random position in a donut shape around origin
593
+ const getRandomPosition = (minRadius = 3, maxRadius = 8, yRange = [0.5, 3]) => {
594
+ const angle = Math.random() * Math.PI * 2;
595
+ const radius = minRadius + Math.random() * (maxRadius - minRadius);
596
+ const x = Math.cos(angle) * radius;
597
+ const z = Math.sin(angle) * radius;
598
+ const y = yRange[0] + Math.random() * (yRange[1] - yRange[0]);
599
+
600
+ return new BABYLON.Vector3(x, y, z);
601
+ };
602
+
603
+ // Create a gradient material using higher-order functions
604
+ const createMaterialFactory = (scene) => (type) => (options = {}) => {
605
+ let material;
606
+
607
+ switch(type) {
608
+ case 'standard':
609
+ material = new BABYLON.StandardMaterial(`mat_${type}_${Date.now()}`, scene);
610
+ if (options.diffuseColor) material.diffuseColor = options.diffuseColor;
611
+ if (options.specularColor) material.specularColor = options.specularColor;
612
+ if (options.emissiveColor) material.emissiveColor = options.emissiveColor;
613
+ material.alpha = options.alpha || 1;
614
+ break;
615
+
616
+ case 'pbr':
617
+ material = new BABYLON.PBRMaterial(`mat_${type}_${Date.now()}`, scene);
618
+ if (options.albedoColor) material.albedoColor = options.albedoColor;
619
+ if (options.metallic !== undefined) material.metallic = options.metallic;
620
+ if (options.roughness !== undefined) material.roughness = options.roughness;
621
+ material.alpha = options.alpha || 1;
622
+ break;
623
+
624
+ case 'gradient':
625
+ material = new BABYLON.GradientMaterial(`mat_${type}_${Date.now()}`, scene);
626
+ if (options.topColor) material.topColor = options.topColor;
627
+ if (options.bottomColor) material.bottomColor = options.bottomColor;
628
+ if (options.offset !== undefined) material.offset = options.offset;
629
+ if (options.scale !== undefined) material.scale = options.scale;
630
+ break;
631
+
632
+ case 'grid':
633
+ material = new BABYLON.GridMaterial(`mat_${type}_${Date.now()}`, scene);
634
+ if (options.mainColor) material.mainColor = options.mainColor;
635
+ if (options.lineColor) material.lineColor = options.lineColor;
636
+ if (options.gridRatio !== undefined) material.gridRatio = options.gridRatio;
637
+ if (options.scale !== undefined) material.scale = options.scale;
638
+ break;
639
+
640
+ case 'brick':
641
+ const brickTexture = new BABYLON.BrickProceduralTexture('brickTex', 512, scene);
642
+ if (options.brickColor) brickTexture.brickColor = options.brickColor;
643
+ if (options.jointColor) brickTexture.jointColor = options.jointColor;
644
+ if (options.numberOfBricksHeight !== undefined) brickTexture.numberOfBricksHeight = options.numberOfBricksHeight;
645
+
646
+ material = new BABYLON.StandardMaterial(`mat_${type}_${Date.now()}`, scene);
647
+ material.diffuseTexture = brickTexture;
648
+ break;
649
+ }
650
+
651
+ return material;
652
+ };
653
+
654
+ // Format position/rotation/scaling for display
655
+ const formatVector = (vector) => {
656
+ const x = vector.x.toFixed(2);
657
+ const y = vector.y.toFixed(2);
658
+ const z = vector.z.toFixed(2);
659
+ return `${x}, ${y}, ${z}`;
660
+ };
661
+
662
+ // ==============================================
663
+ // Scene Initialization
664
+ // ==============================================
665
+
666
+ // Initialize the Babylon.js engine and scene
667
+ const initScene = async () => {
668
+ const updateProgress = createProgressUpdater(10);
669
+
670
+ try {
671
+ // Check for WebGPU support
672
+ const webGPUSupported = await BABYLON.WebGPUEngine.IsSupportedAsync;
673
+ if (!webGPUSupported) {
674
+ dom.webgpuModal.classList.remove('hidden');
675
+ updateProgress(1);
676
+ }
677
+
678
+ // Get the canvas
679
+ canvas = document.getElementById('renderCanvas');
680
+
681
+ // Initialize the engine
682
+ engine = new BABYLON.Engine(
683
+ canvas,
684
+ true,
685
+ {
686
+ preserveDrawingBuffer: true,
687
+ stencil: true,
688
+ antialias: true,
689
+ adaptToDeviceRatio: true
690
+ }
691
+ );
692
+ updateProgress(1);
693
+
694
+ // Create the scene
695
+ scene = new BABYLON.Scene(engine);
696
+ scene.autoClear = false;
697
+ scene.autoClearDepthAndStencil = false;
698
+ updateProgress(1);
699
+
700
+ // Create camera
701
+ const camera = new BABYLON.ArcRotateCamera(
702
+ "camera",
703
+ -Math.PI / 2,
704
+ Math.PI / 2,
705
+ 15,
706
+ BABYLON.Vector3.Zero(),
707
+ scene
708
+ );
709
+ camera.attachControl(canvas, true);
710
+ camera.lowerRadiusLimit = 5;
711
+ camera.upperRadiusLimit = 30;
712
+ camera.wheelDeltaPercentage = 0.01;
713
+ camera.panningSensibility = 50;
714
+ camera.allowUpsideDown = false;
715
+ updateProgress(1);
716
+
717
+ // Create default lights
718
+ setupLights();
719
+ updateProgress(1);
720
+
721
+ // Create ground
722
+ createGround();
723
+ updateProgress(1);
724
+
725
+ // Setup shadow generator
726
+ setupShadows();
727
+ updateProgress(1);
728
+
729
+ // Setup skybox
730
+ setupSkybox('default');
731
+ updateProgress(1);
732
+
733
+ // Setup post-processing
734
+ setupPostProcessing();
735
+ updateProgress(1);
736
+
737
+ // Setup GUI
738
+ setupGUI();
739
+ updateProgress(1);
740
+
741
+ // Setup event listeners
742
+ setupEventListeners();
743
+ updateProgress(1);
744
+
745
+ // Setup picking/selection
746
+ setupPicking();
747
+ updateProgress(1);
748
+
749
+ // Run the render loop
750
+ engine.runRenderLoop(() => {
751
+ scene.render();
752
+ updateStats();
753
+ });
754
+
755
+ // Handle resize
756
+ window.addEventListener('resize', () => {
757
+ engine.resize();
758
+ });
759
+
760
+ } catch (error) {
761
+ console.error("Error initializing scene:", error);
762
+ }
763
+ };
764
+
765
+ // ==============================================
766
+ // Scene Setup Functions
767
+ // ==============================================
768
+
769
+ // Setup lights in the scene
770
+ const setupLights = () => {
771
+ // Hemispheric light (ambient)
772
+ hemisphericLight = new BABYLON.HemisphericLight(
773
+ "hemiLight",
774
+ new BABYLON.Vector3(0, 1, 0),
775
+ scene
776
+ );
777
+ hemisphericLight.intensity = state.lights.intensity;
778
+ hemisphericLight.diffuse = new BABYLON.Color3(1, 1, 1);
779
+ hemisphericLight.groundColor = new BABYLON.Color3(0.5, 0.5, 0.5);
780
+
781
+ // Directional light (sun-like)
782
+ directionalLight = new BABYLON.DirectionalLight(
783
+ "dirLight",
784
+ new BABYLON.Vector3(-1, -2, -1),
785
+ scene
786
+ );
787
+ directionalLight.position = new BABYLON.Vector3(0, 10, 0);
788
+ directionalLight.intensity = state.lights.intensity * 0.8;
789
+ directionalLight.shadowMinZ = 1;
790
+ directionalLight.shadowMaxZ = 20;
791
+ };
792
+
793
+ // Setup ground in the scene
794
+ const createGround = () => {
795
+ const ground = BABYLON.MeshBuilder.CreateGround(
796
+ "ground",
797
+ {
798
+ width: config.defaultScene.groundSize,
799
+ height: config.defaultScene.groundSize,
800
+ subdivisions: config.defaultScene.groundSegments
801
+ },
802
+ scene
803
+ );
804
+
805
+ const material = createMaterialFactory(scene)('pbr')({
806
+ albedoColor: new BABYLON.Color3(0.2, 0.2, 0.25),
807
+ metallic: 0.1,
808
+ roughness: 0.8
809
+ });
810
+
811
+ ground.material = material;
812
+ ground.receiveShadows = true;
813
+ shadowGenerator.addShadowCaster(ground);
814
+ };
815
+
816
+ // Setup shadows in the scene
817
+ const setupShadows = () => {
818
+ shadowGenerator = new BABYLON.ShadowGenerator(2048, directionalLight);
819
+ shadowGenerator.useBlurExponentialShadowMap = true;
820
+ shadowGenerator.blurKernel = 32;
821
+ shadowGenerator.bias = 0.0001;
822
+ shadowGenerator.normalBias = 0.02;
823
+ shadowGenerator.forceBackFacesOnly = false;
824
+ };
825
+
826
+ // Setup skybox
827
+ const setupSkybox = (type) => {
828
+ // Remove existing skybox if any
829
+ if (scene.skybox) {
830
+ scene.skybox.dispose();
831
+ }
832
+
833
+ if (type === 'none') return;
834
+
835
+ let skyboxPath = config.defaultScene.skyboxPath;
836
+ if (type !== 'default') {
837
+ skyboxPath = `https://assets.babylonjs.com/environments/${type}env`;
838
+ }
839
+
840
+ scene.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
841
+ skyboxPath + ".env",
842
+ scene
843
+ );
844
+
845
+ if (type !== 'default') {
846
+ const skybox = scene.createDefaultSkybox(scene.environmentTexture, true, 1000);
847
+ skybox.ignorePicking = true;
848
+ }
849
+ };
850
+
851
+ // Setup post-processing effects
852
+ const setupPostProcessing = () => {
853
+ // Create default pipeline
854
+ const defaultPipeline = new BABYLON.DefaultRenderingPipeline(
855
+ "defaultPipeline",
856
+ true,
857
+ scene,
858
+ [scene.activeCamera]
859
+ );
860
+
861
+ // Bloom effect
862
+ bloomEffect = new BABYLON.BloomEffect(
863
+ "bloom",
864
+ scene,
865
+ 1.0, // Bloom scale
866
+ 0.8, // Bloom threshold
867
+ 4.0, // Bloom weight
868
+ 200 // Blur kernel size
869
+ );
870
+ bloomEffect.enabled = state.postProcessing.bloom;
871
+ defaultPipeline.addEffect(bloomEffect);
872
+
873
+ // Depth of Field
874
+ depthOfFieldEffect = new BABYLON.DepthOfFieldEffect(
875
+ scene,
876
+ { target: scene.activeCamera, focusDistance: 0.1, fStop: 1.4 }
877
+ );
878
+ depthOfFieldEffect.enabled = state.postProcessing.depthOfField;
879
+ defaultPipeline.addEffect(depthOfFieldEffect);
880
+
881
+ // Grain effect
882
+ grainEffect = new BABYLON.GrainEffect("grain", scene);
883
+ grainEffect.enabled = state.postProcessing.grain;
884
+ grainEffect.intensity = 0.3;
885
+ defaultPipeline.addEffect(grainEffect);
886
+
887
+ // SSAO effect
888
+ ssaoEffect = new BABYLON.SSAORenderingPipeline(
889
+ "ssao",
890
+ scene,
891
+ { ssaoRatio: 0.5, combineRatio: 0.5 }
892
+ );
893
+ ssaoEffect.enabled = state.postProcessing.ssao;
894
+ };
895
+
896
+ // Setup GUI
897
+ const setupGUI = () => {
898
+ // Advanced Texture for GUI
899
+ advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
900
+
901
+ // FPS Label
902
+ fpsLabel = new BABYLON.GUI.TextBlock();
903
+ fpsLabel.text = "FPS: 0";
904
+ fpsLabel.color = "white";
905
+ fpsLabel.fontSize = 14;
906
+ fpsLabel.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
907
+ fpsLabel.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
908
+ fpsLabel.left = "20px";
909
+ fpsLabel.top = "20px";
910
+ fpsLabel.fontFamily = "Arial";
911
+ advancedTexture.addControl(fpsLabel);
912
+
913
+ // Object Count Label
914
+ objectCountLabel = new BABYLON.GUI.TextBlock();
915
+ objectCountLabel.text = "Objects: 0";
916
+ objectCountLabel.color = "white";
917
+ objectCountLabel.fontSize = 14;
918
+ objectCountLabel.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
919
+ objectCountLabel.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
920
+ objectCountLabel.left = "20px";
921
+ objectCountLabel.top = "40px";
922
+ objectCountLabel.fontFamily = "Arial";
923
+ advancedTexture.addControl(objectCountLabel);
924
+ };
925
+
926
+ // Setup object picking/selection
927
+ const setupPicking = () => {
928
+ scene.onPointerDown = (evt, pickResult) => {
929
+ if (!pickResult.hit || !pickResult.pickedMesh) {
930
+ hideSelectionInfo();
931
+ currentSelection = null;
932
+ return;
933
+ }
934
+
935
+ const mesh = pickResult.pickedMesh;
936
+
937
+ // Don't select ground, skybox, etc.
938
+ if (!mesh.name || mesh.name === 'ground' || mesh.name.startsWith('skybox') || mesh.name.startsWith('_default_')) {
939
+ hideSelectionInfo();
940
+ currentSelection = null;
941
+ return;
942
+ }
943
+
944
+ showSelectionInfo(mesh);
945
+ currentSelection = mesh;
946
+ };
947
+ };
948
+
949
+ // ==============================================
950
+ // Scene Management Functions
951
+ // ==============================================
952
+
953
+ // Create a new primitive object in the scene
954
+ const createPrimitive = (type, position = null) => {
955
+ if (!scene) return;
956
+
957
+ const meshPosition = position || getRandomPosition();
958
+ let mesh;
959
+
960
+ switch(type) {
961
+ case 'sphere':
962
+ mesh = BABYLON.MeshBuilder.CreateSphere(
963
+ `sphere_${Date.now()}`,
964
+ { diameter: 1 + Math.random() * 2 },
965
+ scene
966
+ );
967
+ break;
968
+
969
+ case 'box':
970
+ mesh = BABYLON.MeshBuilder.CreateBox(
971
+ `box_${Date.now()}`,
972
+ {
973
+ size: 1 + Math.random() * 2,
974
+ width: 1 + Math.random() * 2,
975
+ height: 1 + Math.random() * 2,
976
+ depth: 1 + Math.random() * 2
977
+ },
978
+ scene
979
+ );
980
+ break;
981
+
982
+ case 'cylinder':
983
+ mesh = BABYLON.MeshBuilder.CreateCylinder(
984
+ `cylinder_${Date.now()}`,
985
+ {
986
+ diameterTop: 1 + Math.random(),
987
+ diameterBottom: 1 + Math.random(),
988
+ height: 1 + Math.random() * 2,
989
+ tessellation: 32
990
+ },
991
+ scene
992
+ );
993
+ break;
994
+
995
+ case 'plane':
996
+ mesh = BABYLON.MeshBuilder.CreatePlane(
997
+ `plane_${Date.now()}`,
998
+ {
999
+ size: 1 + Math.random() * 3,
1000
+ width: 1 + Math.random() * 3,
1001
+ height: 1 + Math.random() * 3
1002
+ },
1003
+ scene
1004
+ );
1005
+ break;
1006
+
1007
+ default:
1008
+ return;
1009
+ }
1010
+
1011
+ // Position the mesh
1012
+ mesh.position = meshPosition;
1013
+
1014
+ // Random rotation
1015
+ mesh.rotation = new BABYLON.Vector3(
1016
+ Math.random() * Math.PI * 2,
1017
+ Math.random() * Math.PI * 2,
1018
+ Math.random() * Math.PI * 2
1019
+ );
1020
+
1021
+ // Random scaling
1022
+ const scale = 0.5 + Math.random();
1023
+ mesh.scaling = new BABYLON.Vector3(scale, scale, scale);
1024
+
1025
+ // Apply material
1026
+ applyCurrentMaterialToMesh(mesh);
1027
+
1028
+ // Add to shadow generator
1029
+ shadowGenerator.addShadowCaster(mesh);
1030
+
1031
+ return mesh;
1032
+ };
1033
+
1034
+ // Apply the current selected material to a mesh
1035
+ const applyCurrentMaterialToMesh = (mesh) => {
1036
+ if (!mesh) return;
1037
+
1038
+ const materialFactory = createMaterialFactory(scene);
1039
+ let material;
1040
+
1041
+ switch(state.currentMaterial) {
1042
+ case 'standard':
1043
+ material = materialFactory('standard')({
1044
+ diffuseColor: state.currentColor,
1045
+ specularColor: new BABYLON.Color3(0.5, 0.5, 0.5),
1046
+ emissiveColor: new BABYLON.Color3(0.1, 0.1, 0.1)
1047
+ });
1048
+ break;
1049
+
1050
+ case 'pbr':
1051
+ material = materialFactory('pbr')({
1052
+ albedoColor: state.currentColor,
1053
+ metallic: Math.random(),
1054
+ roughness: Math.random()
1055
+ });
1056
+ break;
1057
+
1058
+ case 'gradient':
1059
+ material = materialFactory('gradient')({
1060
+ topColor: state.currentColor,
1061
+ bottomColor: state.currentColor.scale(0.5),
1062
+ offset: Math.random() * 2,
1063
+ scale: parseFloat(dom.textureScale.value)
1064
+ });
1065
+ break;
1066
+
1067
+ case 'grid':
1068
+ material = materialFactory('grid')({
1069
+ mainColor: state.currentColor,
1070
+ lineColor: state.currentColor.scale(0.5),
1071
+ gridRatio: 0.2 + Math.random() * 0.3,
1072
+ scale: parseFloat(dom.textureScale.value)
1073
+ });
1074
+ break;
1075
+
1076
+ case 'brick':
1077
+ const brickOptions = {
1078
+ brickColor: state.currentColor,
1079
+ jointColor: state.currentColor.scale(0.3),
1080
+ numberOfBricksHeight: 5 + Math.floor(Math.random() * 10)
1081
+ };
1082
+
1083
+ material = materialFactory('brick')(brickOptions);
1084
+ break;
1085
+ }
1086
+
1087
+ mesh.material = material;
1088
+ };
1089
+
1090
+ // Clear all objects from the scene (except ground and lights)
1091
+ const clearAllObjects = () => {
1092
+ if (!scene) return;
1093
+
1094
+ scene.meshes.forEach(mesh => {
1095
+ // Don't remove ground, skybox, or camera/lights
1096
+ if (mesh.name &&
1097
+ !mesh.name.startsWith('ground') &&
1098
+ !mesh.name.startsWith('skybox') &&
1099
+ !mesh.name.startsWith('camera') &&
1100
+ !mesh.name.startsWith('hemiLight') &&
1101
+ !mesh.name.startsWith('dirLight')) {
1102
+ mesh.dispose();
1103
+ }
1104
+ });
1105
+
1106
+ hideSelectionInfo();
1107
+ currentSelection = null;
1108
+ };
1109
+
1110
+ // Reset the camera to default position
1111
+ const resetCamera = () => {
1112
+ if (!scene || !scene.activeCamera) return;
1113
+
1114
+ const camera = scene.activeCamera;
1115
+ camera.alpha = -Math.PI / 2;
1116
+ camera.beta = Math.PI / 2;
1117
+ camera.radius = 15;
1118
+ camera.target = BABYLON.Vector3.Zero();
1119
+ };
1120
+
1121
+ // Take a screenshot of the current scene
1122
+ const takeScreenshot = () => {
1123
+ if (!engine) return;
1124
+
1125
+ // Create a temporary canvas
1126
+ const tempCanvas = document.createElement('canvas');
1127
+ const tempContext = tempCanvas.getContext('2d');
1128
+
1129
+ // Set canvas size
1130
+ tempCanvas.width = canvas.width;
1131
+ tempCanvas.height = canvas.height;
1132
+
1133
+ // Copy the contents
1134
+ tempContext.drawImage(canvas, 0, 0);
1135
+
1136
+ // Show the preview
1137
+ dom.previewImage.src = tempCanvas.toDataURL('image/png');
1138
+ dom.screenshotPreview.classList.remove('hidden');
1139
+ };
1140
+
1141
+ // Toggle fullscreen mode
1142
+ const toggleFullscreen = () => {
1143
+ if (!document.fullscreenElement) {
1144
+ canvas.requestFullscreen().catch(err => {
1145
+ console.error(`Error attempting to enable fullscreen: ${err.message}`);
1146
+ });
1147
+ } else {
1148
+ document.exitFullscreen();
1149
+ }
1150
+ };
1151
+
1152
+ // ==============================================
1153
+ // UI Functions
1154
+ // ==============================================
1155
+
1156
+ // Show the selection info panel
1157
+ const showSelectionInfo = (mesh) => {
1158
+ dom.selectedName.textContent = mesh.name;
1159
+ dom.selectedType.textContent = mesh.getClassName();
1160
+ dom.selectedPosition.textContent = formatVector(mesh.position);
1161
+ dom.selectedRotation.textContent = formatVector(mesh.rotation);
1162
+ dom.selectedScaling.textContent = formatVector(mesh.scaling);
1163
+ dom.selectedMaterial.textContent = mesh.material ? mesh.material.getClassName() : 'None';
1164
+
1165
+ dom.selectionInfo.classList.remove('hidden');
1166
+ };
1167
+
1168
+ // Hide the selection info panel
1169
+ const hideSelectionInfo = () => {
1170
+ dom.selectionInfo.classList.add('hidden');
1171
+ };
1172
+
1173
+ // Delete the currently selected object
1174
+ const deleteSelectedObject = () => {
1175
+ if (!currentSelection) return;
1176
+
1177
+ currentSelection.dispose();
1178
+ hideSelectionInfo();
1179
+ currentSelection = null;
1180
+ };
1181
+
1182
+ // Update performance stats
1183
+ const updateStats = () => {
1184
+ // Throttle the updates
1185
+ if (!fpsLabel || !objectCountLabel || !scene) return;
1186
+
1187
+ // Update FPS
1188
+ const fps = Math.round(engine.getFps());
1189
+ fpsLabel.text = `FPS: ${fps}`;
1190
+ dom.miniFps.textContent = fps;
1191
+
1192
+ // Update object count
1193
+ const meshes = scene.meshes.filter(m => !m.name.startsWith('_default_')).length;
1194
+ objectCountLabel.text = `Objects: ${meshes}`;
1195
+ dom.miniObjCount.textContent = meshes;
1196
+
1197
+ // Update draw calls if supported
1198
+ if (engine.getCurrentRenderCalls) {
1199
+ dom.miniDrawCalls.textContent = engine.getCurrentRenderCalls();
1200
+ }
1201
+ };
1202
+
1203
+ // ==============================================
1204
+ // Event Listeners
1205
+ // ==============================================
1206
+
1207
+ // Setup event listeners for UI controls
1208
+ const setupEventListeners = () => {
1209
+ // Panel toggle
1210
+ dom.togglePanel.addEventListener('click', () => {
1211
+ dom.sidePanel.classList.toggle('-ml-64');
1212
+ });
1213
+
1214
+ // Scene management
1215
+ dom.newScene.addEventListener('click', () => {
1216
+ clearAllObjects();
1217
+ resetCamera();
1218
+ });
1219
+
1220
+ dom.saveScene.addEventListener('click', () => {
1221
+ alert('Scene saved (this would actually save the scene in a real application)');
1222
+ });
1223
+
1224
+ // Environment
1225
+ dom.skyboxSelect.addEventListener('change', (e) => {
1226
+ setupSkybox(e.target.value);
1227
+ });
1228
+
1229
+ dom.envIntensity.addEventListener('input', (e) => {
1230
+ scene.environmentIntensity = parseFloat(e.target.value);
1231
+ });
1232
+
1233
+ dom.toggleFog.addEventListener('click', () => {
1234
+ state.environment.fog = !state.environment.fog;
1235
+
1236
+ if (state.environment.fog) {
1237
+ scene.fogMode = BABYLON.Scene.FOGMODE_EXP;
1238
+ scene.fogDensity = 0.02;
1239
+ scene.fogColor = new BABYLON.Color3(0.1, 0.1, 0.15);
1240
+ dom.toggleFog.textContent = 'Disable';
1241
+ } else {
1242
+ scene.fogMode = BABYLON.Scene.FOGMODE_NONE;
1243
+ dom.toggleFog.textContent = 'Enable';
1244
+ }
1245
+ });
1246
+
1247
+ // Lights
1248
+ dom.toggleHemiLight.addEventListener('click', () => {
1249
+ state.lights.hemispheric = !state.lights.hemispheric;
1250
+ hemisphericLight.intensity = state.lights.hemispheric ? state.lights.intensity : 0;
1251
+ dom.toggleHemiLight.textContent = state.lights.hemispheric ? 'Disable' : 'Enable';
1252
+ });
1253
+
1254
+ dom.toggleDirLight.addEventListener('click', () => {
1255
+ state.lights.directional = !state.lights.directional;
1256
+ directionalLight.intensity = state.lights.directional ? state.lights.intensity * 0.8 : 0;
1257
+ dom.toggleDirLight.textContent = state.lights.directional ? 'Disable' : 'Enable';
1258
+ });
1259
+
1260
+ dom.adjustHemiLight.addEventListener('click', () => {
1261
+ hemisphericLight.diffuse = new BABYLON.Color3(
1262
+ Math.random(),
1263
+ Math.random(),
1264
+ Math.random()
1265
+ );
1266
+ hemisphericLight.groundColor = new BABYLON.Color3(
1267
+ Math.random(),
1268
+ Math.random(),
1269
+ Math.random()
1270
+ );
1271
+ });
1272
+
1273
+ dom.adjustDirLight.addEventListener('click', () => {
1274
+ directionalLight.direction = new BABYLON.Vector3(
1275
+ Math.random() * 2 - 1,
1276
+ Math.random() * -1 - 0.5,
1277
+ Math.random() * 2 - 1
1278
+ ).normalize();
1279
+ });
1280
+
1281
+ dom.lightIntensity.addEventListener('input', (e) => {
1282
+ state.lights.intensity = parseFloat(e.target.value);
1283
+ hemisphericLight.intensity = state.lights.intensity;
1284
+ directionalLight.intensity = state.lights.intensity * 0.8;
1285
+ });
1286
+
1287
+ // Objects
1288
+ dom.addSphere.addEventListener('click', () => createPrimitive('sphere'));
1289
+ dom.addBox.addEventListener('click', () => createPrimitive('box'));
1290
+ dom.addCylinder.addEventListener('click', () => createPrimitive('cylinder'));
1291
+ dom.addPlane.addEventListener('click', () => createPrimitive('plane'));
1292
+ dom.clearObjects.addEventListener('click', clearAllObjects);
1293
+
1294
+ // Materials
1295
+ dom.materialType.addEventListener('change', (e) => {
1296
+ state.currentMaterial = e.target.value;
1297
+ if (currentSelection) {
1298
+ applyCurrentMaterialToMesh(currentSelection);
1299
+ dom.selectedMaterial.textContent = currentSelection.material ?
1300
+ currentSelection.material.getClassName() : 'None';
1301
+ }
1302
+ });
1303
+
1304
+ const colorButtons = ['red', 'green', 'blue', 'yellow', 'purple', 'gray'];
1305
+ colorButtons.forEach(color => {
1306
+ document.getElementById(`color${color.charAt(0).toUpperCase() + color.slice(1)}`)
1307
+ .addEventListener('click', () => {
1308
+ state.currentColor = config.materials.colors[color];
1309
+ if (currentSelection) {
1310
+ applyCurrentMaterialToMesh(currentSelection);
1311
+ }
1312
+ });
1313
+ });
1314
+
1315
+ dom.textureScale.addEventListener('input', (e) => {
1316
+ if (currentSelection && currentSelection.material) {
1317
+ const scale = parseFloat(e.target.value);
1318
+
1319
+ if (currentSelection.material instanceof BABYLON.GradientMaterial) {
1320
+ currentSelection.material.scale = scale;
1321
+ } else if (currentSelection.material instanceof BABYLON.GridMaterial) {
1322
+ currentSelection.material.scale = scale;
1323
+ }
1324
+ }
1325
+ });
1326
+
1327
+ // Effects
1328
+ dom.toggleBloom.addEventListener('click', () => {
1329
+ state.postProcessing.bloom = !state.postProcessing.bloom;
1330
+ bloomEffect.enabled = state.postProcessing.bloom;
1331
+ dom.toggleBloom.textContent = state.postProcessing.bloom ? 'Disable' : 'Enable';
1332
+ });
1333
+
1334
+ dom.toggleDOF.addEventListener('click', () => {
1335
+ state.postProcessing.depthOfField = !state.postProcessing.depthOfField;
1336
+ depthOfFieldEffect.enabled = state.postProcessing.depthOfField;
1337
+ dom.toggleDOF.textContent = state.postProcessing.depthOfField ? 'Disable' : 'Enable';
1338
+ });
1339
+
1340
+ dom.toggleGrain.addEventListener('click', () => {
1341
+ state.postProcessing.grain = !state.postProcessing.grain;
1342
+ grainEffect.enabled = state.postProcessing.grain;
1343
+ dom.toggleGrain.textContent = state.postProcessing.grain ? 'Disable' : 'Enable';
1344
+ });
1345
+
1346
+ dom.toggleSSAO.addEventListener('click', () => {
1347
+ state.postProcessing.ssao = !state.postProcessing.ssao;
1348
+ ssaoEffect.enabled = state.postProcessing.ssao;
1349
+ dom.toggleSSAO.textContent = state.postProcessing.ssao ? 'Disable' : 'Enable';
1350
+ });
1351
+
1352
+ // Viewport controls
1353
+ dom.resetCamera.addEventListener('click', resetCamera);
1354
+ dom.screenshot.addEventListener('click', takeScreenshot);
1355
+ dom.toggleFullscreen.addEventListener('click', toggleFullscreen);
1356
+
1357
+ // Selection info
1358
+ dom.closeSelection.addEventListener('click', hideSelectionInfo);
1359
+ dom.deleteSelected.addEventListener('click', deleteSelectedObject);
1360
+
1361
+ // Modal controls
1362
+ dom.continueButton.addEventListener('click', () => {
1363
+ dom.webgpuModal.classList.add('hidden');
1364
+ });
1365
+
1366
+ dom.learnButton.addEventListener('click', () => {
1367
+ window.open('https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API', '_blank');
1368
+ });
1369
+
1370
+ dom.closePreview.addEventListener('click', () => {
1371
+ dom.screenshotPreview.classList.add('hidden');
1372
+ });
1373
+
1374
+ dom.downloadScreenshot.addEventListener('click', () => {
1375
+ const link = document.createElement('a');
1376
+ link.download = `screenshot-${new Date().toISOString()}.png`;
1377
+ link.href = dom.previewImage.src;
1378
+ link.click();
1379
+ });
1380
+
1381
+ dom.copyScreenshot.addEventListener('click', () => {
1382
+ navigator.clipboard.writeText(dom.previewImage.src).then(() => {
1383
+ alert('Screenshot copied to clipboard!');
1384
+ }).catch(err => {
1385
+ console.error('Could not copy screenshot: ', err);
1386
+ alert('Failed to copy screenshot to clipboard');
1387
+ });
1388
+ });
1389
+ };
1390
+
1391
+ // ==============================================
1392
+ // Public API
1393
+ // ==============================================
1394
+
1395
+ return {
1396
+ init: () => initScene(),
1397
+ getScene: () => scene,
1398
+ getEngine: () => engine
1399
+ };
1400
+ })();
1401
+
1402
+ // Initialize the application when DOM is loaded
1403
+ document.addEventListener('DOMContentLoaded', () => {
1404
+ BabylonApp.init();
1405
+ });
1406
+ </script>
1407
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
1408
+ </html>