Loading farm scene...
setupScene() { this.gl.enable(this.gl.DEPTH_TEST); this.gl.enable(this.gl.BLEND); this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); this.time = 0; this.animals = []; this.waterOffset = 0; // Create some animals for (let i = 0; i < 8; i++) { this.animals.push({ x: Math.random() * 800 - 400, z: Math.random() * 600 - 300, speed: 0.5 + Math.random() * 1.5, direction: Math.random() * Math.PI * 2, type: Math.floor(Math.random() * 3), // 0: cow, 1: sheep, 2: chicken size: 0.8 + Math.random() * 0.4 }); } } createShaders() { const vertexShaderSource = `#version 300 es in vec3 position; in vec3 color; uniform mat4 uProjection; uniform mat4 uView; uniform float uTime; out vec3 vColor; out vec3 vPosition; void main() { vColor = color; vPosition = position; vec3 pos = position; // Add some gentle water movement if (pos.y < -0.1) { pos.y += sin(pos.x * 0.5 + uTime * 2.0) * 0.02; pos.y += cos(pos.z * 0.3 + uTime * 1.5) * 0.015; } gl_Position = uProjection * uView * vec4(pos, 1.0); } `; const fragmentShaderSource = `#version 300 es precision mediump float; in vec3 vColor; in vec3 vPosition; uniform float uTime; out vec4 fragColor; void main() { vec3 color = vColor; // Add some water shimmer if (vPosition.y < -0.05) { float shimmer = sin(vPosition.x * 10.0 + uTime * 5.0) * 0.1; shimmer += cos(vPosition.z * 8.0 + uTime * 3.0) * 0.05; color += vec3(shimmer * 0.3, shimmer * 0.5, shimmer * 0.7); } // Add atmospheric perspective float distance = length(vPosition); float fog = exp(-distance * 0.01); color = mix(vec3(0.7, 0.8, 0.9), color, fog); fragColor = vec4(color, 1.0); } `; this.program = this.createProgram(vertexShaderSource, fragmentShaderSource); } createProgram(vertexSource, fragmentSource) { const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER); this.gl.shaderSource(vertexShader, vertexSource); this.gl.compileShader(vertexShader); const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER); this.gl.shaderSource(fragmentShader, fragmentSource); this.gl.compileShader(fragmentShader); const program = this.gl.createProgram(); this.gl.attachShader(program, vertexShader); this.gl.attachShader(program, fragmentShader); this.gl.linkProgram(program); return program; } createGeometry() { // Create terrain, river, trees, and Angus const vertices = []; const colors = []; // Ground plane (grass) for (let x = -500; x <= 500; x += 50) { for (let z = -300; z <= 300; z += 50) { const grassGreen = [0.2 + Math.random() * 0.3, 0.6 + Math.random() * 0.2, 0.1]; // Quad vertices vertices.push(x, 0, z, x + 50, 0, z, x, 0, z + 50); vertices.push(x + 50, 0, z, x + 50, 0, z + 50, x, 0, z + 50); // Colors for (let i = 0; i < 6; i++) { colors.push(...grassGreen); } } } // River (blue strip) for (let z = -300; z <= 300; z += 20) { const riverBlue = [0.2, 0.4 + Math.random() * 0.2, 0.8]; vertices.push(-100, -0.1, z, 100, -0.1, z, -100, -0.1, z + 20); vertices.push(100, -0.1, z, 100, -0.1, z + 20, -100, -0.1, z + 20); for (let i = 0; i < 6; i++) { colors.push(...riverBlue); } } // Trees (simple green pyramids) const treePositions = [ [-200, 50], [200, 80], [-300, -100], [250, -50], [-150, 150], [180, 120], [-250, -200], [300, -150] ]; treePositions.forEach(([x, z]) => { // Tree trunk (brown) const trunkBrown = [0.4, 0.2, 0.1]; vertices.push(x - 5, 0, z - 5, x + 5, 0, z - 5, x - 5, 20, z - 5); vertices.push(x + 5, 0, z - 5, x + 5, 20, z - 5, x - 5, 20, z - 5); for (let i = 0; i < 6; i++) { colors.push(...trunkBrown); } // Tree leaves (green pyramid) const leafGreen = [0.1, 0.5, 0.1]; vertices.push(x - 20, 20, z - 20, x + 20, 20, z - 20, x, 50, z); vertices.push(x + 20, 20, z - 20, x + 20, 20, z + 20, x, 50, z); vertices.push(x + 20, 20, z + 20, x - 20, 20, z + 20, x, 50, z); vertices.push(x - 20, 20, z + 20, x - 20, 20, z - 20, x, 50, z); for (let i = 0; i < 12; i++) { colors.push(...leafGreen); } }); // Angus (red-haired fisherman by the river) const angusX = 50, angusZ = -250; // Angus body (simple shapes) const skinColor = [0.9, 0.7, 0.6]; const redHair = [0.8, 0.3, 0.1]; // Red hair! const clothingBlue = [0.2, 0.3, 0.6]; // Head (with red hair) vertices.push(angusX - 8, 35, angusZ - 8, angusX + 8, 35, angusZ - 8, angusX - 8, 50, angusZ - 8); vertices.push(angusX + 8, 35, angusZ - 8, angusX + 8, 50, angusZ - 8, angusX - 8, 50, angusZ - 8); for (let i = 0; i < 6; i++) colors.push(...redHair); // Face vertices.push(angusX - 6, 35, angusZ - 6, angusX + 6, 35, angusZ - 6, angusX - 6, 45, angusZ - 6); vertices.push(angusX + 6, 35, angusZ - 6, angusX + 6, 45, angusZ - 6, angusX - 6, 45, angusZ - 6); for (let i = 0; i < 6; i++) colors.push(...skinColor); // Body vertices.push(angusX - 10, 5, angusZ - 5, angusX + 10, 5, angusZ - 5, angusX - 10, 35, angusZ - 5); vertices.push(angusX + 10, 5, angusZ - 5, angusX + 10, 35, angusZ - 5, angusX - 10, 35, angusZ - 5); for (let i = 0; i < 6; i++) colors.push(...clothingBlue); // Fishing rod const rodBrown = [0.4, 0.2, 0.1]; vertices.push(angusX + 15, 25, angusZ, angusX + 40, 30, angusZ - 50, angusX + 15, 27, angusZ); for (let i = 0; i < 3; i++) colors.push(...rodBrown); this.vertexBuffer = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(vertices), this.gl.STATIC_DRAW); this.colorBuffer = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer); this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(colors), this.gl.STATIC_DRAW); this.vertexCount = vertices.length / 3; } setupAnimationLoop() { const animate = () => { this.time += 0.016; this.render(); requestAnimationFrame(animate); }; animate(); } render() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); this.gl.clearColor(0.7, 0.8, 0.9, 1.0); this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); this.gl.useProgram(this.program); // Set up camera const aspect = this.canvas.width / this.canvas.height; const projection = this.perspective(45 * Math.PI / 180, aspect, 0.1, 1000); const cameraX = Math.sin(this.time * 0.2) * 300; const cameraZ = Math.cos(this.time * 0.2) * 300; const view = this.lookAt([cameraX, 100, cameraZ], [0, 0, 0], [0, 1, 0]); const projectionLoc = this.gl.getUniformLocation(this.program, 'uProjection'); const viewLoc = this.gl.getUniformLocation(this.program, 'uView'); const timeLoc = this.gl.getUniformLocation(this.program, 'uTime'); this.gl.uniformMatrix4fv(projectionLoc, false, projection); this.gl.uniformMatrix4fv(viewLoc, false, view); this.gl.uniform1f(timeLoc, this.time); // Bind vertex data const positionLoc = this.gl.getAttribLocation(this.program, 'position'); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); this.gl.enableVertexAttribArray(positionLoc); this.gl.vertexAttribPointer(positionLoc, 3, this.gl.FLOAT, false, 0, 0); const colorLoc = this.gl.getAttribLocation(this.program, 'color'); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer); this.gl.enableVertexAttribArray(colorLoc); this.gl.vertexAttribPointer(colorLoc, 3, this.gl.FLOAT, false, 0, 0); this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount); } perspective(fovy, aspect, near, far) { const f = 1.0 / Math.tan(fovy / 2); return new Float32Array([ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) / (near - far), (2 * far * near) / (near - far), 0, 0, -1, 0 ]); } lookAt(eye, center, up) { const f = this.normalize(this.subtract(center, eye)); const s = this.normalize(this.cross(f, up)); const u = this.cross(s, f); return new Float32Array([ s[0], u[0], -f[0], 0, s[1], u[1], -f[1], 0, s[2], u[2], -f[2], 0, -this.dot(s, eye), -this.dot(u, eye), this.dot(f, eye), 1 ]); } normalize(v) { const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); return [v[0] / len, v[1] / len, v[2] / len]; } subtract(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; } cross(a, b) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; } dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } } // Initialize the farm scene window.addEventListener('load', () => { const canvas = document.getElementById('farmCanvas'); new FarmScene(canvas); }); // Handle window resize window.addEventListener('resize', () => { const canvas = document.getElementById('farmCanvas'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; });