//Name: Edward Mingyu Liu, Jack Zhiyan Jiang //Final Project //Date: 12/04/2023 window.onload = function init() { const canvas = document.getElementById("gl-canvas"); const gl = WebGLUtils.setupWebGL(canvas); if (!gl) { alert("WebGL isn't available"); } gl.viewport(0, 0, canvas.width, canvas.height); gl.clearColor(1.0, 1.0, 1.0, 1.0); gl.enable(gl.DEPTH_TEST); // Load shaders const program = initShaders(gl, "vertex-shader", "fragment-shader"); gl.useProgram(program); // Data for a simple textured sphere const earthData = sphere(10); const earthVertices = flatten(earthData[0]); const earthTexCoords = flatten(earthData[1]); // Load the data into the GPU const earthVBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, earthVBuffer); gl.bufferData(gl.ARRAY_BUFFER, earthVertices, gl.STATIC_DRAW); const vPosition = gl.getAttribLocation(program, "vPosition"); gl.vertexAttribPointer(vPosition, 4, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vPosition); const earthTBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, earthTBuffer); gl.bufferData(gl.ARRAY_BUFFER, earthTexCoords, gl.STATIC_DRAW); const vTexCoord = gl.getAttribLocation(program, "vTexCoord"); gl.vertexAttribPointer(vTexCoord, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vTexCoord); // Load and configure the earthTexture const earthTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, earthTexture); configureTexture(gl, earthTexture, "earth.jpg"); // Moon Data const moonData = sphere(6); const moonVertices = flatten(moonData[0]); const moonTexCoords = flatten(moonData[1]); // Load Moon data into GPU const moonVBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, moonVBuffer); gl.bufferData(gl.ARRAY_BUFFER, moonVertices, gl.STATIC_DRAW); const moonTBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, moonTBuffer); gl.bufferData(gl.ARRAY_BUFFER, moonTexCoords, gl.STATIC_DRAW); // Moon Texture const moonTexture = gl.createTexture(); configureTexture(gl, moonTexture, "moon.jpg"); const modelViewMatrixLoc = gl.getUniformLocation(program, "modelViewMatrix"); const projectionMatrixLoc = gl.getUniformLocation(program, "projectionMatrix"); let eye = vec3(0.0, 0.0, -9.0); const at = vec3(0.0, 0.0, 0.0); const up = vec3(0.0, 1.0, 0.0); let modelViewMatrix = lookAt(eye, at, up); const projectionMatrix = perspective(45.0, canvas.width/canvas.height, 0.1, 20.0); gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix)); gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix)); let earthTheta = 0.0; let moonTheta = 0.0; document.getElementById('cameraY').addEventListener('input', updateCamera); document.getElementById('cameraZ').addEventListener('input', updateCamera); window.addEventListener('resize', resizeCanvas, false); resizeCanvas(); // Initial resize document.getElementById('topViewButton').addEventListener('click', function() { modelViewMatrix = rotateX(90); // Rotate the scene to view from the top modelViewMatrix = mult(modelViewMatrix, translate(0, -5, 0)); updateModelViewMatrix(); // Reset the sliders document.getElementById('cameraY').value = 0; document.getElementById('cameraZ').value = -9; document.getElementById('cameraYValue').innerText = 0; document.getElementById('cameraZValue').innerText = -9; }); document.getElementById('originalViewButton').addEventListener('click', function() { resetCamera(); }); // Camera controls function updateCamera() { let y = parseFloat(document.getElementById('cameraY').value); let z = parseFloat(document.getElementById('cameraZ').value); document.getElementById('cameraYValue').innerText = y.toFixed(1); document.getElementById('cameraZValue').innerText = z.toFixed(1); eye = vec3(0.0, y, z); modelViewMatrix = lookAt(eye, at, up); updateModelViewMatrix(); gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix)); } // Function to resize the canvas function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; gl.viewport(0, 0, canvas.width, canvas.height); // Update the projection matrix const aspect = canvas.width / canvas.height; const projectionMatrix = perspective(45.0, aspect, 0.1, 20.0); gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix)); } function rotateX(theta) { const radians = (theta * Math.PI) / 180.0; return mat4( 1, 0, 0, 0, 0, Math.cos(radians), -Math.sin(radians), 0, 0, Math.sin(radians), Math.cos(radians), 0, 0, 0, 0, 1 ); } function resetCamera() { eye = vec3(0.0, 0.0, -5.0); // Reset the eye position to the initial state modelViewMatrix = lookAt(eye, at, up); // Reset the model-view matrix updateModelViewMatrix(); // Reset the sliders document.getElementById('cameraY').value = 0; document.getElementById('cameraZ').value = -9; document.getElementById('cameraYValue').innerText = 0; document.getElementById('cameraZValue').innerText = -9; } function updateModelViewMatrix() { gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix)); } function render() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Rotate Earth earthTheta += 360*0.01; // Earth rotates 360 degrees every 24 "time units" let mv = mult(modelViewMatrix, rotateY(earthTheta)); gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(mv)); // Draw Earth gl.bindBuffer(gl.ARRAY_BUFFER, earthVBuffer); gl.vertexAttribPointer(vPosition, 4, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vPosition); gl.bindBuffer(gl.ARRAY_BUFFER, earthTBuffer); gl.vertexAttribPointer(vTexCoord, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vTexCoord); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, earthTexture); gl.uniform1i(gl.getUniformLocation(program, "texture"), 0); gl.drawArrays(gl.TRIANGLES, 0, earthData[0].length); // Rotate and translate moon moonTheta += (360/27.3)*0.01; // Moon orbits 360 degrees every 27.3 "time units" const moonDistance = 2.0; const moonScale = 0.25; const moonOrbit = mult(rotateY(moonTheta), translate(moonDistance, 0, 0)); const moonRotation = rotateY(moonTheta); // Synchronous rotation with orbit const moonSelfRotation = rotateY(moonTheta); // Moon's self rotation const moonTransform = mult(mult(moonOrbit, moonRotation), scalem(moonScale, moonScale, moonScale)); const finalMoonTransform = mult(moonTransform, moonSelfRotation); // Combine orbit and self rotation mv = mult(modelViewMatrix, finalMoonTransform); gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(mv)); // Draw Moon gl.bindBuffer(gl.ARRAY_BUFFER, moonVBuffer); gl.vertexAttribPointer(vPosition, 4, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, moonTBuffer); gl.vertexAttribPointer(vTexCoord, 2, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, moonTexture); gl.uniform1i(gl.getUniformLocation(program, "texture"), 1); gl.drawArrays(gl.TRIANGLES, 0, moonData[0].length); requestAnimationFrame(render); } render(); }; function sphere(numSubdivisions) { let va = vec4(0.0, 0.0, -1.0, 1); let vb = vec4(0.0, 0.942809, 0.333333, 1); let vc = vec4(-0.816497, -0.471405, 0.333333, 1); let vd = vec4(0.816497, -0.471405, 0.333333, 1); let vertices = []; let texCoords = []; function tetrahedron(a, b, c, d, n) { divideTriangle(a, b, c, n); divideTriangle(d, c, b, n); divideTriangle(a, d, b, n); divideTriangle(a, c, d, n); } function divideTriangle(a, b, c, count) { if (count > 0) { let ab = mix(a, b, 0.5); let ac = mix(a, c, 0.5); let bc = mix(b, c, 0.5); ab = normalize(ab, true); ac = normalize(ac, true); bc = normalize(bc, true); divideTriangle(a, ab, ac, count - 1); divideTriangle(ab, b, bc, count - 1); divideTriangle(bc, c, ac, count - 1); divideTriangle(ab, bc, ac, count - 1); } else { triangle(a, b, c); } } function triangle(a, b, c) { vertices.push(a); vertices.push(b); vertices.push(c); // Texture coordinates for the sphere texCoords.push([0.5 + Math.atan2(a[2], a[0]) / (2 * Math.PI), 0.5 - Math.asin(a[1]) / Math.PI]); texCoords.push([0.5 + Math.atan2(b[2], b[0]) / (2 * Math.PI), 0.5 - Math.asin(b[1]) / Math.PI]); texCoords.push([0.5 + Math.atan2(c[2], c[0]) / (2 * Math.PI), 0.5 - Math.asin(c[1]) / Math.PI]); } tetrahedron(va, vb, vc, vd, numSubdivisions); return [vertices, texCoords]; } function rotateY(theta) { const radians = (theta * Math.PI) / 180.0; return mat4( Math.cos(radians), 0, Math.sin(radians), 0, 0, 1, 0, 0, -Math.sin(radians), 0, Math.cos(radians), 0, 0, 0, 0, 1 ); } function configureTexture(gl, texture, imageUrl) { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255])); // white const image = new Image(); image.onload = function () { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.generateMipmap(gl.TEXTURE_2D); }; image.src = imageUrl; }