diff --git a/skycraft/draw.ts b/skycraft/draw.ts new file mode 100644 index 0000000..b0bc6ff --- /dev/null +++ b/skycraft/draw.ts @@ -0,0 +1,335 @@ +import {makeBufferFromFaces } from '../geometry'; +import { loadTexture, makeProgram } from '../gl'; +import * as linalg from './linalg'; +import * as se3 from '../se3'; +import { makeOrbitObject } from './orbit'; + +const VSHADER = ` +attribute vec3 aPosition; +attribute vec3 aNormal; +attribute vec2 aTextureCoord; + +uniform mat4 uProjection; +uniform mat4 uModel; +uniform mat4 uView; +uniform vec3 uLightDirection; +uniform float uAmbiantLight; +uniform vec3 uGlowColor; + +varying highp vec2 vTextureCoord; +varying lowp vec3 vLighting; +varying lowp vec3 vRay; +varying lowp vec3 vLightDir; +varying lowp vec3 vNormal; + +highp mat3 transpose(in highp mat3 inmat) { + highp vec3 x = inmat[0]; + highp vec3 y = inmat[1]; + highp vec3 z = inmat[2]; + + return mat3( + vec3(x.x, y.x, z.x), + vec3(x.y, y.y, z.y), + vec3(x.z, y.z, z.z) + ); +} + +void main() { + highp mat4 modelview = uView * uModel; + + gl_Position = uProjection * modelview * vec4(aPosition, 1.0); + + lowp vec3 normal = mat3(uModel) * aNormal; + lowp float diffuseAmount = max(dot(-uLightDirection, normal), 0.0); + lowp vec3 ambiant = uAmbiantLight * vec3(1.0, 1.0, 0.9); + vLighting = ambiant + vec3(1.0, 1.0, 1.0) * diffuseAmount + uGlowColor; + + vTextureCoord = aTextureCoord; + + lowp vec3 camPos = -transpose(mat3(uView))*(uView * vec4(0.0, 0.0, 0.0, 1.0)).xyz; + vRay = -normalize((uModel * vec4(aPosition, 1.0)).xyz - camPos); + vLightDir = -uLightDirection; + vNormal = normal; +} +`; + +const FSHADER = ` +uniform sampler2D uSampler; + +varying highp vec2 vTextureCoord; +varying lowp vec3 vLighting; +varying lowp vec3 vRay; +varying lowp vec3 vLightDir; +varying lowp vec3 vNormal; + +void main() { + highp vec4 color = texture2D(uSampler, vTextureCoord); + if (color.a < 0.1) { + discard; + } + lowp vec3 specularDir = 2.0 * dot(vLightDir, vNormal) * vNormal - vLightDir; + lowp float specularAmount = smoothstep(0.92, 1.0, dot(vRay, specularDir)); + lowp vec3 specular = 0.8 * specularAmount * vec3(1.0, 1.0, 0.8); + gl_FragColor = vec4(vLighting * color.rgb + specular, color.a); +} +`; + +export async function initWorldGl(gl: WebGLRenderingContext) { + const program = makeProgram(gl, VSHADER, FSHADER); + const texture = await loadTexture(gl, 'texture.png'); + + // load those ahead of time + const viewLoc = gl.getUniformLocation(program, 'uView'); + const modelLoc = gl.getUniformLocation(program, 'uModel'); + const projLoc = gl.getUniformLocation(program, 'uProjection'); + const samplerLoc = gl.getUniformLocation(program, 'uSampler'); + const lightDirectionLoc = gl.getUniformLocation(program, 'uLightDirection'); + const ambiantLoc = gl.getUniformLocation(program, 'uAmbiantLight'); + const glowColorLoc = gl.getUniformLocation(program, 'uGlowColor'); + + const positionLoc = gl.getAttribLocation(program, 'aPosition'); + const normalLoc = gl.getAttribLocation(program, 'aNormal'); + const textureLoc = gl.getAttribLocation(program, 'aTextureCoord'); + + const setupScene = (sceneParams) => { + const { + projectionMatrix, + viewMatrix, + ambiantLightAmount, + } = sceneParams; + + gl.useProgram(program); + + gl.uniformMatrix4fv(projLoc, false, new Float32Array(projectionMatrix)); + gl.uniformMatrix4fv(viewLoc, false, new Float32Array(viewMatrix)); + gl.uniform1f(ambiantLoc, ambiantLightAmount); + + // doing this here because it's the same for all world stuff + gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.identity())); + gl.uniform1i(samplerLoc, 0); + + gl.enableVertexAttribArray(positionLoc); + gl.enableVertexAttribArray(normalLoc); + gl.enableVertexAttribArray(textureLoc); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture); + }; + + const drawObject = (objectParams) => { + const { + position, + orientation, + glBuffer, + numVertices, + lightDirection, + glowColor, + } = objectParams; + + gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.product( + se3.translation(...position), orientation))); + gl.uniform3fv(lightDirectionLoc, lightDirection); + gl.uniform3fv(glowColorLoc, glowColor); + + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer); + + gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0); + gl.vertexAttribPointer(normalLoc, 3, gl.BYTE, true, 20, 12); + gl.vertexAttribPointer(textureLoc, 2, gl.UNSIGNED_SHORT, true, 20, 16); + + gl.drawArrays(gl.TRIANGLES, 0, numVertices); + }; + + return { + setupScene, + drawObject, + }; +} + +const ORBIT_VSHADER = ` +attribute vec3 aPosition; +attribute vec2 aValue; + +uniform mat4 uProjection; +uniform mat4 uModel; +uniform mat4 uView; + +varying lowp vec2 vCoords; + +void main() { + highp mat4 modelview = uView * uModel; + gl_Position = uProjection * modelview * vec4(aPosition, 1.0); + + vCoords = aValue; +} +`; + +const ORBIT_FSHADER = ` +varying lowp vec2 vCoords; + +void main() { + lowp float x = vCoords.x; + lowp float y = vCoords.y; + + lowp float f = sqrt(x * x + y * y); + + if (f > 1.00) { + discard; + } else if (f < 0.98) { + discard; + } + gl_FragColor = vec4(1, .5, 0, 0.5); +} +`; + +export function getOrbitDrawContext(gl: WebGLRenderingContext) { + const program = makeProgram(gl, ORBIT_VSHADER, ORBIT_FSHADER); + + // load those ahead of time + const viewLoc = gl.getUniformLocation(program, 'uView'); + const modelLoc = gl.getUniformLocation(program, 'uModel'); + const projLoc = gl.getUniformLocation(program, 'uProjection'); + + const positionLoc = gl.getAttribLocation(program, 'aPosition'); + const valueLoc = gl.getAttribLocation(program, 'aValue'); + + const setupScene = (sceneParams) => { + const { + projectionMatrix, + viewMatrix, + } = sceneParams; + + gl.useProgram(program); + + gl.uniformMatrix4fv(projLoc, false, new Float32Array(projectionMatrix)); + gl.uniformMatrix4fv(viewLoc, false, new Float32Array(viewMatrix)); + + // doing this here because it's the same for all world stuff + gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.identity())); + + gl.enableVertexAttribArray(positionLoc); + gl.enableVertexAttribArray(valueLoc); + }; + + const drawObject = (objectParams) => { + const { + position, + orientation, + value, + glBuffer, + } = objectParams; + + gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.product( + se3.translation(...position), orientation))); + + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer); + + gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0); + gl.vertexAttribPointer(valueLoc, 2, gl.FLOAT, false, 20, 12); + + gl.disable(gl.CULL_FACE); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + gl.enable(gl.CULL_FACE); + }; + + return { + setupScene, + drawObject, + }; +} + +function getObjects(context, body, parentPosition = undefined) { + const objects = []; + const {gl, glContext, player} = context; + const {position, orientation, glowColor} = body; + + if (body.glBuffer === undefined) { + body.glBuffer = makeBufferFromFaces(gl, body.geometry); + } + + objects.push({ + geometry: body.glBuffer, + orientation, + position, + glContext, + glowColor, + }); + if (parentPosition !== undefined) { + const orbitObject = makeOrbitObject(gl, context.orbitGlContext, body.orbit, parentPosition); + objects.push(orbitObject); + } else { + const shipOrientation = [ + se3.rotationOnly(player.tf), + se3.rotationOnly(context.camera.tf), + se3.rotxyz(-Math.PI / 2, Math.PI / 2, Math.PI / 2), + ].reduce(se3.product); + const shipPos = player.position; + objects.push({ + geometry: makeBufferFromFaces(gl, context.spaceship), + orientation: shipOrientation, + position: shipPos, + glContext, + }); + } + + if (body.children !== undefined) { + for (const child of body.children) { + objects.push(...getObjects(context, child, position)); + } + } + + return objects; +} + +function sunDirection(position: linalg.vec3) { + return linalg.scale(position, 1/linalg.norm(position)); +} + +export function draw(context) { + const {gl, camera, player, universe, orbit, orbitGlContext, orbitBody} = context; + const {skyColor, ambiantLight, projMatrix} = context; + const objects = getObjects(context, universe); + if (orbit !== undefined && orbit.excentricity < 1) { + objects.push(makeOrbitObject(gl, orbitGlContext, orbit, orbitBody.position)); + } + + gl.clearColor(...skyColor, 1.0); + gl.clearDepth(1.0); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + const viewMatrix = se3.inverse([ + player.tf, // player position & orientation + camera.tf, // camera orientation relative to player + se3.translation(0, 1, 4), // step back from the player + ].reduce(se3.product)); + let lastGlContext; + + for (const {position, orientation, geometry, glContext, glowColor} of objects) { + if (glContext !== lastGlContext) { + glContext.setupScene({ + projectionMatrix: projMatrix, + viewMatrix, + ambiantLightAmount: ambiantLight, + }); + } + + lastGlContext = glContext; + const lightDirection = sunDirection(position); + + glContext.drawObject({ + position, + orientation, + glBuffer: geometry.glBuffer, + numVertices: geometry.numVertices, + lightDirection, + glowColor: glowColor || [0, 0, 0], + }); + } +} \ No newline at end of file diff --git a/skycraft/index.ts b/skycraft/index.ts index 0953a7b..38a28e6 100644 --- a/skycraft/index.ts +++ b/skycraft/index.ts @@ -1,248 +1,16 @@ -import { makeFace, makeBufferFromFaces } from '../geometry'; -import { loadTexture, makeProgram } from '../gl'; +import { makeFace } from '../geometry'; + import * as linalg from './linalg'; import { loadObjModel } from './obj'; import * as se3 from '../se3'; -import { computeOrbit, findSoi, getCartesianState, makeOrbitObject, updateBodyPhysics } from './orbit'; +import { computeOrbit, findSoi, getCartesianState, updateBodyPhysics } from './orbit'; import { getBodyGeometry } from './chunk'; - -const VSHADER = ` -attribute vec3 aPosition; -attribute vec3 aNormal; -attribute vec2 aTextureCoord; - -uniform mat4 uProjection; -uniform mat4 uModel; -uniform mat4 uView; -uniform vec3 uLightDirection; -uniform float uAmbiantLight; -uniform vec3 uGlowColor; - -varying highp vec2 vTextureCoord; -varying lowp vec3 vLighting; -varying lowp vec3 vRay; -varying lowp vec3 vLightDir; -varying lowp vec3 vNormal; - -highp mat3 transpose(in highp mat3 inmat) { - highp vec3 x = inmat[0]; - highp vec3 y = inmat[1]; - highp vec3 z = inmat[2]; - - return mat3( - vec3(x.x, y.x, z.x), - vec3(x.y, y.y, z.y), - vec3(x.z, y.z, z.z) - ); -} - -void main() { - highp mat4 modelview = uView * uModel; - - gl_Position = uProjection * modelview * vec4(aPosition, 1.0); - - lowp vec3 normal = mat3(uModel) * aNormal; - lowp float diffuseAmount = max(dot(-uLightDirection, normal), 0.0); - lowp vec3 ambiant = uAmbiantLight * vec3(1.0, 1.0, 0.9); - vLighting = ambiant + vec3(1.0, 1.0, 1.0) * diffuseAmount + uGlowColor; - - vTextureCoord = aTextureCoord; - - lowp vec3 camPos = -transpose(mat3(uView))*(uView * vec4(0.0, 0.0, 0.0, 1.0)).xyz; - vRay = -normalize((uModel * vec4(aPosition, 1.0)).xyz - camPos); - vLightDir = -uLightDirection; - vNormal = normal; -} -`; - -const FSHADER = ` -uniform sampler2D uSampler; - -varying highp vec2 vTextureCoord; -varying lowp vec3 vLighting; -varying lowp vec3 vRay; -varying lowp vec3 vLightDir; -varying lowp vec3 vNormal; - -void main() { - highp vec4 color = texture2D(uSampler, vTextureCoord); - if (color.a < 0.1) { - discard; - } - lowp vec3 specularDir = 2.0 * dot(vLightDir, vNormal) * vNormal - vLightDir; - lowp float specularAmount = smoothstep(0.92, 1.0, dot(vRay, specularDir)); - lowp vec3 specular = 0.8 * specularAmount * vec3(1.0, 1.0, 0.8); - gl_FragColor = vec4(vLighting * color.rgb + specular, color.a); -} -`; +import { draw, getOrbitDrawContext, initWorldGl } from './draw'; const kEpoch = 0; -async function initWorldGl(gl: WebGLRenderingContext) { - const program = makeProgram(gl, VSHADER, FSHADER); - const texture = await loadTexture(gl, 'texture.png'); - - // load those ahead of time - const viewLoc = gl.getUniformLocation(program, 'uView'); - const modelLoc = gl.getUniformLocation(program, 'uModel'); - const projLoc = gl.getUniformLocation(program, 'uProjection'); - const samplerLoc = gl.getUniformLocation(program, 'uSampler'); - const lightDirectionLoc = gl.getUniformLocation(program, 'uLightDirection'); - const ambiantLoc = gl.getUniformLocation(program, 'uAmbiantLight'); - const glowColorLoc = gl.getUniformLocation(program, 'uGlowColor'); - - const positionLoc = gl.getAttribLocation(program, 'aPosition'); - const normalLoc = gl.getAttribLocation(program, 'aNormal'); - const textureLoc = gl.getAttribLocation(program, 'aTextureCoord'); - - const setupScene = (sceneParams) => { - const { - projectionMatrix, - viewMatrix, - ambiantLightAmount, - } = sceneParams; - - gl.useProgram(program); - - gl.uniformMatrix4fv(projLoc, false, new Float32Array(projectionMatrix)); - gl.uniformMatrix4fv(viewLoc, false, new Float32Array(viewMatrix)); - gl.uniform1f(ambiantLoc, ambiantLightAmount); - - // doing this here because it's the same for all world stuff - gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.identity())); - gl.uniform1i(samplerLoc, 0); - - gl.enableVertexAttribArray(positionLoc); - gl.enableVertexAttribArray(normalLoc); - gl.enableVertexAttribArray(textureLoc); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - }; - - const drawObject = (objectParams) => { - const { - position, - orientation, - glBuffer, - numVertices, - lightDirection, - glowColor, - } = objectParams; - - gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.product( - se3.translation(...position), orientation))); - gl.uniform3fv(lightDirectionLoc, lightDirection); - gl.uniform3fv(glowColorLoc, glowColor); - - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer); - - gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0); - gl.vertexAttribPointer(normalLoc, 3, gl.BYTE, true, 20, 12); - gl.vertexAttribPointer(textureLoc, 2, gl.UNSIGNED_SHORT, true, 20, 16); - - gl.drawArrays(gl.TRIANGLES, 0, numVertices); - }; - - return { - setupScene, - drawObject, - }; -} - -const ORBIT_VSHADER = ` -attribute vec3 aPosition; -attribute vec2 aValue; - -uniform mat4 uProjection; -uniform mat4 uModel; -uniform mat4 uView; - -varying lowp vec2 vCoords; - -void main() { - highp mat4 modelview = uView * uModel; - gl_Position = uProjection * modelview * vec4(aPosition, 1.0); - - vCoords = aValue; -} -`; - -const ORBIT_FSHADER = ` -varying lowp vec2 vCoords; - -void main() { - lowp float x = vCoords.x; - lowp float y = vCoords.y; - - lowp float f = sqrt(x * x + y * y); - - if (f > 1.00) { - discard; - } else if (f < 0.98) { - discard; - } - gl_FragColor = vec4(1, .5, 0, 0.5); -} -`; - -function getOrbitDrawContext(gl) { - const program = makeProgram(gl, ORBIT_VSHADER, ORBIT_FSHADER); - - // load those ahead of time - const viewLoc = gl.getUniformLocation(program, 'uView'); - const modelLoc = gl.getUniformLocation(program, 'uModel'); - const projLoc = gl.getUniformLocation(program, 'uProjection'); - - const positionLoc = gl.getAttribLocation(program, 'aPosition'); - const valueLoc = gl.getAttribLocation(program, 'aValue'); - - const setupScene = (sceneParams) => { - const { - projectionMatrix, - viewMatrix, - } = sceneParams; - - gl.useProgram(program); - - gl.uniformMatrix4fv(projLoc, false, new Float32Array(projectionMatrix)); - gl.uniformMatrix4fv(viewLoc, false, new Float32Array(viewMatrix)); - - // doing this here because it's the same for all world stuff - gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.identity())); - - gl.enableVertexAttribArray(positionLoc); - gl.enableVertexAttribArray(valueLoc); - }; - - const drawObject = (objectParams) => { - const { - position, - orientation, - value, - glBuffer, - } = objectParams; - - gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.product( - se3.translation(...position), orientation))); - - gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer); - - gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0); - gl.vertexAttribPointer(valueLoc, 2, gl.FLOAT, false, 20, 12); - - gl.disable(gl.CULL_FACE); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - gl.enable(gl.CULL_FACE); - }; - - return { - setupScene, - drawObject, - }; -} - function closeToPlanet(context) { - const body = findSoi(context); + const body = findSoi(context.universe, context.player.position); const relativePos = linalg.diff(context.player.position, body.position); return linalg.norm(relativePos) < 20; @@ -531,103 +299,6 @@ function updatePhysics(time: number, context) { function updateGeometry(context, timeout_ms = 10) { } -function getObjects(context, body, parentPosition = undefined) { - const objects = []; - const {gl, glContext, player} = context; - const {position, orientation, glowColor} = body; - - if (body.glBuffer === undefined) { - body.glBuffer = makeBufferFromFaces(gl, body.geometry); - } - - objects.push({ - geometry: body.glBuffer, - orientation, - position, - glContext, - glowColor, - }); - if (parentPosition !== undefined) { - const orbitObject = makeOrbitObject(gl, context.orbitGlContext, body.orbit, parentPosition); - objects.push(orbitObject); - } else { - const shipOrientation = [ - se3.rotationOnly(player.tf), - se3.rotationOnly(context.camera.tf), - se3.rotxyz(-Math.PI / 2, Math.PI / 2, Math.PI / 2), - ].reduce(se3.product); - const shipPos = player.position; - objects.push({ - geometry: makeBufferFromFaces(gl, context.spaceship), - orientation: shipOrientation, - position: shipPos, - glContext, - }); - } - - if (body.children !== undefined) { - for (const child of body.children) { - objects.push(...getObjects(context, child, position)); - } - } - - return objects; -} - -function sunDirection(position: linalg.vec3) { - return linalg.scale(position, 1/linalg.norm(position)); -} - -function draw(context) { - const {gl, camera, player, universe, orbit, orbitGlContext, orbitBody} = context; - const {skyColor, ambiantLight, projMatrix} = context; - const objects = getObjects(context, universe); - if (orbit !== undefined && orbit.excentricity < 1) { - objects.push(makeOrbitObject(gl, orbitGlContext, orbit, orbitBody.position)); - } - - gl.clearColor(...skyColor, 1.0); - gl.clearDepth(1.0); - gl.enable(gl.DEPTH_TEST); - gl.depthFunc(gl.LEQUAL); - - gl.enable(gl.CULL_FACE); - gl.cullFace(gl.BACK); - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - - const viewMatrix = se3.inverse([ - player.tf, // player position & orientation - camera.tf, // camera orientation relative to player - se3.translation(0, 1, 4), // step back from the player - ].reduce(se3.product)); - let lastGlContext; - - for (const {position, orientation, geometry, glContext, glowColor} of objects) { - if (glContext !== lastGlContext) { - glContext.setupScene({ - projectionMatrix: projMatrix, - viewMatrix, - ambiantLightAmount: ambiantLight, - }); - } - - lastGlContext = glContext; - const lightDirection = sunDirection(position); - - glContext.drawObject({ - position, - orientation, - glBuffer: geometry.glBuffer, - numVertices: geometry.numVertices, - lightDirection, - glowColor: glowColor || [0, 0, 0], - }); - } -} - function tick(time: number, context) { handleInput(context); const simTime = time * 0.001 + context.timeOffset; @@ -723,4 +394,4 @@ async function main() { requestAnimationFrame(time => tick(time, context)); } -window.onload = main; +window.onload = main; \ No newline at end of file