diff options
Diffstat (limited to 'html/rogue/js/world.js')
-rw-r--r-- | html/rogue/js/world.js | 1054 |
1 files changed, 0 insertions, 1054 deletions
diff --git a/html/rogue/js/world.js b/html/rogue/js/world.js deleted file mode 100644 index 2b2529a..0000000 --- a/html/rogue/js/world.js +++ /dev/null @@ -1,1054 +0,0 @@ -// 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 - ); -}; |