package main import ( "encoding/json" "fmt" "html/template" "log" "net/http" "sync" "time" "github.com/gorilla/websocket" ) var currentParams = TorusParams{ MajorRadius: 2.0, MinorRadius: 0.5, MajorSegments: 50, MinorSegments: 30, RotationSpeedX: 0.3, RotationSpeedY: 0.5, } var currentAsteroidParams = AsteroidParams{ SpawnRate: 1.0, MaxAsteroids: 500, MinSize: 0.05, MaxSize: 0.3, MinSpeed: 0.5, MaxSpeed: 3.0, RotationSpeed: 1.0, } var currentScene = "donut" // "donut" or "asteroids" // Global CUDA status for WebSocket broadcasting var globalCudaEnabled *bool // Status WebSocket broadcaster type StatusBroadcaster struct { clients map[*websocket.Conn]bool register chan *websocket.Conn unregister chan *websocket.Conn broadcast chan StatusData mutex sync.RWMutex } type StatusData struct { CudaEnabled bool `json:"cudaEnabled"` Params TorusParams `json:"params"` AsteroidParams AsteroidParams `json:"asteroidParams"` CurrentScene string `json:"currentScene"` FPS float64 `json:"fps"` WindowWidth int `json:"windowWidth"` WindowHeight int `json:"windowHeight"` } var statusBroadcaster *StatusBroadcaster var statusUpgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { origin := r.Header.Get("Origin") return origin == "" || origin == "http://localhost:8080" || origin == "http://127.0.0.1:8080" }, } func newStatusBroadcaster() *StatusBroadcaster { return &StatusBroadcaster{ clients: make(map[*websocket.Conn]bool), register: make(chan *websocket.Conn), unregister: make(chan *websocket.Conn), broadcast: make(chan StatusData), } } func (sb *StatusBroadcaster) run() { ticker := time.NewTicker(100 * time.Millisecond) // Update 10 times per second defer ticker.Stop() for { select { case client := <-sb.register: sb.mutex.Lock() sb.clients[client] = true sb.mutex.Unlock() LogInfo("StatusWS", "New client connected to status stream") // Send initial status to new client sb.sendStatusToClient(client) case client := <-sb.unregister: sb.mutex.Lock() if _, ok := sb.clients[client]; ok { delete(sb.clients, client) client.Close() } sb.mutex.Unlock() LogInfo("StatusWS", "Client disconnected from status stream") case <-ticker.C: // Broadcast status updates to all clients sb.broadcastStatus() } } } func (sb *StatusBroadcaster) sendStatusToClient(client *websocket.Conn) { status := StatusData{ CudaEnabled: globalCudaEnabled != nil && *globalCudaEnabled, Params: currentParams, AsteroidParams: currentAsteroidParams, CurrentScene: currentScene, FPS: GetCurrentFPS(), WindowWidth: func() int { w, _ := GetWindowSize(); return w }(), WindowHeight: func() int { _, h := GetWindowSize(); return h }(), } if err := client.WriteJSON(status); err != nil { sb.unregister <- client } } func (sb *StatusBroadcaster) broadcastStatus() { status := StatusData{ CudaEnabled: globalCudaEnabled != nil && *globalCudaEnabled, Params: currentParams, AsteroidParams: currentAsteroidParams, CurrentScene: currentScene, FPS: GetCurrentFPS(), WindowWidth: func() int { w, _ := GetWindowSize(); return w }(), WindowHeight: func() int { _, h := GetWindowSize(); return h }(), } sb.mutex.RLock() defer sb.mutex.RUnlock() for client := range sb.clients { if err := client.WriteJSON(status); err != nil { go func(c *websocket.Conn) { sb.unregister <- c }(client) } } } const htmlTemplate = ` CUDA-Accelerated 3D Visualization Control Panel

🎮 3D Visualization Control Panel

🍩 Torus Parameters

🌌 Asteroid Field Parameters

Info: This control panel adjusts the 3D visualization in real-time. Switch between the torus scene and asteroid field using the buttons above or press 1/2 in the 3D viewport.

Current Mode: {{if .CudaEnabled}}CUDA Accelerated{{else}}CPU Fallback{{end}}
Current Scene: {{.CurrentScene}}
Performance: -- FPS
Viewport: 800 x 600 (resizable)

📋 System Logs

Connecting...
[Starting] WebUI Control panel loaded, connecting to log stream...
` // StartWebServer starts the web interface server func StartWebServer(params *TorusParams, cudaEnabled *bool) { // Set global CUDA status for WebSocket broadcasting globalCudaEnabled = cudaEnabled // Initialize and start status broadcaster statusBroadcaster = newStatusBroadcaster() go statusBroadcaster.run() // Serve static files for the web interface http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { tmpl := template.Must(template.New("index").Parse(htmlTemplate)) data := struct { TorusParams AsteroidParams CudaEnabled bool CurrentScene string }{ TorusParams: currentParams, AsteroidParams: currentAsteroidParams, CudaEnabled: cudaEnabled != nil && *cudaEnabled, CurrentScene: currentScene, } tmpl.Execute(w, data) }) // WebSocket endpoint for real-time logging http.HandleFunc("/ws", wsLogHandler) // WebSocket endpoint for real-time status updates http.HandleFunc("/ws-status", func(w http.ResponseWriter, r *http.Request) { conn, err := statusUpgrader.Upgrade(w, r, nil) if err != nil { log.Println("Status WebSocket upgrade error:", err) return } statusBroadcaster.register <- conn // Keep connection alive and handle disconnection defer func() { statusBroadcaster.unregister <- conn conn.Close() }() // Read messages from client (ping/pong) for { _, _, err := conn.ReadMessage() if err != nil { break } } }) // API endpoint for scene switching http.HandleFunc("/api/scene", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } r.Body = http.MaxBytesReader(w, r.Body, 1048576) // 1MB limit var sceneData map[string]string if err := json.NewDecoder(r.Body).Decode(&sceneData); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } if scene, ok := sceneData["scene"]; ok { currentScene = scene // Update the actual scene in main.go SetCurrentScene(scene) LogSuccess("Scene", fmt.Sprintf("Scene switched to: %s", scene)) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "scene changed"}) }) // API endpoint to update torus parameters http.HandleFunc("/api/params", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } r.Body = http.MaxBytesReader(w, r.Body, 1048576) // 1MB limit var updates map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&updates); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // Log parameter changes for key, value := range updates { LogInfo("UI", fmt.Sprintf("Torus parameter changed: %s = %v", key, value)) } // Update parameters for key, value := range updates { switch key { case "majorRadius": if v, ok := value.(float64); ok { oldValue := currentParams.MajorRadius currentParams.MajorRadius = float32(v) LogSuccess("Parameters", fmt.Sprintf("Major Radius: %.2f → %.2f", oldValue, float32(v))) } case "minorRadius": if v, ok := value.(float64); ok { oldValue := currentParams.MinorRadius currentParams.MinorRadius = float32(v) LogSuccess("Parameters", fmt.Sprintf("Minor Radius: %.3f → %.3f", oldValue, float32(v))) } case "majorSegments": if v, ok := value.(float64); ok { oldValue := currentParams.MajorSegments currentParams.MajorSegments = int(v) LogSuccess("Parameters", fmt.Sprintf("Major Segments: %d → %d", oldValue, int(v))) } case "minorSegments": if v, ok := value.(float64); ok { oldValue := currentParams.MinorSegments currentParams.MinorSegments = int(v) LogSuccess("Parameters", fmt.Sprintf("Minor Segments: %d → %d", oldValue, int(v))) } case "rotationSpeedX": if v, ok := value.(float64); ok { oldValue := currentParams.RotationSpeedX currentParams.RotationSpeedX = float32(v) LogSuccess("Parameters", fmt.Sprintf("X Rotation Speed: %.1f → %.1f", oldValue, float32(v))) } case "rotationSpeedY": if v, ok := value.(float64); ok { oldValue := currentParams.RotationSpeedY currentParams.RotationSpeedY = float32(v) LogSuccess("Parameters", fmt.Sprintf("Y Rotation Speed: %.1f → %.1f", oldValue, float32(v))) } } } // Copy back to the main params if provided if params != nil { *params = currentParams } LogInfo("System", "Torus parameters updated successfully") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "updated"}) }) // API endpoint to update asteroid parameters http.HandleFunc("/api/asteroid-params", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } r.Body = http.MaxBytesReader(w, r.Body, 1048576) // 1MB limit var updates map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&updates); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // Log parameter changes for key, value := range updates { LogInfo("UI", fmt.Sprintf("Asteroid parameter changed: %s = %v", key, value)) } // Update parameters for key, value := range updates { switch key { case "spawnRate": if v, ok := value.(float64); ok { oldValue := currentAsteroidParams.SpawnRate currentAsteroidParams.SpawnRate = float32(v) LogSuccess("Asteroids", fmt.Sprintf("Spawn Rate: %.1f → %.1f", oldValue, float32(v))) } case "maxAsteroids": if v, ok := value.(float64); ok { oldValue := currentAsteroidParams.MaxAsteroids currentAsteroidParams.MaxAsteroids = int(v) LogSuccess("Asteroids", fmt.Sprintf("Max Asteroids: %d → %d", oldValue, int(v))) } case "minSize": if v, ok := value.(float64); ok { oldValue := currentAsteroidParams.MinSize currentAsteroidParams.MinSize = float32(v) LogSuccess("Asteroids", fmt.Sprintf("Min Size: %.3f → %.3f", oldValue, float32(v))) } case "maxSize": if v, ok := value.(float64); ok { oldValue := currentAsteroidParams.MaxSize currentAsteroidParams.MaxSize = float32(v) LogSuccess("Asteroids", fmt.Sprintf("Max Size: %.3f → %.3f", oldValue, float32(v))) } case "minSpeed": if v, ok := value.(float64); ok { oldValue := currentAsteroidParams.MinSpeed currentAsteroidParams.MinSpeed = float32(v) LogSuccess("Asteroids", fmt.Sprintf("Min Speed: %.1f → %.1f", oldValue, float32(v))) } case "maxSpeed": if v, ok := value.(float64); ok { oldValue := currentAsteroidParams.MaxSpeed currentAsteroidParams.MaxSpeed = float32(v) LogSuccess("Asteroids", fmt.Sprintf("Max Speed: %.1f → %.1f", oldValue, float32(v))) } case "rotationSpeed": if v, ok := value.(float64); ok { oldValue := currentAsteroidParams.RotationSpeed currentAsteroidParams.RotationSpeed = float32(v) LogSuccess("Asteroids", fmt.Sprintf("Rotation Speed: %.1f → %.1f", oldValue, float32(v))) } } } // Update the actual asteroid config in main.go UpdateAsteroidConfig(currentAsteroidParams) LogInfo("System", "Asteroid parameters updated successfully") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "updated"}) }) // API endpoint to clear asteroids http.HandleFunc("/api/clear-asteroids", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Call the actual clear function in main.go ClearAllAsteroids() LogInfo("Asteroids", "Clearing all asteroids from field") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "asteroids cleared"}) }) // API endpoint for status http.HandleFunc("/api/status", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "cudaEnabled": cudaEnabled != nil && *cudaEnabled, "params": currentParams, "asteroidParams": currentAsteroidParams, "currentScene": currentScene, "fps": GetCurrentFPS(), "windowWidth": func() int { w, _ := GetWindowSize(); return w }(), "windowHeight": func() int { _, h := GetWindowSize(); return h }(), }) }) LogInfo("WebServer", "Starting web interface on http://localhost:8080") LogInfo("WebServer", "Access the control panel and logs in your browser") log.Fatal(http.ListenAndServe(":8080", nil)) } // GetCurrentParams returns the current torus parameters func GetCurrentParams() TorusParams { return currentParams } // GetCurrentAsteroidParams returns the current asteroid parameters func GetCurrentAsteroidParams() AsteroidParams { return currentAsteroidParams } // GetCurrentScene returns the current scene name func GetCurrentScene() string { return currentScene }