Bouncy Shaded Cube Final Release

Spawns multiple bouncy 3D objects with shape selection, corrected collision, and a settings UI.

// ==UserScript==
// @name         Bouncy Shaded Cube Final Release
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Spawns multiple bouncy 3D objects with shape selection, corrected collision, and a settings UI.
// @author       alisteveman12
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js
// @run-at       document-idle
// @icon       data:image/jpeg;base64,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
// ==/UserScript==

(function( ) {
    'use strict';

    // Check if THREE is loaded. If not, wait and check again.
    if (typeof window.THREE === 'undefined') {
        window.setTimeout(arguments.callee, 100);
        return;
    }

    // --- Configuration (Stored in GM_values) ---
    const DEFAULT_SETTINGS = {
        SHAPE: 'Cube', // New setting
        CUBE_SIZE: 50,
        SPEED: 2,
        BOUNCE_HEIGHT: 10,
        BOUNCE_SPEED: 0.05,
        MAX_CUBES: 10,
        COLLISION_ENABLED: true,
        CUBE_COLOR: 0x00ff00 // Default green color
    };

    let settings = {};

    // --- Global State Variables ---
    let cubeIsRunning = false;
    let animationFrameId = null;
    let canvas = null;
    let renderer = null;
    let scene = null;
    let camera = null;
    let cubes = [];
    let cubeButton = null;
    let spawnMoreButton = null;
    let deleteCubeButton = null;
    let settingsButton = null;
    let settingsPanel = null;

    let width = window.innerWidth;
    let height = window.innerHeight;

    // --- Cube Class (to manage multiple cubes) ---
    class BouncyCube {
        constructor(color) {
            let geometry;
            const size = settings.CUBE_SIZE;

            switch (settings.SHAPE) {
                case 'Sphere':
                    geometry = new THREE.SphereGeometry(size / 2, 32, 32);
                    break;
                case 'Tetrahedron':
                    geometry = new THREE.TetrahedronGeometry(size / 2);
                    break;
                case 'Custom':
                    // --- Placeholder for Custom Model Loading ---
                    console.warn("Custom Model selected, but not yet implemented. Defaulting to Cube.");
                    geometry = new THREE.BoxGeometry(size, size, size);
                    break;
                case 'Cube':
                default:
                    geometry = new THREE.BoxGeometry(size, size, size);
                    break;
            }

            this.geometry = geometry;
            const objectColor = color || Math.random() * 0xffffff;
            this.material = new THREE.MeshPhongMaterial({ color: objectColor, shininess: 30 });
            this.mesh = new THREE.Mesh(this.geometry, this.material);
            scene.add(this.mesh);

            // Initial random position and velocity
            this.cubeX = Math.random() * width;
            this.cubeY = Math.random() * height;
            this.velocityX = (Math.random() > 0.5 ? 1 : -1) * (settings.SPEED + Math.random() * 1);
            this.velocityY = (Math.random() > 0.5 ? 1 : -1) * (settings.SPEED + Math.random() * 1);
            this.timeOffset = Math.random() * 100;

            // Set initial world position
            const worldPos = screenToWorld(this.cubeX, this.cubeY);
            this.mesh.position.x = worldPos.x;
            this.mesh.position.y = worldPos.y;
        }

        update() {
            // 1. Update position based on screen coordinates
            this.cubeX += this.velocityX;
            this.cubeY += this.velocityY;
            this.timeOffset += settings.BOUNCE_SPEED;

            // 2. Collision detection (on screen edges)
            const halfSize = settings.CUBE_SIZE / 2;
            if (this.cubeX + halfSize > width || this.cubeX - halfSize < 0) {
                this.velocityX = -this.velocityX;
                this.cubeX = Math.max(halfSize, Math.min(width - halfSize, this.cubeX));
            }

            if (this.cubeY + halfSize > height || this.cubeY - halfSize < 0) {
                this.velocityY = -this.velocityY;
                this.cubeY = Math.max(halfSize, Math.min(height - halfSize, this.cubeY));
            }

            // 3. Convert screen position to world position for rendering
            const worldPos = screenToWorld(this.cubeX, this.cubeY);
            this.mesh.position.x = worldPos.x;
            this.mesh.position.y = worldPos.y;

            // 4. "Bounce" effect (up and down along the Z-axis)
            this.mesh.position.z = Math.sin(this.timeOffset) * settings.BOUNCE_HEIGHT;

            // 5. Rotation for visual interest
            this.mesh.rotation.x += 0.01;
            this.mesh.rotation.y += 0.01;
        }

        dispose() {
            scene.remove(this.mesh);
            this.geometry.dispose();
            this.material.dispose();
        }
    }

    // --- Utility Functions ---

    function loadSettings() {
        settings = GM_getValue('bouncyCubeSettings', DEFAULT_SETTINGS);
        // Ensure all default keys exist in case of new settings
        settings = { ...DEFAULT_SETTINGS, ...settings };
    }

    function saveSettings() {
        GM_setValue('bouncyCubeSettings', settings);
    }

    // Function to convert screen coordinates to Three.js world coordinates
    function screenToWorld(x, y) {
        const vector = new THREE.Vector3();
        vector.set(
            (x / width) * 2 - 1,
            -(y / height) * 2 + 1,
            0.5
        );
        vector.unproject(camera);
        const dir = vector.sub(camera.position).normalize();
        const distance = -camera.position.z / dir.z;
        const pos = camera.position.clone().add(dir.multiplyScalar(distance));
        return pos;
    }

    function onWindowResize() {
        if (!cubeIsRunning) return;

        width = window.innerWidth;
        height = window.innerHeight;

        // Update canvas size
        canvas.width = width;
        canvas.height = height;

        // Update camera aspect ratio
        camera.aspect = width / height;
        camera.updateProjectionMatrix();

        // Update renderer size
        renderer.setSize(width, height);
    }

    function checkCollisions() {
        if (!settings.COLLISION_ENABLED) return;

        for (let i = 0; i < cubes.length; i++) {
            for (let j = i + 1; j < cubes.length; j++) {
                const cubeA = cubes[i];
                const cubeB = cubes[j];

                const dx = cubeA.cubeX - cubeB.cubeX;
                const dy = cubeA.cubeY - cubeB.cubeY;
                const distance = Math.sqrt(dx * dx + dy * dy);
                const minDistance = settings.CUBE_SIZE;

                if (distance < minDistance) {
                    // Collision detected
                    const angle = Math.atan2(dy, dx);
                    const sin = Math.sin(angle);
                    const cos = Math.cos(angle);

                    // Rotate velocities to the collision axis
                    const vA = { x: cubeA.velocityX, y: cubeA.velocityY };
                    const vB = { x: cubeB.velocityX, y: cubeB.velocityY };

                    const uA = {
                        x: vA.x * cos + vA.y * sin,
                        y: vA.y * cos - vA.x * sin
                    };
                    const uB = {
                        x: vB.x * cos + vB.y * sin,
                        y: vB.y * cos - vB.x * sin
                    };

                    // Swap x velocities (perfect elastic collision)
                    const vFinalA = { x: uB.x, y: uA.y };
                    const vFinalB = { x: uA.x, y: uB.y };

                    // Rotate back to original coordinate system
                    cubeA.velocityX = vFinalA.x * cos - vFinalA.y * sin;
                    cubeA.velocityY = vFinalA.y * cos + vFinalA.x * sin;

                    cubeB.velocityX = vFinalB.x * cos - vFinalB.y * sin;
                    cubeB.velocityY = vFinalB.y * cos + vFinalB.x * sin;

                    // Separation to prevent sticking (crucial fix)
                    const overlap = minDistance - distance;
                    const separationX = overlap * cos;
                    const separationY = overlap * sin;

                    // Move both cubes apart by half the overlap
                    cubeA.cubeX += separationX * 0.5;
                    cubeA.cubeY += separationY * 0.5;
                    cubeB.cubeX -= separationX * 0.5;
                    cubeB.cubeY -= separationY * 0.5;
                }
            }
        }
    }

    function animate() {
        animationFrameId = requestAnimationFrame(animate);

        // Update all cubes
        cubes.forEach(cube => cube.update());

        // Check for collisions
        checkCollisions();

        // Render the scene
        renderer.render(scene, camera);
    }

    function updateButtonVisibility() {
        const hasCubes = cubes.length > 0;

        if (spawnMoreButton) {
            spawnMoreButton.style.display = hasCubes ? 'block' : 'none';
            spawnMoreButton.textContent = `Spawn More (${cubes.length}/${settings.MAX_CUBES})`;
        }

        if (deleteCubeButton) {
            deleteCubeButton.style.display = hasCubes ? 'block' : 'none';
            deleteCubeButton.textContent = `Delete (${cubes.length})`;
        }

        if (settingsButton) {
            settingsButton.style.display = hasCubes ? 'block' : 'none';
            // Also hide the settings panel if the button is hidden
            if (!hasCubes && settingsPanel) {
                settingsPanel.style.display = 'none';
            }
        }

        // Update main toggle button text
        cubeButton.textContent = hasCubes ? 'Stop Objects' : 'Start Objects';
    }

    function spawnCube() {
        if (cubes.length >= settings.MAX_CUBES) {
            alert(`Maximum number of objects (${settings.MAX_CUBES}) reached!`);
            return;
        }
        cubes.push(new BouncyCube(settings.CUBE_COLOR));
        updateButtonVisibility();
    }

    function deleteCube() {
        if (cubes.length === 0) {
            return;
        }
        // Remove the last spawned cube
        const cubeToRemove = cubes.pop();
        cubeToRemove.dispose();

        updateButtonVisibility();

        // If all cubes are gone, stop the whole animation
        if (cubes.length === 0) {
            stopCube();
        }
    }

    // --- Main Control Functions ---

    function startCube() {
        if (cubeIsRunning) return;

        // 1. Setup Canvas
        canvas = document.createElement('canvas');
        canvas.id = 'bouncy-cube-canvas';
        canvas.style.cssText = 'position: fixed; top: 0; left: 0; pointer-events: none; z-index: 99999;';
        document.body.appendChild(canvas);

        width = window.innerWidth;
        height = window.innerHeight;
        canvas.width = width;
        canvas.height = height;

        // 2. Three.js Setup
        scene = new THREE.Scene();
        camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
        renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true });
        renderer.setSize(width, height);
        camera.position.z = 100;

        // 3. Lighting Setup
        const ambientLight = new THREE.AmbientLight(0x404040, 2);
        scene.add(ambientLight);
        const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        directionalLight.position.set(1, 1, 1).normalize();
        scene.add(directionalLight);

        // 4. Spawn the initial cube
        cubes = [];
        spawnCube();

        // 5. Start Animation and Listeners
        window.addEventListener('resize', onWindowResize, false);
        animate();

        cubeIsRunning = true;
        updateButtonVisibility();
    }

    function stopCube() {
        if (!cubeIsRunning) return;

        // 1. Stop Animation
        cancelAnimationFrame(animationFrameId);

        // 2. Remove Canvas and Listeners
        if (canvas && canvas.parentNode) {
            canvas.parentNode.removeChild(canvas);
        }
        window.removeEventListener('resize', onWindowResize, false);

        // 3. Dispose of all cubes
        cubes.forEach(cube => cube.dispose());
        cubes = [];

        // 4. Cleanup Three.js objects
        renderer.dispose();
        scene = null;
        camera = null;
        renderer = null;
        canvas = null;

        cubeIsRunning = false;
        updateButtonVisibility();
    }

    function toggleCube() {
        if (cubeIsRunning) {
            stopCube();
        } else {
            startCube();
        }
    }

    function toggleSettingsPanel() {
        if (settingsPanel.style.display === 'block') {
            settingsPanel.style.display = 'none';
        } else {
            settingsPanel.style.display = 'block';
            populateSettingsPanel();
        }
    }

    function handleSettingChange(event) {
        const key = event.target.id.replace('setting-', '');
        let value;

        if (event.target.type === 'checkbox') {
            value = event.target.checked;
        } else if (event.target.type === 'color') {
            // Convert hex color string to number
            value = parseInt(event.target.value.substring(1), 16);
        } else if (event.target.tagName === 'SELECT') {
            value = event.target.value;
        } else {
            value = parseFloat(event.target.value);
            if (key === 'MAX_CUBES') {
                value = parseInt(event.target.value);
            }
        }

        settings[key] = value;
        saveSettings();

        // Special case: If a geometry-affecting setting changes, re-initialize cubes
        if (cubeIsRunning && (key === 'CUBE_SIZE' || key === 'CUBE_COLOR' || key === 'SHAPE')) {
            // Stop and restart to apply new geometry/material
            stopCube();
            startCube();
        }

        updateButtonVisibility();
    }

    function populateSettingsPanel() {
        const shapeOptions = [
            { value: 'Cube', text: 'Cube' },
            { value: 'Sphere', text: 'Sphere' },
            { value: 'Tetrahedron', text: 'Tetrahedron (Triangle)' },
            { value: 'Custom', text: 'Custom Model (Coming Soon)', disabled: true }
        ];

        let shapeSelectOptions = shapeOptions.map(opt =>
            `<option value="${opt.value}" ${settings.SHAPE === opt.value ? 'selected' : ''} ${opt.disabled ? 'disabled' : ''}>${opt.text}</option>`
        ).join('');

        settingsPanel.innerHTML = `
            <h3>Object Settings</h3>
            <div class="setting-group">
                <label for="setting-SHAPE">Shape:</label>
                <select id="setting-SHAPE">
                    ${shapeSelectOptions}
                </select>
            </div>
            <div class="setting-group">
                <label for="setting-CUBE_SIZE">Size:</label>
                <input type="range" id="setting-CUBE_SIZE" min="10" max="100" step="5" value="${settings.CUBE_SIZE}">
                <span>${settings.CUBE_SIZE}</span>
            </div>
            <div class="setting-group">
                <label for="setting-SPEED">Speed:</label>
                <input type="range" id="setting-SPEED" min="0.5" max="5" step="0.5" value="${settings.SPEED}">
                <span>${settings.SPEED}</span>
            </div>
            <div class="setting-group">
                <label for="setting-BOUNCE_HEIGHT">Bounce Height:</label>
                <input type="range" id="setting-BOUNCE_HEIGHT" min="0" max="50" step="5" value="${settings.BOUNCE_HEIGHT}">
                <span>${settings.BOUNCE_HEIGHT}</span>
            </div>
            <div class="setting-group">
                <label for="setting-BOUNCE_SPEED">Bounce Speed:</label>
                <input type="range" id="setting-BOUNCE_SPEED" min="0.01" max="0.2" step="0.01" value="${settings.BOUNCE_SPEED}">
                <span>${settings.BOUNCE_SPEED}</span>
            </div>
            <div class="setting-group">
                <label for="setting-MAX_CUBES">Max Objects:</label>
                <input type="number" id="setting-MAX_CUBES" min="1" max="50" value="${settings.MAX_CUBES}">
            </div>
            <div class="setting-group">
                <label for="setting-COLLISION_ENABLED">Collisions:</label>
                <input type="checkbox" id="setting-COLLISION_ENABLED" ${settings.COLLISION_ENABLED ? 'checked' : ''}>
            </div>
            <div class="setting-group">
                <label for="setting-CUBE_COLOR">Color:</label>
                <input type="color" id="setting-CUBE_COLOR" value="#${settings.CUBE_COLOR.toString(16).padStart(6, '0')}">
            </div>
            <button id="reset-settings">Reset to Default</button>
        `;

        // Add event listeners to all inputs
        settingsPanel.querySelectorAll('input, select').forEach(input => {
            input.addEventListener('input', handleSettingChange);
            // Update range span dynamically
            if (input.type === 'range') {
                input.addEventListener('input', (e) => {
                    e.target.nextElementSibling.textContent = e.target.value;
                });
            }
        });

        // Add event listener for reset button
        settingsPanel.querySelector('#reset-settings').addEventListener('click', () => {
            if (confirm('Are you sure you want to reset all settings to default?')) {
                settings = DEFAULT_SETTINGS;
                saveSettings();
                populateSettingsPanel(); // Re-populate with default values

                // Stop and restart to apply all defaults
                if (cubeIsRunning) {
                    stopCube();
                    startCube();
                }
            }
        });
    }

    // --- Menu Button and Panel Setup ---

    function createMenuButtons() {
        // Consolidated CSS for all buttons and the settings panel
        GM_addStyle("#bouncy-cube-toggle, #spawn-more-cubes, #delete-cube, #settings-button { position: fixed; padding: 10px 15px; background-color: #333; color: white; border: none; border-radius: 5px; cursor: pointer; z-index: 100000; font-family: sans-serif; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.5); transition: background-color 0.3s; } #bouncy-cube-toggle:hover, #spawn-more-cubes:hover, #delete-cube:hover, #settings-button:hover { background-color: #555; } #bouncy-cube-toggle { bottom: 10px; right: 10px; } #spawn-more-cubes { bottom: 60px; right: 10px; } #delete-cube { bottom: 110px; right: 10px; background-color: #d9534f; } #delete-cube:hover { background-color: #c9302c; } #settings-button { bottom: 160px; right: 10px; background-color: #f0ad4e; } #settings-button:hover { background-color: #ec971f; } #bouncy-cube-settings-panel { position: fixed; bottom: 10px; right: 180px; width: 300px; background-color: #222; color: #fff; border: 1px solid #555; border-radius: 8px; padding: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); z-index: 99999; display: none; font-family: sans-serif; } #bouncy-cube-settings-panel h3 { margin-top: 0; border-bottom: 1px solid #555; padding-bottom: 10px; } #bouncy-cube-settings-panel .setting-group { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } #bouncy-cube-settings-panel input[type=\"range\"] { flex-grow: 1; margin: 0 10px; } #bouncy-cube-settings-panel input[type=\"number\"], #bouncy-cube-settings-panel input[type=\"color\"], #bouncy-cube-settings-panel select { width: 60px; color: #000; } #bouncy-cube-settings-panel button { width: 100%; margin-top: 10px; background-color: #5bc0de; color: white; } #bouncy-cube-settings-panel button:hover { background-color: #31b0d5; }");

        // 1. Start/Stop Cube Button (Bottom)
        cubeButton = document.createElement('button');
        cubeButton.id = 'bouncy-cube-toggle';
        cubeButton.textContent = 'Start Objects';
        cubeButton.addEventListener('click', toggleCube);
        document.body.appendChild(cubeButton);

        // 2. Spawn More Button (Middle-Bottom)
        spawnMoreButton = document.createElement('button');
        spawnMoreButton.id = 'spawn-more-cubes';
        spawnMoreButton.textContent = 'Spawn More';
        spawnMoreButton.style.display = 'none';
        spawnMoreButton.addEventListener('click', spawnCube);
        document.body.appendChild(spawnMoreButton);

        // 3. Delete Cube Button (Middle-Top)
        deleteCubeButton = document.createElement('button');
        deleteCubeButton.id = 'delete-cube';
        deleteCubeButton.textContent = 'Delete';
        deleteCubeButton.style.display = 'none';
        deleteCubeButton.addEventListener('click', deleteCube);
        document.body.appendChild(deleteCubeButton);

        // 4. Settings Button (Top)
        settingsButton = document.createElement('button');
        settingsButton.id = 'settings-button';
        settingsButton.textContent = 'Settings';
        settingsButton.style.display = 'none'; // Initially hidden
        settingsButton.addEventListener('click', toggleSettingsPanel);
        document.body.appendChild(settingsButton);

        // 5. Settings Panel
        settingsPanel = document.createElement('div');
        settingsPanel.id = 'bouncy-cube-settings-panel';
        document.body.appendChild(settingsPanel);
    }

    // --- Initialization ---
    loadSettings();
    createMenuButtons();

})();