<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Psychedelic 3D Creature Simulator</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #191970; /* Match initial bg */ }
        canvas { display: block; }
        #info {
            position: absolute;
            top: 10px;
            width: 100%;
            text-align: center;
            z-index: 100;
            display:block;
            color: white;
            font-family: monospace;
            text-shadow: 1px 1px 2px black; /* Add shadow for visibility */
        }
    </style>
</head>
<body>
    <div id="info">Psychedelic 3D Creature Simulator<br/>Use mouse/touch to look around and zoom.</div>
    <canvas id="c"></canvas>

    <!-- Import Map for Three.js Modules -->
    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/three@0.162.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.162.0/examples/jsm/"
            }
        }
    </script>

    <!-- Main Three.js Logic -->
    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        // import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; // Needed for loading advanced models

        let scene, camera, renderer, controls, clock;
        let ground, sun;
        const creatures = [];
        const environmentObjects = [];
        const worldBoundary = 200; // How far creatures roam before wrapping
        const worldSize = worldBoundary * 2.5; // Make ground larger than roam area

        // Define the new color palette
        const colorPalette = {
            blue: 0x0077FF, // Brighter Blue
            red: 0xFF0000,
            yellow: 0xFFFF00,
            violet: 0x9400D3, // DarkViolet
            darkBlue: 0x00008B, // DarkBlue
            purple: 0x8A2BE2, // BlueViolet
            brightYellow: 0xFFD700, // Gold
            darkViolet: 0x4B0082, // Indigo
            crimson: 0xDC143C
        };
        const environmentPalette = [colorPalette.blue, colorPalette.red, colorPalette.yellow, colorPalette.violet];


        // --- Initialization ---
        function init() {
            clock = new THREE.Clock();

            // Scene
            scene = new THREE.Scene();
            scene.background = new THREE.Color(colorPalette.darkBlue); // Dark Blue background
            scene.fog = new THREE.Fog(colorPalette.darkBlue, worldBoundary * 0.8, worldBoundary * 1.5); // Match background

            // Camera
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, worldBoundary * 2);
            camera.position.set(0, 20, 40);

            // Renderer
            const canvas = document.querySelector('#c');
            renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;

            // Lighting (Keep lights white for neutral illumination)
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
            scene.add(ambientLight);

            sun = new THREE.DirectionalLight(0xffffff, 1.5);
            sun.position.set(50, 80, 30);
            sun.castShadow = true;
            sun.shadow.mapSize.width = 1024;
            sun.shadow.mapSize.height = 1024;
            sun.shadow.camera.near = 0.5;
            sun.shadow.camera.far = 200;
            const shadowCamSize = 80;
            sun.shadow.camera.left = -shadowCamSize;
            sun.shadow.camera.right = shadowCamSize;
            sun.shadow.camera.top = shadowCamSize;
            sun.shadow.camera.bottom = -shadowCamSize;
            scene.add(sun);
            scene.add(sun.target);

            // Controls
            controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.maxPolarAngle = Math.PI / 2 - 0.1;
            controls.minDistance = 5;
            controls.maxDistance = worldBoundary * 0.7;

            // Ground
            const groundGeometry = new THREE.PlaneGeometry(worldSize, worldSize);
            // Use a violet shade for the ground
            const groundMaterial = new THREE.MeshStandardMaterial({ color: colorPalette.purple, side: THREE.DoubleSide });
            ground = new THREE.Mesh(groundGeometry, groundMaterial);
            ground.rotation.x = -Math.PI / 2;
            ground.receiveShadow = true;
            scene.add(ground);

            // Create Environment
            createEnvironment();

            // Create Creatures
            createCreatures();

            // Handle Window Resize
            window.addEventListener('resize', onWindowResize);

            // Start Animation Loop
            animate();
        }

        // --- Environment Creation ---
        function createEnvironment() {
            const density = 0.1;
            const objectCount = Math.floor(worldSize * worldSize * density / 50);

            // Trees
            for (let i = 0; i < objectCount / 2; i++) {
                createTree(getRandomPositionOnGround());
            }
            // Plants/Flowers/Fruit Trees (Simplified)
             for (let i = 0; i < objectCount; i++) {
                createPlant(getRandomPositionOnGround());
            }
        }

        function createTree(position) {
            const trunkHeight = THREE.MathUtils.randFloat(4, 8);
            const trunkRadius = trunkHeight * 0.1;
            const foliageHeight = trunkHeight * THREE.MathUtils.randFloat(1.5, 2.5);
            const foliageRadius = trunkHeight * THREE.MathUtils.randFloat(0.6, 1.0);

            const trunkGeom = new THREE.CylinderGeometry(trunkRadius * 0.7, trunkRadius, trunkHeight, 8);
            // Dark Violet trunk color
            const trunkMat = new THREE.MeshStandardMaterial({ color: colorPalette.darkViolet });
            const trunk = new THREE.Mesh(trunkGeom, trunkMat);
            trunk.position.set(position.x, trunkHeight / 2, position.z);
            trunk.castShadow = true;
            trunk.receiveShadow = true;
            scene.add(trunk);
            environmentObjects.push(trunk);

            // Foliage (Sphere) - Random color from the environment palette
            const foliageGeom = new THREE.SphereGeometry(foliageRadius, 8, 6);
            const foliageColor = environmentPalette[Math.floor(Math.random() * environmentPalette.length)];
            const foliageMat = new THREE.MeshStandardMaterial({ color: foliageColor });
            const foliage = new THREE.Mesh(foliageGeom, foliageMat);
            foliage.position.set(position.x, trunkHeight + foliageRadius*0.5, position.z);
            foliage.castShadow = true;
            foliage.receiveShadow = true;
            scene.add(foliage);
            environmentObjects.push(foliage);

            // Simple Fruits (Bright Yellow)
            if (Math.random() < 0.2) {
                for(let f=0; f<5; f++) {
                    const fruitGeom = new THREE.SphereGeometry(foliageRadius * 0.1, 6, 6);
                    // Bright Yellow fruit
                    const fruitMat = new THREE.MeshStandardMaterial({ color: colorPalette.brightYellow });
                    const fruit = new THREE.Mesh(fruitGeom, fruitMat);
                    const fruitPos = new THREE.Vector3()
                        .randomDirection()
                        .multiplyScalar(foliageRadius * Math.random())
                        .add(foliage.position);
                    fruit.position.copy(fruitPos);
                    fruit.castShadow = true;
                    scene.add(fruit);
                    environmentObjects.push(fruit);
                }
            }
        }

         function createPlant(position) {
            const plantHeight = THREE.MathUtils.randFloat(0.5, 1.5);
            const plantRadius = plantHeight * THREE.MathUtils.randFloat(0.3, 0.6);

            // Simple representation (Cone) - Random color from the environment palette
            const plantGeom = new THREE.ConeGeometry(plantRadius, plantHeight, 6);
            const plantColor = environmentPalette[Math.floor(Math.random() * environmentPalette.length)];
            const plantMat = new THREE.MeshStandardMaterial({ color: plantColor });
            const plant = new THREE.Mesh(plantGeom, plantMat);
            plant.position.set(position.x, plantHeight / 2, position.z);
            plant.castShadow = true;
            scene.add(plant);
            environmentObjects.push(plant);
         }

        function getRandomPositionOnGround() {
            const x = THREE.MathUtils.randFloat(-worldBoundary, worldBoundary);
            const z = THREE.MathUtils.randFloat(-worldBoundary, worldBoundary);
            return new THREE.Vector3(x, 0, z);
        }


        // --- Creature Creation (Simplified Geometry with New Colors) ---
        function createCreatures() {
            const numEach = 10;

            // Butterflies (Still random bright colors - they fit the theme)
            for (let i = 0; i < numEach; i++) {
                const geom = new THREE.SphereGeometry(0.2, 8, 6);
                const mat = new THREE.MeshBasicMaterial({ color: new THREE.Color().setHSL(Math.random(), 1.0, 0.7) }); // Keep random HSL
                const butterfly = new THREE.Mesh(geom, mat);
                butterfly.position.copy(getRandomPositionOnGround()).add(new THREE.Vector3(0, THREE.MathUtils.randFloat(2, 6), 0));
                setupCreature(butterfly, 'butterfly', THREE.MathUtils.randFloat(2, 5), true);
                scene.add(butterfly);
                creatures.push(butterfly);
            }

            // Birds (Blue)
             for (let i = 0; i < numEach; i++) {
                const geom = new THREE.ConeGeometry(0.3, 0.8, 8);
                const mat = new THREE.MeshStandardMaterial({ color: colorPalette.blue }); // Blue birds
                const bird = new THREE.Mesh(geom, mat);
                bird.position.copy(getRandomPositionOnGround()).add(new THREE.Vector3(0, THREE.MathUtils.randFloat(5, 15), 0));
                bird.rotation.x = Math.PI / 2;
                setupCreature(bird, 'bird', THREE.MathUtils.randFloat(5, 10), true);
                bird.castShadow = true;
                scene.add(bird);
                creatures.push(bird);
            }

            // Tigers (Red)
            for (let i = 0; i < numEach / 2; i++) {
                const geom = new THREE.CapsuleGeometry(0.8, 1.5, 4, 8);
                const mat = new THREE.MeshStandardMaterial({ color: colorPalette.crimson }); // Red tigers
                const tiger = new THREE.Mesh(geom, mat);
                tiger.position.copy(getRandomPositionOnGround()).add(new THREE.Vector3(0, 0.8, 0));
                tiger.rotation.x = Math.PI / 2;
                setupCreature(tiger, 'tiger', THREE.MathUtils.randFloat(3, 6), false);
                tiger.castShadow = true;
                scene.add(tiger);
                creatures.push(tiger);
            }

            // Cats (Yellow)
            for (let i = 0; i < numEach / 2; i++) {
                const geom = new THREE.CapsuleGeometry(0.4, 0.8, 4, 8);
                const mat = new THREE.MeshStandardMaterial({ color: colorPalette.brightYellow }); // Yellow cats
                const cat = new THREE.Mesh(geom, mat);
                cat.position.copy(getRandomPositionOnGround()).add(new THREE.Vector3(0, 0.4, 0));
                cat.rotation.x = Math.PI / 2;
                setupCreature(cat, 'cat', THREE.MathUtils.randFloat(2, 5), false);
                cat.castShadow = true;
                scene.add(cat);
                creatures.push(cat);
            }

            // Dogs (Violet)
             for (let i = 0; i < numEach / 2; i++) {
                const geom = new THREE.CapsuleGeometry(0.6, 1.2, 4, 8);
                const mat = new THREE.MeshStandardMaterial({ color: colorPalette.violet }); // Violet dogs
                const dog = new THREE.Mesh(geom, mat);
                dog.position.copy(getRandomPositionOnGround()).add(new THREE.Vector3(0, 0.6, 0));
                dog.rotation.x = Math.PI / 2;
                setupCreature(dog, 'dog', THREE.MathUtils.randFloat(4, 8), false);
                dog.castShadow = true;
                scene.add(dog);
                creatures.push(dog);
            }
        }

        // Helper to set up creature movement data (No changes needed here)
        function setupCreature(mesh, type, speed, canFly) {
            mesh.userData = {
                type: type,
                speed: speed,
                canFly: canFly,
                velocity: new THREE.Vector3(),
                target: getRandomTarget(mesh.position, canFly),
                wanderlust: 0.02,
                flyHeight: canFly ? THREE.MathUtils.randFloat(2, type === 'bird' ? 15 : 6) : mesh.position.y,
                baseY: mesh.position.y,
                flutter: Math.random() * Math.PI * 2
            };
        }

        function getRandomTarget(currentPosition, canFly) {
            const target = getRandomPositionOnGround();
             if (canFly) {
                target.y = THREE.MathUtils.randFloat(2, 15);
            } else {
                 target.y = currentPosition.y; // Use base Y for ground creatures target
                 // Correction: Use the stored baseY if available, otherwise current Y
                 target.y = currentPosition.userData?.baseY ?? currentPosition.y;
             }
             target.sub(currentPosition).normalize().multiplyScalar(THREE.MathUtils.randFloat(20, 80)).add(currentPosition);
             const innerBoundary = worldBoundary * 0.95;
             target.x = THREE.MathUtils.clamp(target.x, -innerBoundary, innerBoundary);
             target.z = THREE.MathUtils.clamp(target.z, -innerBoundary, innerBoundary);
             if(canFly) {
                 target.y = THREE.MathUtils.clamp(target.y, 1, 20);
             } else {
                 target.y = currentPosition.userData?.baseY ?? currentPosition.y; // Ensure ground target is at base Y
             }
            return target;
        }


        // --- Animation Loop (No changes needed here) ---
        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta();
            const elapsed = clock.getElapsedTime();

            creatures.forEach(creature => {
                updateCreature(creature, delta, elapsed);
            });

            controls.update();
            renderer.render(scene, camera);
        }

        // --- Creature Movement Logic (Small correction for ground creature Y target) ---
        function updateCreature(creature, delta, elapsed) {
             const data = creature.userData;
             const direction = new THREE.Vector3().subVectors(data.target, creature.position);
             const distanceToTarget = direction.length();

             if (distanceToTarget < data.speed * delta * 5 || distanceToTarget < 1.0) {
                  data.target = getRandomTarget(creature.position, data.canFly);
                  direction.subVectors(data.target, creature.position); // Recalculate direction
             }

             direction.normalize();
             data.velocity.lerp(direction.multiplyScalar(data.speed), data.wanderlust);

             const moveStep = data.velocity.clone().multiplyScalar(delta);
             creature.position.add(moveStep);

             if (!(creature.geometry instanceof THREE.SphereGeometry)) {
                  const lookTargetPos = creature.position.clone().add(data.velocity.clone().normalize().multiplyScalar(2)); // Look slightly ahead
                  if(!data.canFly) {
                      lookTargetPos.y = data.baseY; // Keep horizontal look for ground animals
                  }
                  creature.lookAt(lookTargetPos);
                  if (creature.geometry instanceof THREE.CapsuleGeometry || creature.geometry instanceof THREE.ConeGeometry) {
                      creature.rotateX(Math.PI / 2); // Geometry orientation correction
                  }
              }

             if (data.canFly) {
                 data.flutter += delta * 10;
                 const flutterAmount = data.type === 'butterfly' ? 0.05 : 0.02;
                 creature.position.y += Math.sin(data.flutter) * flutterAmount;
                 if(Math.abs(creature.position.y - data.flyHeight) > 0.5) {
                      creature.position.y += Math.sign(data.flyHeight - creature.position.y) * delta * 0.5;
                  }
             } else {
                  creature.position.y = data.baseY; // Keep ground creatures strictly on ground
             }

             // Wrap around
             if (Math.abs(creature.position.x) > worldBoundary) {
                 creature.position.x = -creature.position.x * 0.98;
             }
             if (Math.abs(creature.position.z) > worldBoundary) {
                 creature.position.z = -creature.position.z * 0.98;
             }
             if(data.canFly){
                  creature.position.y = THREE.MathUtils.clamp(creature.position.y, 0.5, 25);
              }
        }


        // --- Utility: Handle Window Resize (No changes needed here) ---
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        }

        // --- Start ---
        init();

    </script>
</body>
</html>

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.