about summary refs log blame commit diff stats
path: root/html/rogue/js/world.js
blob: 6213f42d06267b2e1a9883771489bac0b7762fa3 (plain) (tree)
1
2
3
4
5
6
7
8


                            



                                         
                                       


                                         

  

                                                   
            
                    
                   
                    
                         

                               

            
                    
                   
                    
                         

                               


     







                         
                                               
                             
      









                                                    






                         




















                                                
                             


     





















                          






                           

                              





                      

                              





                      

                              





                      

                              





                      

                               






                      

                               
                     


     
                                                  













                                                     
                                        














                                                     
                                          








                                                     
                                          




























































































                                                     























                                                
















                                                     
                                          








                                                     
                                        



































































































                                                                                                 
         



                 
 





                                               









                                                                      




                                        
























































                                                                                                           
                                
                                               


                                                                            

                                                        

                                                           
                      

                               



                              

      









                         
                                                 
                         








                                         
                                             
 





                                                           
        





                                                    
            


                                                       
            

                                                                                 
            














                                                                                                            
 














                                                                                                            
                

                                                                  
                





                                                                                                                        
                



                                                                               
                

                                                                                               

                                
                                         
                           

                                                           




                             






























                                                                                                                         
                                
                                           
                           

                                                                                





                             

                                



                               













                                                     

  







                                                 

                                                                            







                               
                                            








                       

                                                                                       
                        
































                                                                                                      
         

                        

      


                                                                                
    









                                             










                                                    

























                                                         


                                                    
                                                                          





















                                                                     






                                                                            






                                                                             
                                                     
















                                                                                    



                      

  

























































                                                                  
// 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: 130,
        trunkHeight: 60,
        canopyRadius: 35,
        leafClusters: 5,
        canopyColor: '#33aa22', // Bright green
        trunkColor: '#664422'
    },
    MEDIUM: {
        type: 'maple',
        width: 130,
        height: 160,
        trunkHeight: 70,
        canopyRadius: 45,
        leafClusters: 6,
        canopyColor: '#228833', // Deep forest green
        trunkColor: '#553311'
    },
    LARGE: {
        type: 'maple',
        width: 160,
        height: 200,
        trunkHeight: 85,
        canopyRadius: 55,
        leafClusters: 7,
        canopyColor: '#115522', // Dark green
        trunkColor: '#553311'
    },
    BRIGHT: {
        type: 'maple',
        width: 140,
        height: 180,
        trunkHeight: 75,
        canopyRadius: 50,
        leafClusters: 6,
        canopyColor: '#44bb33', // Vibrant green
        trunkColor: '#664422'
    },
    SAGE: {
        type: 'maple',
        width: 150,
        height: 190,
        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,
        blades: 5,
        color: '#33aa55',
        shadowColor: '#229944'
    },
    SHORT: {
        type: 'grass',
        width: 20,
        height: 25,
        blades: 4,
        color: '#33bb66',
        shadowColor: '#22aa55'
    },
    GOLDEN_TALL: {
        type: 'grass',
        width: 30,
        height: 40,
        blades: 5,
        color: '#eebb33',
        shadowColor: '#cc9922'
    },
    GOLDEN_SHORT: {
        type: 'grass',
        width: 20,
        height: 25,
        blades: 4,
        color: '#ffcc44',
        shadowColor: '#ddaa33'
    },
    BLUE_TALL: {
        type: 'grass',
        width: 30,
        height: 40,
        blades: 5,
        color: '#44aaff',
        shadowColor: '#2299ff',
        glowing: true
    },
    BLUE_SHORT: {
        type: 'grass',
        width: 20,
        height: 25,
        blades: 4,
        color: '#55bbff',
        shadowColor: '#33aaff',
        glowing: true
    }
};

// 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();
};

// Update renderFirTree function
const renderFirTree = (ctx, tree, groundY) => {
    const { x, config } = tree;
    const { width, height, canopyOffset, trunkColor, canopyColor } = config;
    
    // Calculate trunk dimensions - make trunk narrower
    const trunkWidth = width/4; // Changed from /3 to /4
    const trunkHeight = height - (height - canopyOffset)/2;
    
    // Draw trunk base
    ctx.fillStyle = trunkColor;
    ctx.fillRect(
        x - trunkWidth/2,
        groundY - trunkHeight,
        trunkWidth,
        trunkHeight
    );
    
    // Add trunk hatching
    addHatchingPattern(
        ctx,
        x,
        groundY,
        trunkWidth,
        trunkHeight,
        trunkColor
    );

    // Define a range of green colors for texture
    const greenColors = [
        '#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
    ].filter(color => color !== canopyColor);

    const drawFeatheredTree = (baseWidth, baseY, tipY) => {
        // Enhanced feathering parameters
        const rowCount = 40;
        const feathersPerRow = 24;
        const featherLength = baseWidth * 0.3;
        const featherWidth = 2;
        
        ctx.lineWidth = featherWidth;
        
        // Draw rows of feathers from bottom to top
        for (let row = 0; row < rowCount; row++) {
            const t = row / (rowCount - 1);
            const rowY = baseY - (baseY - tipY) * t;
            
            // More aggressive width reduction near top
            const taper = Math.pow(1 - t, 0.8);
            const rowWidth = baseWidth * taper;
            
            // Reduce feather count more aggressively near top
            const featherCount = Math.max(2, Math.floor(feathersPerRow * taper));
            
            // Skip drawing if width is too small (creates sharp point)
            if (rowWidth < 2) continue;
            
            // Draw center column of needles first
            const centerX = x;
            // Draw a needle pointing left
            ctx.strokeStyle = greenColors[Math.floor(seededRandom(centerX - 1, rowY) * greenColors.length)];
            ctx.beginPath();
            ctx.moveTo(centerX, rowY);
            const leftAngle = Math.PI + (Math.PI * 0.05 * seededRandom(centerX, rowY) - Math.PI * 0.025);
            ctx.lineTo(
                centerX + Math.cos(leftAngle) * featherLength * taper,
                rowY + Math.sin(leftAngle) * featherLength * taper
            );
            ctx.stroke();

            // Draw a needle pointing right
            ctx.strokeStyle = greenColors[Math.floor(seededRandom(centerX + 1, rowY) * greenColors.length)];
            ctx.beginPath();
            ctx.moveTo(centerX, rowY);
            const rightAngle = 0 + (Math.PI * 0.05 * seededRandom(centerX, rowY) - Math.PI * 0.025);
            ctx.lineTo(
                centerX + Math.cos(rightAngle) * featherLength * taper,
                rowY + Math.sin(rightAngle) * featherLength * taper
            );
            ctx.stroke();
            
            // Draw regular feathers for this row
            for (let i = 0; i < featherCount; i++) {
                const featherT = i / (featherCount - 1);
                const startX = x - rowWidth/2 + rowWidth * featherT;
                
                // Skip the center point as we've already drawn it
                if (Math.abs(startX - x) < 1) continue;
                
                // Calculate angle to point directly away from center with slight upward tilt
                const relativeX = (startX - x) / (rowWidth/2);
                const baseAngle = relativeX < 0 ? Math.PI : 0;
                const upwardTilt = Math.PI * 0.1 * t;
                const angleVariation = Math.PI * 0.05;
                const angle = baseAngle + (angleVariation * seededRandom(startX, rowY) - angleVariation/2) - upwardTilt;
                
                // Reduce length near top but keep needles longer
                const lengthMultiplier = 0.8 + (1 - t) * 0.3;
                const finalLength = featherLength * lengthMultiplier * taper * 
                    (0.9 + seededRandom(startX * rowY, rowY) * 0.2);
                
                const colorIndex = Math.floor(seededRandom(startX, rowY) * greenColors.length);
                ctx.strokeStyle = greenColors[colorIndex];
                
                ctx.beginPath();
                ctx.moveTo(startX, rowY);
                ctx.lineTo(
                    startX + Math.cos(angle) * finalLength,
                    rowY + Math.sin(angle) * finalLength
                );
                ctx.stroke();
            }
        }
        
        // Add extra feathers at the edges with upward tilt
        const edgeFeatherCount = 40;
        for (let i = 0; i < edgeFeatherCount; i++) {
            const t = i / (edgeFeatherCount - 1);
            const taper = Math.pow(1 - t, 0.8);
            
            // Left edge
            const leftX = x - baseWidth/2 * taper + (baseWidth/2 * taper) * t;
            const leftY = baseY - (baseY - tipY) * t;
            const leftUpwardTilt = Math.PI * 0.1 * t;
            const leftAngle = Math.PI + (Math.PI * 0.05 * seededRandom(leftX, leftY) - Math.PI * 0.025) - leftUpwardTilt;
            
            if (taper > 0.1) {
                ctx.strokeStyle = greenColors[Math.floor(seededRandom(leftX, leftY) * greenColors.length)];
                ctx.beginPath();
                ctx.moveTo(leftX, leftY);
                ctx.lineTo(
                    leftX + Math.cos(leftAngle) * featherLength * taper * 1.2,
                    leftY + Math.sin(leftAngle) * featherLength * taper * 1.2
                );
                ctx.stroke();
            }
            
            // Right edge
            const rightX = x + baseWidth/2 * taper - (baseWidth/2 * taper) * t;
            const rightY = baseY - (baseY - tipY) * t;
            const rightUpwardTilt = Math.PI * 0.1 * t;
            const rightAngle = 0 + (Math.PI * 0.05 * seededRandom(rightX, rightY) - Math.PI * 0.025) - rightUpwardTilt;
            
            if (taper > 0.1) {
                ctx.strokeStyle = greenColors[Math.floor(seededRandom(rightX, rightY) * greenColors.length)];
                ctx.beginPath();
                ctx.moveTo(rightX, rightY);
                ctx.lineTo(
                    rightX + Math.cos(rightAngle) * featherLength * taper * 1.2,
                    rightY + Math.sin(rightAngle) * featherLength * taper * 1.2
                );
                ctx.stroke();
            }
        }
    };

    // Draw feathered tree shape
    drawFeatheredTree(
        width * 1.2,
        groundY - canopyOffset,
        groundY - height * 1.1
    );
};

// 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, blades, color, shadowColor, glowing } = config;
    
    // Get or initialize grass state
    const stateKey = `grass_${x}`;
    if (!window.gameState.world.grassStates[stateKey]) {
        window.gameState.world.grassStates[stateKey] = {
            rustleAmount: 0,
            rustleDecay: 0.95
        };
    }
    const state = window.gameState.world.grassStates[stateKey];
    
    // Check for player interaction
    const player = window.gameState.player;
    const interactionDistance = width;
    const distanceToPlayer = Math.abs(player.x + player.width/2 - x);
    
    if (distanceToPlayer < interactionDistance) {
        state.rustleAmount = Math.min(1, state.rustleAmount + 0.3);
    } else {
        state.rustleAmount *= state.rustleDecay;
    }

    // Add glow effect for blue grass
    if (glowing) {
        ctx.save();
        ctx.shadowColor = color;
        ctx.shadowBlur = 10 + Math.sin(time/500) * 3; // Pulsing glow effect
    }

    // Draw each blade of grass
    for (let i = 0; i < blades; i++) {
        const bladeX = x + (i - blades/2) * (width/blades);
        const bladeWidth = width / blades * 0.7;
        
        // Calculate blade curve based on rustle and time
        const rustleOffset = Math.sin(time/300 + i) * 5 * state.rustleAmount;
        const baseWave = Math.sin(time/1000 + i) * 2;
        
        // Draw blade
        ctx.fillStyle = i % 2 === 0 ? color : shadowColor;
        ctx.beginPath();
        ctx.moveTo(bladeX, groundY);
        
        // Control points for quadratic curve
        const cp1x = bladeX + rustleOffset + baseWave;
        const cp1y = groundY - height/2;
        const cp2x = bladeX + rustleOffset * 1.5 + baseWave;
        const cp2y = groundY - height;
        
        // Draw curved blade
        ctx.quadraticCurveTo(cp1x, cp1y, cp2x, cp2y);
        ctx.quadraticCurveTo(cp1x + bladeWidth, cp1y, bladeX + bladeWidth, groundY);
        ctx.fill();
    }

    if (glowing) {
        ctx.restore();
    }
};

// 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 };
};