about summary refs log tree commit diff stats
path: root/html/side-scrolling-rogue-thing/js/world.js
diff options
context:
space:
mode:
Diffstat (limited to 'html/side-scrolling-rogue-thing/js/world.js')
-rw-r--r--html/side-scrolling-rogue-thing/js/world.js1054
1 files changed, 1054 insertions, 0 deletions
diff --git a/html/side-scrolling-rogue-thing/js/world.js b/html/side-scrolling-rogue-thing/js/world.js
new file mode 100644
index 0000000..2b2529a
--- /dev/null
+++ b/html/side-scrolling-rogue-thing/js/world.js
@@ -0,0 +1,1054 @@
+// Color palettes and configurations
+const NATURE_COLORS = {
+    GREENS: [
+        '#11aa33', // Darker forest green
+        '#22bb44', // Medium forest green
+        '#33cc55', // Bright forest green
+        '#118844', // Deep sea green
+        '#22aa66', // Sage green
+        '#11bb55', // Deep pine
+        '#229955', // Ocean green
+        '#33bb66', // Fresh spring green
+        '#117744', // Dark emerald
+    ],
+    TRUNK_COLORS: {
+        LIGHT: '#664422',
+        MEDIUM: '#553311',
+        DARK: '#442200'
+    }
+};
+
+const GRASS_CONFIG = {
+    MIN_BLADES: 4,
+    MAX_BLADES: 8,
+    SPREAD_FACTOR: 0.7,
+    RUSTLE_SPEED: 300,
+    WAVE_SPEED: 1000,
+    GLOW_SPEED: 500,
+    BASE_BLADE_WIDTH: 5
+};
+
+const FIR_CONFIG = {
+    ROW_COUNT: 40,
+    FEATHERS_PER_ROW: 24,
+    EDGE_FEATHER_COUNT: 40,
+    FEATHER_WIDTH: 2,
+    TAPER_POWER: 0.8,
+    CENTER_NEEDLES: 5,     // Number of needles in center column
+    NEEDLE_SPACING: 1.5,   // Spacing between center needles
+    UPWARD_TILT: 0.1,     // Amount of upward tilt (in radians)
+    ANGLE_VARIATION: 0.05, // Variation in needle angles
+    LENGTH_MULTIPLIER: 0.8 // Base length multiplier for needles
+};
+
+// Define world object types
+const WORLD_OBJECTS = {
+    PLATFORM: 'platform',
+    FIR_BACKGROUND: 'fir_background',
+    FIR_FOREGROUND: 'fir_foreground',
+    MAPLE_BACKGROUND: 'maple_background',
+    MAPLE_FOREGROUND: 'maple_foreground',
+    ROCK_BACKGROUND: 'rock_background',
+    ROCK_FOREGROUND: 'rock_foreground',
+    GRASS_BACKGROUND: 'grass_background',
+    GRASS_FOREGROUND: 'grass_foreground'
+};
+
+// Separate configurations for different tree types
+const FIR_TYPES = {
+    SMALL: {
+        type: 'fir',
+        width: 100,
+        height: 230,
+        canopyOffset: 45,
+        canopyColor: '#22aa44',
+        trunkColor: '#553311'
+    },
+    LARGE: {
+        type: 'fir',
+        width: 150,
+        height: 320,
+        canopyOffset: 65,
+        canopyColor: '#11aa44',
+        trunkColor: '#442200'
+    }
+};
+
+const MAPLE_TYPES = {
+    SMALL: {
+        type: 'maple',
+        width: 100,
+        height: 330,
+        trunkHeight: 60,
+        canopyRadius: 35,
+        leafClusters: 5,
+        canopyColor: '#33aa22', // Bright green
+        trunkColor: '#664422'
+    },
+    MEDIUM: {
+        type: 'maple',
+        width: 130,
+        height: 360,
+        trunkHeight: 70,
+        canopyRadius: 45,
+        leafClusters: 6,
+        canopyColor: '#228833', // Deep forest green
+        trunkColor: '#553311'
+    },
+    LARGE: {
+        type: 'maple',
+        width: 160,
+        height: 400,
+        trunkHeight: 85,
+        canopyRadius: 55,
+        leafClusters: 7,
+        canopyColor: '#115522', // Dark green
+        trunkColor: '#553311'
+    },
+    BRIGHT: {
+        type: 'maple',
+        width: 140,
+        height: 380,
+        trunkHeight: 75,
+        canopyRadius: 50,
+        leafClusters: 6,
+        canopyColor: '#44bb33', // Vibrant green
+        trunkColor: '#664422'
+    },
+    SAGE: {
+        type: 'maple',
+        width: 150,
+        height: 490,
+        trunkHeight: 80,
+        canopyRadius: 52,
+        leafClusters: 6,
+        canopyColor: '#225544', // Sage green
+        trunkColor: '#553311'
+    }
+};
+
+// Rock configurations
+const ROCK_TYPES = {
+    SMALL: {
+        width: 40,
+        height: 30,
+        color: '#666',
+        highlights: '#888'
+    },
+    MEDIUM: {
+        width: 70,
+        height: 45,
+        color: '#555',
+        highlights: '#777'
+    },
+    LARGE: {
+        width: 100,
+        height: 60,
+        color: '#444',
+        highlights: '#666'
+    }
+};
+
+// Add grass configurations
+const GRASS_TYPES = {
+    TALL: {
+        type: 'grass',
+        width: 30,
+        height: 40,
+        color: '#33aa55',
+        shadowColor: '#229944'
+    },
+    SHORT: {
+        type: 'grass',
+        width: 20,
+        height: 25,
+        color: '#33bb66',
+        shadowColor: '#22aa55'
+    },
+    GOLDEN_TALL: {
+        type: 'grass',
+        width: 30,
+        height: 40,
+        color: '#eebb33',
+        shadowColor: '#cc9922'
+    },
+    GOLDEN_SHORT: {
+        type: 'grass',
+        width: 20,
+        height: 25,
+        color: '#ffcc44',
+        shadowColor: '#ddaa33'
+    },
+    BLUE_TALL: {
+        type: 'grass',
+        width: 30,
+        height: 40,
+        color: '#44aaff',
+        shadowColor: '#2299ff',
+        glowing: true
+    },
+    BLUE_SHORT: {
+        type: 'grass',
+        width: 20,
+        height: 25,
+        color: '#55bbff',
+        shadowColor: '#33aaff',
+        glowing: true
+    }
+};
+
+// Utility functions
+const utils = {
+    getRandomColorFromPalette: (palette, seed1, seed2) => {
+        const colorIndex = Math.floor(seededRandom(seed1, seed2) * palette.length);
+        return palette[colorIndex];
+    },
+
+    getBladeCount: (x, height) => {
+        const randomValue = Math.abs(seededRandom(x, height));
+        return GRASS_CONFIG.MIN_BLADES + 
+            Math.round(randomValue * (GRASS_CONFIG.MAX_BLADES - GRASS_CONFIG.MIN_BLADES));
+    },
+
+    calculateTaper: (t) => Math.pow(1 - t, FIR_CONFIG.TAPER_POWER)
+};
+
+// Create a world with platforms, trees, and rocks
+const createWorld = () => {
+    const world = {
+        groundHeight: 12,
+        // Separate arrays for different layers
+        backgroundTrees: [
+            // Far left trees
+            {
+                type: WORLD_OBJECTS.FIR_BACKGROUND,
+                x: -1500,
+                config: FIR_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
+                x: -1200,
+                config: MAPLE_TYPES.SAGE
+            },
+            {
+                type: WORLD_OBJECTS.FIR_BACKGROUND,
+                x: -900,
+                config: FIR_TYPES.SMALL
+            },
+            // Existing trees
+            {
+                type: WORLD_OBJECTS.FIR_BACKGROUND,
+                x: -400,
+                config: FIR_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
+                x: -250,
+                config: MAPLE_TYPES.BRIGHT
+            },
+            {
+                type: WORLD_OBJECTS.FIR_BACKGROUND,
+                x: 50,
+                config: FIR_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
+                x: 250,
+                config: MAPLE_TYPES.MEDIUM
+            },
+            {
+                type: WORLD_OBJECTS.FIR_BACKGROUND,
+                x: 500,
+                config: FIR_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
+                x: 650,
+                config: MAPLE_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.FIR_BACKGROUND,
+                x: 900,
+                config: FIR_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
+                x: 1100,
+                config: MAPLE_TYPES.LARGE
+            },
+            // Far right trees
+            {
+                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
+                x: 1400,
+                config: MAPLE_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.FIR_BACKGROUND,
+                x: 1700,
+                config: FIR_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_BACKGROUND,
+                x: 2000,
+                config: MAPLE_TYPES.LARGE
+            }
+        ],
+        backgroundRocks: [
+            // Far left rocks
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: -1300,
+                config: ROCK_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: -1000,
+                config: ROCK_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: -300,
+                config: ROCK_TYPES.MEDIUM
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: -100,
+                config: ROCK_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: 150,
+                config: ROCK_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: 400,
+                config: ROCK_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: 750,
+                config: ROCK_TYPES.MEDIUM
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: 1000,
+                config: ROCK_TYPES.SMALL
+            },
+            // Far right rocks
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: 1600,
+                config: ROCK_TYPES.MEDIUM
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_BACKGROUND,
+                x: 1900,
+                config: ROCK_TYPES.SMALL
+            }
+        ],
+        platforms: [
+            // {
+            //     type: WORLD_OBJECTS.PLATFORM,
+            //     x: 300,
+            //     y: 300,
+            //     width: 200,
+            //     height: 20,
+            //     color: '#484'
+            // },
+            // {
+            //     type: WORLD_OBJECTS.PLATFORM,
+            //     x: 600,
+            //     y: 200,
+            //     width: 200,
+            //     height: 20,
+            //     color: '#484'
+            // },
+            // {
+            //     type: WORLD_OBJECTS.PLATFORM,
+            //     x: -200,
+            //     y: 250,
+            //     width: 200,
+            //     height: 20,
+            //     color: '#484'
+            // }
+        ],
+        foregroundTrees: [
+            // Far left trees
+            {
+                type: WORLD_OBJECTS.FIR_FOREGROUND,
+                x: -1400,
+                config: FIR_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
+                x: -1100,
+                config: MAPLE_TYPES.SMALL
+            },
+            // Existing trees
+            {
+                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
+                x: -150,
+                config: MAPLE_TYPES.BRIGHT
+            },
+            {
+                type: WORLD_OBJECTS.FIR_FOREGROUND,
+                x: 200,
+                config: FIR_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
+                x: 450,
+                config: MAPLE_TYPES.SAGE
+            },
+            {
+                type: WORLD_OBJECTS.FIR_FOREGROUND,
+                x: 800,
+                config: FIR_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
+                x: 1200,
+                config: MAPLE_TYPES.SMALL
+            },
+            // Far right trees
+            {
+                type: WORLD_OBJECTS.FIR_FOREGROUND,
+                x: 1500,
+                config: FIR_TYPES.LARGE
+            },
+            {
+                type: WORLD_OBJECTS.MAPLE_FOREGROUND,
+                x: 1800,
+                config: MAPLE_TYPES.SMALL
+            }
+        ],
+        foregroundRocks: [
+            // Far left rocks
+            {
+                type: WORLD_OBJECTS.ROCK_FOREGROUND,
+                x: -1000,
+                config: ROCK_TYPES.MEDIUM
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_FOREGROUND,
+                x: 0,
+                config: ROCK_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_FOREGROUND,
+                x: 300,
+                config: ROCK_TYPES.MEDIUM
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_FOREGROUND,
+                x: 700,
+                config: ROCK_TYPES.SMALL
+            },
+            {
+                type: WORLD_OBJECTS.ROCK_FOREGROUND,
+                x: 950,
+                config: ROCK_TYPES.LARGE
+            },
+            // Far right rocks
+            {
+                type: WORLD_OBJECTS.ROCK_FOREGROUND,
+                x: 1500,
+                config: ROCK_TYPES.LARGE
+            }
+        ],
+        backgroundGrass: [
+            // Far left grass
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -1400, config: GRASS_TYPES.BLUE_TALL },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -1100, config: GRASS_TYPES.GOLDEN_SHORT },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -950, config: GRASS_TYPES.TALL },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -350, config: GRASS_TYPES.SHORT },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -180, config: GRASS_TYPES.GOLDEN_TALL },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 100, config: GRASS_TYPES.TALL },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 320, config: GRASS_TYPES.BLUE_SHORT },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 580, config: GRASS_TYPES.GOLDEN_SHORT },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 750, config: GRASS_TYPES.BLUE_TALL },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 950, config: GRASS_TYPES.TALL },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1050, config: GRASS_TYPES.GOLDEN_TALL },
+            // Far right grass
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1500, config: GRASS_TYPES.BLUE_SHORT },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1750, config: GRASS_TYPES.GOLDEN_TALL },
+            { type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1850, config: GRASS_TYPES.SHORT }
+        ],
+        foregroundGrass: [
+            // Far left grass
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -1250, config: GRASS_TYPES.TALL },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -1050, config: GRASS_TYPES.BLUE_SHORT },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -280, config: GRASS_TYPES.BLUE_TALL },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -50, config: GRASS_TYPES.GOLDEN_SHORT },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 150, config: GRASS_TYPES.TALL },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 420, config: GRASS_TYPES.BLUE_SHORT },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 680, config: GRASS_TYPES.GOLDEN_TALL },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 880, config: GRASS_TYPES.SHORT },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1150, config: GRASS_TYPES.BLUE_TALL },
+            // Far right grass
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1650, config: GRASS_TYPES.GOLDEN_TALL },
+            { type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1850, config: GRASS_TYPES.BLUE_SHORT }
+        ],
+        // Track grass animation states
+        grassStates: {},
+        // Add methods for quick spatial lookups
+        getObjectsInView: function(bounds) {
+            return {
+                backgroundTrees: this.backgroundTrees.filter(tree => 
+                    tree.x > bounds.left && tree.x < bounds.right
+                ),
+                // ... similar for other object types
+            };
+        }
+    };
+    
+    return world;
+};
+
+// Add seededRandom function
+const seededRandom = (x, y) => {
+    const dot = x * 12.9898 + y * 78.233;
+    return (Math.sin(dot) * 43758.5453123) % 1;
+};
+
+// Add hatching helper function
+const addHatchingPattern = (ctx, x, y, width, height, color) => {
+    const spacing = 4; // Space between hatch lines
+    const length = 10;  // Length of each hatch line
+    const margin = 4; // Increased margin from edges
+    const baseAngle = Math.PI * 1.5; // Vertical angle (pointing down)
+    const angleVariation = Math.PI / 12; // Reduced angle variation
+    
+    // Define darker brown shades
+    const brownShades = [
+        '#442211', // Dark brown
+        '#553322', // Medium dark brown
+        '#443311', // Reddish dark brown
+        '#332211', // Very dark brown
+        '#554422', // Warm dark brown
+    ];
+    
+    // Create clipping path for trunk
+    ctx.save();
+    ctx.beginPath();
+    ctx.rect(
+        x - width/2,
+        y - height,
+        width,
+        height
+    );
+    ctx.clip();
+    
+    // Calculate bounds with increased safety margin
+    const bounds = {
+        minX: x - width/2 + margin,
+        maxX: x + width/2 - margin,
+        minY: y - height + margin,
+        maxY: y - margin
+    };
+
+    // Draw hatching
+    ctx.lineWidth = 1;
+    for (let hatchX = bounds.minX; hatchX < bounds.maxX; hatchX += spacing) {
+        for (let hatchY = bounds.minY; hatchY < bounds.maxY; hatchY += spacing) {
+            // Use position for random variation
+            const seed1 = hatchX * 13.37;
+            const seed2 = hatchY * 7.89;
+            
+            // Random variations with reduced range
+            const variation = {
+                x: (seededRandom(seed1, seed2) - 0.5) * 1.5, // Reduced variation
+                y: (seededRandom(seed2, seed1) - 0.5) * 1.5, // Reduced variation
+                angle: baseAngle + (seededRandom(seed1 + seed2, seed2 - seed1) - 0.5) * angleVariation
+            };
+
+            // Pick a random brown shade
+            const colorIndex = Math.floor(seededRandom(seed1 * seed2, seed2 * seed1) * brownShades.length);
+            ctx.strokeStyle = brownShades[colorIndex];
+
+            // Draw hatch line
+            ctx.beginPath();
+            ctx.moveTo(
+                hatchX + variation.x,
+                hatchY + variation.y
+            );
+            ctx.lineTo(
+                hatchX + Math.cos(variation.angle) * length + variation.x,
+                hatchY + Math.sin(variation.angle) * length + variation.y
+            );
+            ctx.stroke();
+        }
+    }
+    
+    ctx.restore();
+};
+
+class FirTreeRenderer {
+    static renderTrunk(ctx, x, width, height, groundY, trunkColor) {
+        const trunkWidth = width/4;
+        const trunkHeight = height;
+        
+        ctx.fillStyle = trunkColor;
+        ctx.fillRect(
+            x - trunkWidth/2,
+            groundY - trunkHeight,
+            trunkWidth,
+            trunkHeight
+        );
+        
+        addHatchingPattern(ctx, x, groundY, trunkWidth, trunkHeight, trunkColor);
+    }
+
+    static renderCenterNeedles(ctx, x, rowY, taper, featherLength, greenColors) {
+        for (let i = -FIR_CONFIG.CENTER_NEEDLES; i <= FIR_CONFIG.CENTER_NEEDLES; i++) {
+            const offset = i * (FIR_CONFIG.NEEDLE_SPACING * 1.5);
+            
+            // Left needle
+            FirTreeRenderer.renderSingleNeedle(
+                ctx, x + offset, rowY, Math.PI, taper, featherLength, greenColors
+            );
+            
+            // Right needle
+            FirTreeRenderer.renderSingleNeedle(
+                ctx, x + offset, rowY, 0, taper, featherLength, greenColors
+            );
+        }
+    }
+
+    static renderSingleNeedle(ctx, x, y, baseAngle, taper, length, colors) {
+        const angleVar = Math.PI * 0.02;
+        const angle = baseAngle + (angleVar * seededRandom(x, y) - angleVar/2);
+        
+        ctx.strokeStyle = utils.getRandomColorFromPalette(colors, x, y);
+        ctx.beginPath();
+        ctx.moveTo(x, y);
+        ctx.lineTo(
+            x + Math.cos(angle) * length * taper,
+            y + Math.sin(angle) * length * taper
+        );
+        ctx.stroke();
+    }
+
+    static renderRowNeedles(ctx, x, rowY, rowWidth, featherCount, t, taper, featherLength, greenColors) {
+        for (let i = 0; i < featherCount; i++) {
+            const featherT = i / (featherCount - 1);
+            const startX = x - rowWidth/2 + rowWidth * featherT;
+            
+            if (Math.abs(startX - x) < 1) continue;
+            
+            const relativeX = (startX - x) / (rowWidth/2);
+            const baseAngle = relativeX < 0 ? Math.PI : 0;
+            const upwardTilt = Math.PI * FIR_CONFIG.UPWARD_TILT * t;
+            const angle = baseAngle + 
+                (FIR_CONFIG.ANGLE_VARIATION * seededRandom(startX, rowY) - FIR_CONFIG.ANGLE_VARIATION/2) - 
+                upwardTilt;
+            
+            const lengthMultiplier = FIR_CONFIG.LENGTH_MULTIPLIER + (1 - t) * 0.3;
+            const finalLength = featherLength * lengthMultiplier * taper * 
+                (0.9 + seededRandom(startX * rowY, rowY) * 0.2);
+            
+            ctx.strokeStyle = utils.getRandomColorFromPalette(greenColors, startX, rowY);
+            ctx.beginPath();
+            ctx.moveTo(startX, rowY);
+            ctx.lineTo(
+                startX + Math.cos(angle) * finalLength,
+                rowY + Math.sin(angle) * finalLength
+            );
+            ctx.stroke();
+        }
+    }
+
+    static renderEdgeNeedles(ctx, x, baseWidth, baseY, tipY, featherLength, greenColors) {
+        for (let i = 0; i < FIR_CONFIG.EDGE_FEATHER_COUNT; i++) {
+            const t = i / (FIR_CONFIG.EDGE_FEATHER_COUNT - 1);
+            const taper = utils.calculateTaper(t);
+            
+            if (taper <= 0.1) continue;
+            
+            // Left edge
+            FirTreeRenderer.renderEdgeNeedle(
+                ctx, x, baseWidth, baseY, tipY, t, taper, 
+                featherLength, Math.PI, greenColors, true
+            );
+            
+            // Right edge
+            FirTreeRenderer.renderEdgeNeedle(
+                ctx, x, baseWidth, baseY, tipY, t, taper, 
+                featherLength, 0, greenColors, false
+            );
+        }
+    }
+
+    static renderEdgeNeedle(ctx, x, baseWidth, baseY, tipY, t, taper, length, baseAngle, colors, isLeft) {
+        const sign = isLeft ? -1 : 1;
+        const needleX = x + sign * (baseWidth/2 * taper - (baseWidth/2 * taper) * t);
+        const needleY = baseY - (baseY - tipY) * t;
+        
+        const upwardTilt = Math.PI * 0.1 * t;
+        const angle = baseAngle + 
+            (Math.PI * 0.05 * seededRandom(needleX, needleY) - Math.PI * 0.025) - 
+            upwardTilt;
+        
+        ctx.strokeStyle = utils.getRandomColorFromPalette(colors, needleX, needleY);
+        ctx.beginPath();
+        ctx.moveTo(needleX, needleY);
+        ctx.lineTo(
+            needleX + Math.cos(angle) * length * taper * 1.2,
+            needleY + Math.sin(angle) * length * taper * 1.2
+        );
+        ctx.stroke();
+    }
+}
+
+// Helper function to darken/lighten colors
+const shadeColor = (color, percent) => {
+    const num = parseInt(color.replace('#', ''), 16);
+    const amt = Math.round(2.55 * percent);
+    const R = (num >> 16) + amt;
+    const G = (num >> 8 & 0x00FF) + amt;
+    const B = (num & 0x0000FF) + amt;
+    return '#' + (0x1000000 +
+        (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
+        (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
+        (B < 255 ? (B < 1 ? 0 : B) : 255)
+    ).toString(16).slice(1);
+};
+
+// Add new maple tree render function
+const renderMapleTree = (ctx, tree, groundY) => {
+    const { x, config } = tree;
+    const { 
+        width, height, trunkHeight, canopyRadius,
+        leafClusters, trunkColor, canopyColor 
+    } = config;
+    
+    // Draw trunk base with narrower width (changed from width/4 to width/5)
+    const trunkWidth = width/5.5;
+    ctx.fillStyle = trunkColor;
+    ctx.fillRect(
+        x - trunkWidth/2,
+        groundY - trunkHeight,
+        trunkWidth,
+        trunkHeight
+    );
+    
+    // Add trunk hatching with updated width
+    addHatchingPattern(
+        ctx,
+        x,
+        groundY,
+        trunkWidth,
+        trunkHeight,
+        trunkColor
+    );
+
+    // Function to create an irregular polygon
+    const createIrregularPolygon = (centerX, centerY, radius, sides, seed1, seed2) => {
+        ctx.beginPath();
+        
+        // Create points array first to allow smoothing
+        const points = [];
+        for (let i = 0; i < sides; i++) {
+            const angle = (i / sides) * Math.PI * 2;
+            
+            // Even more subtle variation range (0.95-1.05)
+            const radiusVariation = 0.95 + seededRandom(seed1 * i, seed2 * i) * 0.10;
+            const pointRadius = radius * radiusVariation;
+            
+            points.push({
+                x: centerX + Math.cos(angle) * pointRadius,
+                y: centerY + Math.sin(angle) * pointRadius
+            });
+        }
+        
+        // Start the path
+        ctx.moveTo(points[0].x, points[0].y);
+        
+        // Draw smooth curves through all points
+        for (let i = 0; i < points.length; i++) {
+            const current = points[i];
+            const next = points[(i + 1) % points.length];
+            const nextNext = points[(i + 2) % points.length];
+            
+            // Calculate control points for bezier curve with more smoothing
+            const cp1x = current.x + (next.x - points[(i - 1 + points.length) % points.length].x) / 4;
+            const cp1y = current.y + (next.y - points[(i - 1 + points.length) % points.length].y) / 4;
+            const cp2x = next.x - (nextNext.x - current.x) / 4;
+            const cp2y = next.y - (nextNext.y - current.y) / 4;
+            
+            // Draw bezier curve
+            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, next.x, next.y);
+        }
+        
+        ctx.closePath();
+    };
+
+    // Draw single canopy as irregular polygon
+    const sides = Math.floor(48 + seededRandom(x, groundY) * 16); // 48-64 sides
+    const mainY = groundY - trunkHeight - canopyRadius;
+    
+    ctx.fillStyle = canopyColor;
+    createIrregularPolygon(
+        x,
+        mainY,
+        canopyRadius * 1.4, // Increased size
+        sides,
+        x,
+        groundY
+    );
+    ctx.fill();
+};
+
+// Main tree render function that handles both types
+const renderTree = (ctx, tree, groundY) => {
+    if (tree.config.type === 'fir') {
+        renderFirTree(ctx, tree, groundY);
+    } else if (tree.config.type === 'maple') {
+        renderMapleTree(ctx, tree, groundY);
+    }
+};
+
+const renderRock = (ctx, rock, groundY) => {
+    const { x, config } = rock;
+    const { width, height, color, highlights } = config;
+    
+    // Draw main rock shape (slightly irregular pentagon)
+    ctx.fillStyle = color;
+    ctx.beginPath();
+    ctx.moveTo(x - width/2, groundY);
+    ctx.lineTo(x - width/2 + width/6, groundY - height);
+    ctx.lineTo(x + width/3, groundY - height);
+    ctx.lineTo(x + width/2, groundY - height/2);
+    ctx.lineTo(x + width/2, groundY);
+    ctx.closePath();
+    ctx.fill();
+
+    // Add highlights
+    ctx.fillStyle = highlights;
+    ctx.beginPath();
+    ctx.moveTo(x - width/4, groundY - height);
+    ctx.lineTo(x, groundY - height);
+    ctx.lineTo(x + width/6, groundY - height/2);
+    ctx.lineTo(x - width/6, groundY - height/2);
+    ctx.closePath();
+    ctx.fill();
+};
+
+// Add grass rendering function
+const renderGrass = (ctx, grass, groundY, time) => {
+    const { x, config } = grass;
+    const { width, height, color, shadowColor, glowing } = config;
+    
+    // Initialize or get grass state
+    const stateKey = `grass_${x}`;
+    if (!window.gameState.world.grassStates[stateKey]) {
+        window.gameState.world.grassStates[stateKey] = GrassState.create(x, height);
+    }
+    const state = GrassState.validate(window.gameState.world.grassStates[stateKey]);
+    
+    // Update interaction
+    const player = window.gameState.player;
+    const distanceToPlayer = Math.abs(player.x + player.width / 2 - x);
+    GrassState.update(state, distanceToPlayer, width);
+
+    // Handle rendering
+    setupGlowEffect(ctx, glowing, color, time);
+    renderGrassBlades(ctx, state, x, width, height, groundY, time, color, shadowColor);
+    resetGlowEffect(ctx, glowing);
+};
+
+const setupGlowEffect = (ctx, glowing, color, time) => {
+    if (glowing) {
+        ctx.save();
+        ctx.shadowColor = color;
+        ctx.shadowBlur = 10 + Math.sin(time/GRASS_CONFIG.GLOW_SPEED) * 3;
+    }
+};
+
+const resetGlowEffect = (ctx, glowing) => {
+    if (glowing) {
+        ctx.restore();
+    }
+};
+
+const renderGrassBlades = (ctx, state, x, width, height, groundY, time, color, shadowColor) => {
+    const bladeWidth = width / GRASS_CONFIG.BASE_BLADE_WIDTH * 0.7;
+    
+    for (let i = 0; i < state.bladeCount; i++) {
+        renderSingleBlade(
+            ctx, i, state, x, width, height, groundY, time,
+            bladeWidth, color, shadowColor
+        );
+    }
+};
+
+const renderSingleBlade = (
+    ctx, index, state, x, width, height, groundY, time,
+    bladeWidth, color, shadowColor
+) => {
+    const centerOffset = (index - (state.bladeCount - 1) / 2) * 
+        (width * GRASS_CONFIG.SPREAD_FACTOR / state.bladeCount);
+    const bladeX = x + centerOffset;
+    
+    const rustleOffset = Math.sin(time / GRASS_CONFIG.RUSTLE_SPEED + index) * 5 * state.rustleAmount;
+    const baseWave = Math.sin(time / GRASS_CONFIG.WAVE_SPEED + index) * 2;
+    
+    drawBladeCurve(
+        ctx, bladeX, groundY, height, rustleOffset, baseWave,
+        bladeWidth, index % 2 === 0 ? color : shadowColor
+    );
+};
+
+const drawBladeCurve = (
+    ctx, bladeX, groundY, height, rustleOffset, baseWave,
+    bladeWidth, color
+) => {
+    const cp1x = bladeX + rustleOffset + baseWave;
+    const cp1y = groundY - height / 2;
+    const cp2x = bladeX + rustleOffset * 1.5 + baseWave;
+    const cp2y = groundY - height;
+    
+    ctx.fillStyle = color;
+    ctx.beginPath();
+    ctx.moveTo(bladeX, groundY);
+    ctx.quadraticCurveTo(cp1x, cp1y, cp2x, cp2y);
+    ctx.quadraticCurveTo(cp1x + bladeWidth, cp1y, bladeX + bladeWidth, groundY);
+    ctx.fill();
+};
+
+// Collision detection helper
+const checkCollision = (player, platform) => {
+    return player.x < platform.x + platform.width &&
+           player.x + player.width > platform.x &&
+           player.y < platform.y + platform.height &&
+           player.y + player.height > platform.y;
+};
+
+// World physics helper
+const handleWorldCollisions = (player, world) => {
+    let onGround = false;
+    
+    // Check ground collision first
+    const groundY = window.innerHeight - world.groundHeight;
+    if (player.y + player.height > groundY) {
+        player.y = groundY - player.height;
+        player.velocityY = 0;
+        onGround = true;
+    }
+    
+    // Then check platform collisions
+    for (const platform of world.platforms) {
+        if (checkCollision(player, platform)) {
+            // Calculate overlap on each axis
+            const overlapX = Math.min(
+                player.x + player.width - platform.x,
+                platform.x + platform.width - player.x
+            );
+            const overlapY = Math.min(
+                player.y + player.height - platform.y,
+                platform.y + platform.height - player.y
+            );
+
+            // Resolve collision on the axis with smallest overlap
+            if (overlapX < overlapY) {
+                // Horizontal collision
+                if (player.x < platform.x) {
+                    player.x = platform.x - player.width;
+                } else {
+                    player.x = platform.x + platform.width;
+                }
+                player.velocityX = 0;
+            } else {
+                // Vertical collision
+                if (player.y < platform.y) {
+                    player.y = platform.y - player.height;
+                    player.velocityY = 0;
+                    onGround = true;
+                } else {
+                    player.y = platform.y + platform.height;
+                    player.velocityY = 0;
+                }
+            }
+        }
+    }
+    
+    return { ...player, jumping: !onGround };
+};
+
+class GrassState {
+    static create(x, height) {
+        return {
+            rustleAmount: 0,
+            rustleDecay: 0.95,
+            bladeCount: utils.getBladeCount(x, height)
+        };
+    }
+
+    static validate(state) {
+        if (!state.bladeCount || state.bladeCount < GRASS_CONFIG.MIN_BLADES) {
+            state.bladeCount = GRASS_CONFIG.MIN_BLADES;
+        }
+        return state;
+    }
+
+    static update(state, distanceToPlayer, interactionDistance) {
+        if (distanceToPlayer < interactionDistance) {
+            state.rustleAmount = Math.min(1, state.rustleAmount + 0.3);
+        } else {
+            state.rustleAmount *= state.rustleDecay;
+        }
+    }
+}
+
+const renderFirTree = (ctx, tree, groundY) => {
+    const { x, config } = tree;
+    const { width, height, canopyOffset, trunkColor, canopyColor } = config;
+    
+    // Setup
+    const greenColors = NATURE_COLORS.GREENS.filter(color => color !== canopyColor);
+    ctx.lineWidth = FIR_CONFIG.FEATHER_WIDTH;
+    
+    // Render trunk
+    FirTreeRenderer.renderTrunk(
+        ctx, x, width, height - (height - canopyOffset)/2, 
+        groundY, trunkColor
+    );
+    
+    const drawFeatheredTree = (baseWidth, baseY, tipY) => {
+        const featherLength = baseWidth * 0.3;
+        
+        for (let row = 0; row < FIR_CONFIG.ROW_COUNT; row++) {
+            const t = row / (FIR_CONFIG.ROW_COUNT - 1);
+            const rowY = baseY - (baseY - tipY) * t;
+            
+            const taper = utils.calculateTaper(t);
+            const rowWidth = baseWidth * taper;
+            
+            if (rowWidth < 2) continue;
+            
+            const featherCount = Math.max(2, Math.floor(FIR_CONFIG.FEATHERS_PER_ROW * taper));
+            
+            // Render center column of needles
+            FirTreeRenderer.renderCenterNeedles(ctx, x, rowY, taper, featherLength, greenColors);
+            
+            // Render row needles
+            FirTreeRenderer.renderRowNeedles(
+                ctx, x, rowY, rowWidth, featherCount, t, 
+                taper, featherLength, greenColors
+            );
+        }
+        
+        // Render edge needles
+        FirTreeRenderer.renderEdgeNeedles(
+            ctx, x, baseWidth, baseY, tipY, featherLength, greenColors
+        );
+    };
+
+    // Draw complete tree
+    drawFeatheredTree(
+        width * 1.2,
+        groundY - canopyOffset,
+        groundY - height * 1.1
+    );
+};
08-10 11:26:32 +0200 committer Anselm R.Garbe <arg@10ksloc.org> 2006-08-10 11:26:32 +0200 applied sumik's multihead patch' href='/acidbong/suckless/dwm/commit/event.c?id=fde45ebed844c227a17c21d161f60aa55c8b3c41'>fde45eb ^
adaa28a ^


0f3acce ^









439e15d ^

439e15d ^
dc5d967 ^
439e15d ^









0053620 ^

439e15d ^




439e15d ^
dc5d967 ^

439e15d ^



3399650 ^
c09bf8d ^
dba2306 ^
b1701ad ^

3399650 ^


66da153 ^
901b3ed ^
66da153 ^
3399650 ^

c0705ee ^
3399650 ^

c09bf8d ^
c0705ee ^

3399650 ^

439e15d ^




439e15d ^


0053620 ^

439e15d ^
adaa28a ^
bf35794 ^
adaa28a ^








0f3acce ^
adaa28a ^







0464e42 ^
adaa28a ^


0f3acce ^
adaa28a ^

adaa28a ^

3af6434 ^

ee31e38 ^
a73a882 ^
ee31e38 ^
a73a882 ^
adaa28a ^

b6ad663 ^












1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418