diff --git a/index.html b/index.html index 460cd6a..ba8849f 100644 --- a/index.html +++ b/index.html @@ -57,6 +57,10 @@
+ +
+ Create New Level diff --git a/public/styles.css b/public/styles.css index 6d242f9..9d770ef 100644 --- a/public/styles.css +++ b/public/styles.css @@ -31,9 +31,7 @@ body { #mainDiv { position: absolute; display: block; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + top: 48px; z-index: 1000; overflow: scroll; } @@ -185,6 +183,26 @@ body { box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); } +.test-level-button { + background: linear-gradient(135deg, #FF6B6B 0%, #C92A2A 100%); + color: white; + border: 2px solid rgba(255, 107, 107, 0.5); + padding: 12px 30px; + border-radius: 8px; + cursor: pointer; + font-size: 1.1em; + font-weight: bold; + transition: all 0.3s; + margin-bottom: 15px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +.test-level-button:hover { + transform: scale(1.05); + box-shadow: 0 6px 20px rgba(255, 107, 107, 0.6); + border-color: rgba(255, 107, 107, 0.8); +} + .editor-link, .settings-link { position: absolute; diff --git a/src/explosionManager.ts b/src/explosionManager.ts index 4720929..ae31ff2 100644 --- a/src/explosionManager.ts +++ b/src/explosionManager.ts @@ -55,7 +55,12 @@ export class ExplosionManager { * @returns Array of sphere mesh objects */ private splitIntoSeparateMeshes(mesh: Mesh, pieces: number = 32): Mesh[] { - console.log(`Creating ${pieces} sphere debris pieces`); + console.log(`[ExplosionManager] Creating ${pieces} sphere debris pieces`); + console.log('[ExplosionManager] Base mesh info:', { + position: mesh.position.toString(), + scaling: mesh.scaling.toString(), + hasMaterial: !!mesh.material + }); const meshPieces: Mesh[] = []; const basePosition = mesh.position.clone(); @@ -65,41 +70,62 @@ export class ExplosionManager { const material = mesh.material?.clone('debris-material'); if (material) { //(material as any).emissiveColor = Color3.Yellow(); + console.log('[ExplosionManager] Material cloned successfully'); + } else { + console.warn('[ExplosionManager] WARNING: No material on base mesh'); } // Create sphere debris scattered around the original mesh position const avgScale = (baseScale.x + baseScale.y + baseScale.z) / 3; const debrisSize = avgScale * 0.3; // Size relative to asteroid + console.log('[ExplosionManager] Debris parameters:', { + avgScale, + debrisSize, + offsetRadius: avgScale * 0.5 + }); + for (let i = 0; i < pieces; i++) { - // Create a small sphere for debris - const sphere = MeshBuilder.CreateIcoSphere( - `${mesh.name}_debris_${i}`, - { - radius: debrisSize, - subdivisions: 2 - }, DefaultScene.MainScene - ); + try { + // Create a small sphere for debris + const sphere = MeshBuilder.CreateIcoSphere( + `${mesh.name}_debris_${i}`, + { + radius: debrisSize, + subdivisions: 2 + }, DefaultScene.MainScene + ); - // Position spheres in a small cluster around the original position - const offsetRadius = avgScale * 0.5; - const angle1 = (i / pieces) * Math.PI * 2; - const angle2 = Math.random() * Math.PI; + // Position spheres in a small cluster around the original position + const offsetRadius = avgScale * 0.5; + const angle1 = (i / pieces) * Math.PI * 2; + const angle2 = Math.random() * Math.PI; - sphere.position = new Vector3( - basePosition.x + Math.sin(angle2) * Math.cos(angle1) * offsetRadius, - basePosition.y + Math.sin(angle2) * Math.sin(angle1) * offsetRadius, - basePosition.z + Math.cos(angle2) * offsetRadius - ); + sphere.position = new Vector3( + basePosition.x + Math.sin(angle2) * Math.cos(angle1) * offsetRadius, + basePosition.y + Math.sin(angle2) * Math.sin(angle1) * offsetRadius, + basePosition.z + Math.cos(angle2) * offsetRadius + ); - sphere.material = material; - sphere.isVisible = true; - sphere.setEnabled(true); + sphere.material = material; + sphere.isVisible = true; + sphere.setEnabled(true); - meshPieces.push(sphere); + meshPieces.push(sphere); + } catch (error) { + console.error(`[ExplosionManager] ERROR creating debris piece ${i}:`, error); + } } - console.log(`Created ${meshPieces.length} sphere debris pieces`); + console.log(`[ExplosionManager] Successfully created ${meshPieces.length}/${pieces} sphere debris pieces`); + if (meshPieces.length > 0) { + console.log('[ExplosionManager] First piece sample:', { + name: meshPieces[0].name, + position: meshPieces[0].position.toString(), + isVisible: meshPieces[0].isVisible, + isEnabled: meshPieces[0].isEnabled() + }); + } return meshPieces; } @@ -108,20 +134,33 @@ export class ExplosionManager { * @param mesh The mesh to explode (will be cloned internally) */ public playExplosion(mesh: AbstractMesh): void { + console.log('[ExplosionManager] playExplosion called'); + console.log('[ExplosionManager] Input mesh:', { + name: mesh.name, + id: mesh.id, + isInstancedMesh: !!(mesh as any).sourceMesh, + position: mesh.position.toString(), + scaling: mesh.scaling.toString() + }); + // Get the source mesh if this is an instanced mesh let sourceMesh: Mesh; if ((mesh as any).sourceMesh) { sourceMesh = (mesh as any).sourceMesh as Mesh; + console.log('[ExplosionManager] Using source mesh from instance:', sourceMesh.name); } else { sourceMesh = mesh as Mesh; + console.log('[ExplosionManager] Using mesh directly (not instanced)'); } // Clone the source mesh so we don't affect the original + console.log('[ExplosionManager] Cloning mesh...'); const meshToExplode = sourceMesh.clone("exploding-" + mesh.name, null, true, false); if (!meshToExplode) { - console.warn("Failed to clone mesh for explosion"); + console.error('[ExplosionManager] ERROR: Failed to clone mesh for explosion'); return; } + console.log('[ExplosionManager] Mesh cloned successfully'); // Apply the instance's transformation to the cloned mesh meshToExplode.position = mesh.getAbsolutePosition().clone(); @@ -134,77 +173,130 @@ export class ExplosionManager { // Check if mesh has proper geometry if (!meshToExplode.getTotalVertices || meshToExplode.getTotalVertices() === 0) { - console.warn("Mesh has no vertices, cannot explode"); + console.error('[ExplosionManager] ERROR: Mesh has no vertices, cannot explode'); meshToExplode.dispose(); return; } - console.log(`Exploding mesh: ${meshToExplode.name}, vertices: ${meshToExplode.getTotalVertices()}`); + console.log(`[ExplosionManager] Mesh ready for explosion:`, { + name: meshToExplode.name, + vertices: meshToExplode.getTotalVertices(), + position: meshToExplode.position.toString(), + scaling: meshToExplode.scaling.toString() + }); // Split the mesh into separate mesh objects (MeshExploder requirement) + console.log('[ExplosionManager] Splitting mesh into pieces...'); const meshPieces = this.splitIntoSeparateMeshes(meshToExplode, 12); if (meshPieces.length === 0) { - console.warn("Failed to split mesh into pieces"); + console.error('[ExplosionManager] ERROR: Failed to split mesh into pieces'); meshToExplode.dispose(); return; } // Original mesh is no longer needed - the pieces replace it + console.log('[ExplosionManager] Disposing original cloned mesh'); meshToExplode.dispose(); // Create the exploder with the array of separate meshes // The second parameter is optional - it's the center mesh to explode from // If not provided, MeshExploder will auto-calculate the center - const exploder = new MeshExploder(meshPieces); + console.log('[ExplosionManager] Creating MeshExploder...'); + try { + const exploder = new MeshExploder(meshPieces); + console.log('[ExplosionManager] MeshExploder created successfully'); - console.log(`Starting explosion animation for ${meshPieces.length} mesh pieces`); - - // Animate the explosion by calling explode() each frame with increasing values - const startTime = Date.now(); - const animationDuration = this.config.duration; - const maxForce = this.config.explosionForce; - - const animate = () => { - const elapsed = Date.now() - startTime; - const progress = Math.min(elapsed / animationDuration, 1.0); - - // Calculate current explosion value (0 to maxForce) - const currentValue = progress * maxForce; - exploder.explode(currentValue); - - // Animate debris size to zero (1.0 to 0.0) - const scale = 1.0 - progress; - meshPieces.forEach(piece => { - piece.scaling.set(scale, scale, scale); + console.log(`[ExplosionManager] Starting explosion animation:`, { + pieceCount: meshPieces.length, + duration: this.config.duration, + maxForce: this.config.explosionForce }); - // Continue animation if not complete - if (progress < 1.0) { - requestAnimationFrame(animate); - } else { - // Animation complete - clean up - console.log(`Explosion animation complete, cleaning up`); - this.cleanupExplosion(meshPieces); - } - }; + // Animate the explosion by calling explode() each frame with increasing values + const startTime = Date.now(); + const animationDuration = this.config.duration; + const maxForce = this.config.explosionForce; + let frameCount = 0; - // Start the animation - animate(); + const animate = () => { + const elapsed = Date.now() - startTime; + const progress = Math.min(elapsed / animationDuration, 1.0); + + // Calculate current explosion value (0 to maxForce) + const currentValue = progress * maxForce; + + try { + exploder.explode(currentValue); + } catch (error) { + console.error('[ExplosionManager] ERROR in explode():', error); + } + + // Animate debris size to zero (1.0 to 0.0) + const scale = 1.0 - progress; + meshPieces.forEach(piece => { + if (piece && !piece.isDisposed()) { + piece.scaling.set(scale, scale, scale); + } + }); + + frameCount++; + + // Log every 15 frames (approximately every 250ms at 60fps) + if (frameCount % 15 === 0 || frameCount === 1) { + console.log(`[ExplosionManager] Animation frame ${frameCount}:`, { + elapsed: `${elapsed}ms`, + progress: progress.toFixed(3), + currentValue: currentValue.toFixed(2), + scale: scale.toFixed(3), + piecesAlive: meshPieces.filter(p => !p.isDisposed()).length + }); + } + + // Continue animation if not complete + if (progress < 1.0) { + requestAnimationFrame(animate); + } else { + // Animation complete - clean up + console.log(`[ExplosionManager] Animation complete after ${frameCount} frames, cleaning up`); + this.cleanupExplosion(meshPieces); + } + }; + + // Start the animation + console.log('[ExplosionManager] Starting animation loop...'); + animate(); + } catch (error) { + console.error('[ExplosionManager] ERROR creating MeshExploder:', error); + // Clean up pieces if exploder failed + meshPieces.forEach(piece => { + if (piece && !piece.isDisposed()) { + piece.dispose(); + } + }); + } } /** * Clean up explosion meshes */ private cleanupExplosion(meshPieces: Mesh[]): void { + console.log('[ExplosionManager] Starting cleanup of explosion meshes...'); + + let disposedCount = 0; // Dispose all the mesh pieces - meshPieces.forEach(mesh => { + meshPieces.forEach((mesh, index) => { if (mesh && !mesh.isDisposed()) { - mesh.dispose(); + try { + mesh.dispose(); + disposedCount++; + } catch (error) { + console.error(`[ExplosionManager] ERROR disposing piece ${index}:`, error); + } } }); - console.log(`Explosion cleaned up - disposed ${meshPieces.length} pieces`); + console.log(`[ExplosionManager] Cleanup complete - disposed ${disposedCount}/${meshPieces.length} pieces`); } /** diff --git a/src/main.ts b/src/main.ts index 853e446..d884192 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,28 +1,31 @@ -import {AudioEngineV2, DirectionalLight} from "@babylonjs/core"; import { + AudioEngineV2, Color3, CreateAudioEngineAsync, + DirectionalLight, Engine, HavokPlugin, ParticleHelper, Scene, + ScenePerformancePriority, Vector3, WebGPUEngine, WebXRDefaultExperience, - WebXRFeatureName + WebXRFeatureName, WebXRFeaturesManager } from "@babylonjs/core"; import '@babylonjs/loaders'; import HavokPhysics from "@babylonjs/havok"; import {DefaultScene} from "./defaultScene"; import {Level1} from "./level1"; +import {TestLevel} from "./testLevel"; import Demo from "./demo"; import Level from "./level"; import setLoadingMessage from "./setLoadingMessage"; import {RockFactory} from "./rockFactory"; import {ControllerDebug} from "./controllerDebug"; import {router, showView} from "./router"; -import {populateLevelSelector, hasSavedLevels} from "./levelSelector"; +import {hasSavedLevels, populateLevelSelector} from "./levelSelector"; import {LevelConfig} from "./levelConfig"; import {generateDefaultLevels} from "./levelEditor"; @@ -79,11 +82,85 @@ export class Main { }, 500); }); }); + + // Listen for test level button click + window.addEventListener('DOMContentLoaded', () => { + console.log('[Main] DOMContentLoaded fired, looking for test button...'); + const testLevelBtn = document.querySelector('#testLevelBtn'); + console.log('[Main] Test button found:', !!testLevelBtn); + + if (testLevelBtn) { + testLevelBtn.addEventListener('click', async () => { + console.log('[Main] ========== TEST LEVEL BUTTON CLICKED =========='); + + // Show loading UI + const mainDiv = document.querySelector('#mainDiv'); + const levelSelect = document.querySelector('#levelSelect') as HTMLElement; + console.log('[Main] mainDiv exists:', !!mainDiv); + console.log('[Main] levelSelect exists:', !!levelSelect); + + if (levelSelect) { + levelSelect.style.display = 'none'; + console.log('[Main] levelSelect hidden'); + } + setLoadingMessage("Initializing Test Scene..."); + + // Unlock audio engine on user interaction + if (this._audioEngine) { + console.log('[Main] Unlocking audio engine...'); + await this._audioEngine.unlockAsync(); + console.log('[Main] Audio engine unlocked'); + } + + // Create test level + console.log('[Main] Creating TestLevel...'); + this._currentLevel = new TestLevel(this._audioEngine); + console.log('[Main] TestLevel created:', !!this._currentLevel); + + // Wait for level to be ready + console.log('[Main] Registering ready observable...'); + this._currentLevel.getReadyObservable().add(() => { + console.log('[Main] ========== TEST LEVEL READY OBSERVABLE FIRED =========='); + setLoadingMessage("Test Scene Ready! Entering VR..."); + console.log('[Main] Setting timeout to enter VR...'); + + // Small delay to show message + setTimeout(() => { + console.log('[Main] Timeout fired, removing mainDiv and calling play()'); + if (mainDiv) { + mainDiv.remove(); + console.log('[Main] mainDiv removed'); + } + console.log('[Main] About to call this.play()...'); + this.play(); + }, 500); + }); + console.log('[Main] Ready observable registered'); + + // Now initialize the level (after observable is registered) + console.log('[Main] Calling TestLevel.initialize()...'); + await this._currentLevel.initialize(); + console.log('[Main] TestLevel.initialize() completed'); + }); + console.log('[Main] Click listener added to test button'); + } else { + console.warn('[Main] Test level button not found in DOM'); + } + }); } private _started = false; public async play() { + console.log('[Main] play() called'); + console.log('[Main] Current level exists:', !!this._currentLevel); this._gameState = GameState.PLAY; - await this._currentLevel.play(); + + if (this._currentLevel) { + console.log('[Main] Calling level.play()...'); + await this._currentLevel.play(); + console.log('[Main] level.play() completed'); + } else { + console.error('[Main] ERROR: No current level to play!'); + } } public demo() { this._gameState = GameState.DEMO; @@ -97,11 +174,11 @@ export class Main { disableTeleportation: true, disableNearInteraction: true, disableHandTracking: true, - disableDefaultUI: true, + disableDefaultUI: true }); - DefaultScene.XR.baseExperience.featuresManager.enableFeature(WebXRFeatureName.LAYERS, "stable", - {preferMultiviewOnInit: true}); + console.log(WebXRFeaturesManager.GetAvailableFeatures()); + //DefaultScene.XR.baseExperience.featuresManager.enableFeature(WebXRFeatureName.LAYERS, "latest", {preferMultiviewOnInit: true}); setLoadingMessage("Get Ready!"); @@ -124,16 +201,20 @@ export class Main { if (webGpu) { this._engine = new WebGPUEngine(canvas); + console.log("Webgpu enabled"); await (this._engine as WebGPUEngine).initAsync(); } else { + console.log("Standard WebGL enabled"); this._engine = new Engine(canvas, true); } + this._engine.setHardwareScalingLevel(1 / window.devicePixelRatio); window.onresize = () => { this._engine.resize(); } DefaultScene.DemoScene = new Scene(this._engine); DefaultScene.MainScene = new Scene(this._engine); + DefaultScene.MainScene.ambientColor = new Color3(0,0,0); DefaultScene.MainScene.clearColor = new Color3(0, 0, 0).toColor4(); @@ -175,7 +256,7 @@ export class Main { //DefaultScene.MainScene.ambientColor = new Color3(.1, .1, .1); const light = new DirectionalLight("dirLight", new Vector3(-1, -2, -1), DefaultScene.MainScene); DefaultScene.MainScene.enablePhysics(new Vector3(0, 0, 0), havokPlugin); - DefaultScene.MainScene.getPhysicsEngine().setTimeStep(1/30); + DefaultScene.MainScene.getPhysicsEngine().setTimeStep(1/60); DefaultScene.MainScene.getPhysicsEngine().setSubTimeStep(5); DefaultScene.MainScene.collisionsEnabled = true; diff --git a/src/materialFactory.ts b/src/materialFactory.ts index 58d01e6..a9197d5 100644 --- a/src/materialFactory.ts +++ b/src/materialFactory.ts @@ -154,6 +154,7 @@ export class MaterialFactory { material.disableLighting = true; material.roughness = 1; material.specularColor = Color3.Black(); + material.freeze(); return material; } diff --git a/src/rockFactory.ts b/src/rockFactory.ts index 393bbc1..1874b67 100644 --- a/src/rockFactory.ts +++ b/src/rockFactory.ts @@ -77,6 +77,7 @@ export class RockFactory { DefaultScene.MainScene, this._originalMaterial ) as PBRMaterial; + this._rockMaterial.freeze(); this._rockMesh.material = this._rockMaterial; importMesh.meshes[1].dispose(false, true); @@ -119,22 +120,37 @@ export class RockFactory { body.setCollisionCallbackEnabled(true); body.getCollisionObservable().add((eventData) => { if (eventData.type == 'COLLISION_STARTED') { + console.log('[RockFactory] Collision detected:', { + collidedWith: eventData.collidedAgainst.transformNode.id, + asteroidName: eventData.collider.transformNode.name + }); + if ( eventData.collidedAgainst.transformNode.id == 'ammo') { + console.log('[RockFactory] ASTEROID HIT! Triggering explosion...'); score.notifyObservers({score: 1, remaining: -1, message: "Asteroid Destroyed"}); // Get the asteroid mesh before disposing const asteroidMesh = eventData.collider.transformNode as AbstractMesh; + console.log('[RockFactory] Asteroid mesh to explode:', { + name: asteroidMesh.name, + id: asteroidMesh.id, + position: asteroidMesh.position.toString() + }); // Play explosion using ExplosionManager (clones mesh internally) + console.log('[RockFactory] Calling ExplosionManager.playExplosion()...'); RockFactory._explosionManager.playExplosion(asteroidMesh); + console.log('[RockFactory] Explosion call completed'); // Now dispose the physics objects and original mesh + console.log('[RockFactory] Disposing physics objects and meshes...'); eventData.collider.shape.dispose(); eventData.collider.transformNode.dispose(); eventData.collider.dispose(); eventData.collidedAgainst.shape.dispose(); eventData.collidedAgainst.transformNode.dispose(); eventData.collidedAgainst.dispose(); + console.log('[RockFactory] Disposal complete'); } } }); diff --git a/src/ship.ts b/src/ship.ts index 6728ce6..2d9021a 100644 --- a/src/ship.ts +++ b/src/ship.ts @@ -343,10 +343,7 @@ export class Ship { private controllerCallback = (controllerEvent: ControllerEvent) => { // Log first few events to verify they're firing - if (Math.random() < 0.01) { // Only log 1% to avoid spam - console.log('Controller event:', controllerEvent.type, controllerEvent.hand, - controllerEvent.type === 'thumbstick' ? controllerEvent.axisData : controllerEvent.value); - } + if (controllerEvent.type == 'thumbstick') { if (controllerEvent.hand == 'left') { diff --git a/src/testLevel.ts b/src/testLevel.ts new file mode 100644 index 0000000..1d650a5 --- /dev/null +++ b/src/testLevel.ts @@ -0,0 +1,232 @@ +import { DefaultScene } from "./defaultScene"; +import { + Color3, + DirectionalLight, + MeshBuilder, + Observable, + StandardMaterial, + Vector3 +} from "@babylonjs/core"; +import type { AudioEngineV2 } from "@babylonjs/core"; +import Level from "./level"; + +/** + * Minimal test level with just a box and a light for debugging + */ +export class TestLevel implements Level { + private _onReadyObservable: Observable = new Observable(); + private _initialized: boolean = false; + private _audioEngine: AudioEngineV2; + private _boxCreationInterval: NodeJS.Timeout | null = null; + private _totalBoxesCreated: number = 0; + private _boxesPerIteration: number = 1; + + constructor(audioEngine: AudioEngineV2) { + this._audioEngine = audioEngine; + console.log('[TestLevel] Constructor called'); + // Don't call initialize here - let Main call it after registering the observable + } + + getReadyObservable(): Observable { + return this._onReadyObservable; + } + + public async play() { + console.log('[TestLevel] play() called - entering XR'); + console.log('[TestLevel] XR available:', !!DefaultScene.XR); + console.log('[TestLevel] XR baseExperience:', !!DefaultScene.XR?.baseExperience); + + try { + // Enter XR mode + const xr = await DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor'); + console.log('[TestLevel] XR mode entered successfully'); + console.log('[TestLevel] XR session:', xr); + console.log('[TestLevel] Camera position:', DefaultScene.XR.baseExperience.camera.position.toString()); + this.startBoxCreation(); + } catch (error) { + console.error('[TestLevel] ERROR entering XR:', error); + } + } + + public dispose() { + console.log('[TestLevel] dispose() called'); + + // Stop box creation timer + if (this._boxCreationInterval) { + clearInterval(this._boxCreationInterval); + this._boxCreationInterval = null; + console.log('[TestLevel] Box creation timer stopped'); + } + } + + /** + * Create a box at the specified position with the specified color + */ + private createBox(position: Vector3, color: Color3, name?: string): void { + const box = MeshBuilder.CreateBox( + name || `box_${this._totalBoxesCreated}`, + { size: 0.5 }, + DefaultScene.MainScene + ); + box.position = position; + + const material = new StandardMaterial(`material_${this._totalBoxesCreated}`, DefaultScene.MainScene); + material.diffuseColor = color; + material.specularColor = new Color3(0.5, 0.5, 0.5); + box.material = material; + + this._totalBoxesCreated++; + } + + /** + * Start the box creation timer that doubles the number of boxes each iteration + */ + private startBoxCreation(): void { + console.log('[TestLevel] Starting box creation timer...'); + + const createBatch = () => { + const boxesToCreate = Math.min( + this._boxesPerIteration, + 1000 - this._totalBoxesCreated + ); + + console.log(`[TestLevel] Creating ${boxesToCreate} boxes (total will be: ${this._totalBoxesCreated + boxesToCreate}/1000)`); + + for (let i = 0; i < boxesToCreate; i++) { + // Random position in a 20x20x20 cube around origin + const position = new Vector3( + Math.random() * 20 - 10, + Math.random() * 20, + Math.random() * 20 - 10 + ); + + // Random color + const color = new Color3( + Math.random(), + Math.random(), + Math.random() + ); + + this.createBox(position, color); + } + + console.log(`[TestLevel] Created ${boxesToCreate} boxes. Total: ${this._totalBoxesCreated}/1000`); + + // Log performance metrics + const fps = DefaultScene.MainScene.getEngine().getFps(); + + // Directly compute triangle count from all meshes + const totalIndices = DefaultScene.MainScene.meshes.reduce((sum, mesh) => { + if (mesh.isEnabled() && mesh.isVisible) { + return sum + mesh.getTotalIndices(); + } + return sum; + }, 0); + const triangleCount = Math.floor(totalIndices / 3); + + console.log(`[TestLevel] Performance Metrics:`, { + fps: fps.toFixed(2), + triangleCount: triangleCount, + totalIndices: totalIndices, + totalMeshes: DefaultScene.MainScene.meshes.length, + activeMeshes: DefaultScene.MainScene.meshes.filter(m => m.isEnabled() && m.isVisible).length, + totalBoxes: this._totalBoxesCreated + }); + + // Check if we've reached 1000 boxes + if (this._totalBoxesCreated >= 1000) { + console.log('[TestLevel] Reached 1000 boxes, stopping timer'); + if (this._boxCreationInterval) { + clearInterval(this._boxCreationInterval); + this._boxCreationInterval = null; + } + return; + } + + // Double the number for next iteration + this._boxesPerIteration *= 2; + }; + + // Create first batch immediately + createBatch(); + + // Set up interval for subsequent batches + this._boxCreationInterval = setInterval(createBatch, 5000); + } + + public async initialize() { + console.log('[TestLevel] initialize() called'); + console.log('[TestLevel] Scene info:', { + name: DefaultScene.MainScene.name, + meshCount: DefaultScene.MainScene.meshes.length, + lightCount: DefaultScene.MainScene.lights.length + }); + + if (this._initialized) { + console.log('[TestLevel] Already initialized, skipping'); + return; + } + + // Create a simple directional light + const light = new DirectionalLight( + "testLight", + new Vector3(-1, -2, 1), + DefaultScene.MainScene + ); + light.intensity = 1.0; + console.log('[TestLevel] Created directional light:', { + name: light.name, + direction: light.direction.toString(), + intensity: light.intensity + }); + + // Create a simple colored box + const box = MeshBuilder.CreateBox( + "testBox", + { size: 2 }, + DefaultScene.MainScene + ); + box.position = new Vector3(0, 1, 5); // In front of camera + + // Create a simple material + const material = new StandardMaterial("testMaterial", DefaultScene.MainScene); + material.diffuseColor = new Color3(1, 0, 0); // Red + material.specularColor = new Color3(0.5, 0.5, 0.5); + box.material = material; + console.log('[TestLevel] Created test box:', { + name: box.name, + position: box.position.toString(), + size: 2, + color: 'red' + }); + + // Create a ground plane for reference + const ground = MeshBuilder.CreateGround( + "testGround", + { width: 10, height: 10 }, + DefaultScene.MainScene + ); + ground.position.y = 0; + + const groundMaterial = new StandardMaterial("groundMaterial", DefaultScene.MainScene); + groundMaterial.diffuseColor = new Color3(0.3, 0.3, 0.3); // Grey + ground.material = groundMaterial; + console.log('[TestLevel] Created ground plane:', { + name: ground.name, + dimensions: '10x10', + position: ground.position.toString() + }); + + console.log('[TestLevel] Final scene state:', { + totalMeshes: DefaultScene.MainScene.meshes.length, + totalLights: DefaultScene.MainScene.lights.length, + meshNames: DefaultScene.MainScene.meshes.map(m => m.name) + }); + + this._initialized = true; + console.log('[TestLevel] Initialization complete - scene ready for XR'); + + // Notify that initialization is complete + this._onReadyObservable.notifyObservers(this); + } +}